-
-
Notifications
You must be signed in to change notification settings - Fork 78
feat(family): a character's family is loaded, named, and described #2283
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
ee6ef9d
072fe74
deb846f
356e2d1
f363c60
e62882d
bae6a57
4c9c69e
d2fb5d0
66ae859
f218112
61019ee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -601,4 +601,16 @@ | |
| <data name="TIMESPACES_PARSED" xml:space="preserve"> | ||
| <value>{0} istanze scriptate caricate!</value> | ||
| </data> | ||
| <data name="FAMILY_AUTHORITY_HEAD" xml:space="preserve"> | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. all those are not translated we shouldnt add a value and not translate them
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Translated, and then corrected — the first pass invented them, which is worse than leaving them in English because nobody would have looked again. The client already has these four. and a captured line reads So the values are the client's wording now, not a translation of ours. Eighteen of the thirty-two were wrong — the first French one said 🤖 Addressed by Claude Code |
||
| <value>Capo</value> | ||
| </data> | ||
| <data name="FAMILY_AUTHORITY_ASSISTANT" xml:space="preserve"> | ||
| <value>Vicecapo</value> | ||
| </data> | ||
| <data name="FAMILY_AUTHORITY_MANAGER" xml:space="preserve"> | ||
| <value>Guardiano</value> | ||
| </data> | ||
| <data name="FAMILY_AUTHORITY_MEMBER" xml:space="preserve"> | ||
| <value>Membro</value> | ||
| </data> | ||
| </root> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| // __ _ __ __ ___ __ ___ ___ | ||
| // | \| |/__\ /' _/ / _//__\| _ \ __| | ||
| // | | ' | \/ |`._`.| \_| \/ | v / _| | ||
| // |_|\__|\__/ |___/ \__/\__/|_|_\___| | ||
| // | ||
|
|
||
| using NosCore.Algorithm.FamilyExperienceService; | ||
| using NosCore.Core.I18N; | ||
| using NosCore.Data.Enumerations.Family; | ||
| using NosCore.Data.Enumerations.I18N; | ||
| using NosCore.Packets.ServerPackets.Families; | ||
| using NosCore.Shared.Enumerations; | ||
| using System.Linq; | ||
|
|
||
| namespace NosCore.GameObject.Ecs.Extensions; | ||
|
|
||
| public static class FamilyExtensions | ||
| { | ||
| public static GidxPacket GenerateGidx(this PlayerComponentBundle player, | ||
| IGameLanguageLocalizer localizer, RegionType viewerLanguage) | ||
| { | ||
| var family = player.Family; | ||
| if (family == null) | ||
| { | ||
| return new GidxPacket | ||
| { | ||
| VisualType = VisualType.Player, | ||
| VisualId = player.VisualId, | ||
| FamilyId = null, | ||
| FamilyName = null, | ||
| FamilyLevel = 0 | ||
| }; | ||
| } | ||
|
|
||
| return new GidxPacket | ||
| { | ||
| VisualType = VisualType.Player, | ||
| VisualId = player.VisualId, | ||
| FamilyId = family.FamilyId, | ||
| FamilyName = FamilyTag(family, player.CharacterId, localizer, viewerLanguage), | ||
| FamilyLevel = family.FamilyLevel | ||
| }; | ||
| } | ||
|
|
||
| public static GInfoPacket? GenerateGInfo(this PlayerComponentBundle player, | ||
| IFamilyExperienceService familyExperienceService) | ||
| { | ||
| var family = player.Family; | ||
| if (family == null) | ||
| { | ||
| return null; | ||
| } | ||
|
|
||
| return new GInfoPacket | ||
| { | ||
| FamilyName = family.Name, | ||
| CharacterName = family.HeadCharacterName, | ||
| FamilyHeadGenderType = family.FamilyHeadGender, | ||
| FamilyLevel = family.FamilyLevel, | ||
| FamilyXp = family.FamilyExperience, | ||
| MaxFamilyXp = familyExperienceService.GetFamilyExperience(family.FamilyLevel), | ||
| MembersCount = (ushort)family.Members.Count, | ||
| MembersCapacity = family.MaxSize, | ||
| CharacterFamilyAuthority = (Packets.Enumerations.FamilyAuthority)family.AuthorityOf(player.CharacterId), | ||
| FamilyManagerCanInvit = family.ManagerCanInvite, | ||
| FamilyManagerCanNotice = family.ManagerCanNotice, | ||
| FamilyManagerCanShout = family.ManagerCanShout, | ||
| FamilyManagerCanGetHistory = family.ManagerCanGetHistory, | ||
| FamilyManagerAuthorityType = family.ManagerAuthorityType, | ||
| FamilyMemberCanGetHistory = family.MemberCanGetHistory, | ||
| FamilyMemberAuthorityType = family.MemberAuthorityType, | ||
| // A serialized field keeps its own spaces, and the notice is free text. | ||
| FamilyMessage = family.FamilyMessage?.Replace(' ', '^') | ||
| }; | ||
| } | ||
|
|
||
| private static string FamilyTag(Services.FamilyService.Family family, long characterId, | ||
| IGameLanguageLocalizer localizer, RegionType viewerLanguage) | ||
| { | ||
| var rank = family.AuthorityOf(characterId) switch | ||
| { | ||
| FamilyAuthority.Head => LanguageKey.FAMILY_AUTHORITY_HEAD, | ||
| FamilyAuthority.Assistant => LanguageKey.FAMILY_AUTHORITY_ASSISTANT, | ||
| FamilyAuthority.Manager => LanguageKey.FAMILY_AUTHORITY_MANAGER, | ||
| _ => LanguageKey.FAMILY_AUTHORITY_MEMBER | ||
| }; | ||
|
|
||
| return $"{family.Name}({localizer[rank, viewerLanguage]})".Replace(' ', '^'); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| // __ _ __ __ ___ __ ___ ___ | ||
| // | \| |/__\ /' _/ / _//__\| _ \ __| | ||
| // | | ' | \/ |`._`.| \_| \/ | v / _| | ||
| // |_|\__|\__/ |___/ \__/\__/|_|_\___| | ||
| // | ||
|
|
||
| using NosCore.Data.Dto; | ||
| using NosCore.Data.Enumerations.Family; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
|
|
||
| namespace NosCore.GameObject.Services.FamilyService | ||
| { | ||
| public class Family : FamilyDto | ||
| { | ||
| public IReadOnlyList<FamilyCharacterDto> Members { get; set; } = []; | ||
|
|
||
| /// <summary>The head's name, kept here because the head is usually offline.</summary> | ||
| public string HeadCharacterName { get; set; } = string.Empty; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we need a family member name lookup as it's not the only name we need. So likely we should just rely on Member
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, and it is worth settling before anything else reads a member.
The moment the member list window arrives that becomes N lookups, which is the wrong shape. Two ways out, and I would rather you pick than guess:
I lean on (1) and it is a small change — the service already has the id list in hand. Happy to put it in this PR or keep it for the one that adds the window; say which. 🤖 Addressed by Claude Code |
||
|
|
||
| /// <summary>Not in the member list means Member: the packet cannot say "none".</summary> | ||
| public FamilyAuthority AuthorityOf(long characterId) => | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why would somebody not be in member list?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They would not — and you are right that the fallback is hiding something.
The redundancy underneath it is real: the service reads the membership row, drops it, and That is tangled with the other comment about 🤖 Addressed by Claude Code |
||
| Members.FirstOrDefault(s => s.CharacterId == characterId)?.Authority | ||
| ?? FamilyAuthority.Member; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| // __ _ __ __ ___ __ ___ ___ | ||
| // | \| |/__\ /' _/ / _//__\| _ \ __| | ||
| // | | ' | \/ |`._`.| \_| \/ | v / _| | ||
| // |_|\__|\__/ |___/ \__/\__/|_|_\___| | ||
| // | ||
|
|
||
| using Mapster; | ||
| using NosCore.Dao.Interfaces; | ||
| using NosCore.Data.Dto; | ||
| using NosCore.Data.Enumerations.Family; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace NosCore.GameObject.Services.FamilyService | ||
| { | ||
| public class FamilyService(IDao<FamilyDto, long> familyDao, | ||
| IDao<FamilyCharacterDto, long> familyCharacterDao, | ||
| IDao<CharacterDto, long> characterDao) : IFamilyService | ||
| { | ||
| public async Task<Family?> GetFamilyAsync(long characterId) | ||
| { | ||
| var membership = await familyCharacterDao | ||
| .FirstOrDefaultAsync(s => s.CharacterId == characterId); | ||
| if (membership == null) | ||
| { | ||
| return null; | ||
| } | ||
|
|
||
| var familyDto = await familyDao | ||
| .FirstOrDefaultAsync(s => s.FamilyId == membership.FamilyId); | ||
| if (familyDto == null) | ||
| { | ||
| return null; | ||
| } | ||
|
|
||
| var family = familyDto.Adapt<Family>(); | ||
| family.Members = familyCharacterDao.Where(s => s.FamilyId == family.FamilyId)?.ToList() | ||
| ?? new List<FamilyCharacterDto>(); | ||
|
|
||
| var head = family.Members.FirstOrDefault(s => s.Authority == FamilyAuthority.Head); | ||
| if (head != null) | ||
| { | ||
| var headCharacter = await characterDao | ||
| .FirstOrDefaultAsync(s => s.CharacterId == head.CharacterId); | ||
| family.HeadCharacterName = headCharacter?.Name ?? string.Empty; | ||
| } | ||
|
|
||
| return family; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
interesting so those don't have a internationalized version on official?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They do, and I should have checked before adding four keys.
conststringcarries the four ranks at 10915-10918, with the parentheses already in the string, in every language the client ships:A captured line,
gidx 1 626114 5052 -Nemesis-(Membre) 8, matches the French row — so the server sends the rank as text, and the client is not resolving a number here the way it does forGame18NConstString.I have put the client's own wording into the resources rather than our translations, which fixed eighteen of the thirty-two values. What I have not done is move the source: they are still four
LanguageKeys rather than rows imported fromconststring.ConstStringParserreads only the UK file today, on the stated grounds that the other languages wrap the same numbers in translated prose — which is true for the ladders but not for these, where the text is the payload and differs per language by design. Turning that into a per-language import is a change to the parser's shape, so I would rather do it as its own PR if you want it. Say the word.🤖 Addressed by Claude Code