package dnfbridge import ( "context" "fmt" "sort" ) type currentActorSkillResource struct { id uint16 rank byte } func (r *csharpLegacyUserInfoReader) currentActorSkillResources() ([]currentActorSkillResource, error) { if r == nil || r.service == nil || r.characterID == "" { return nil, nil } repos, ready := r.service.repositoryGroup() if !ready || repos.Skill == nil { return nil, nil } ctx := r.ctx if ctx == nil { ctx = context.Background() } record, found, err := repos.Skill.Load(ctx, r.characterID) if err != nil { return nil, err } if !found { return nil, nil } rows := make([]currentActorSkillResource, 0, len(record.Skills)) for id, state := range record.Skills { if !state.Enabled || state.Level <= 0 { continue } if id < 0 || id > 0xffff || state.Level > 0xff { return nil, fmt.Errorf("actor skill resource exceeds native u16/u8 range: skill=%d rank=%d", id, state.Level) } rows = append(rows, currentActorSkillResource{id: uint16(id), rank: byte(state.Level)}) } if len(rows) > 0xff { return nil, fmt.Errorf("actor skill resource count exceeds native u8 range: %d", len(rows)) } sort.Slice(rows, func(i, j int) bool { return rows[i].id < rows[j].id }) return rows, nil } func (r *csharpLegacyUserInfoReader) writeCurrentActorSkillResources(w *packetWriter) { if r != nil && r.suppressActorSkillResources { w.writeByte(0xff) for tree := 0; tree < currentSkillInfoTreeCount; tree++ { w.writeByte(0) } if r.service != nil { r.service.logGameEvent(r.session, "game-actor-skill-resources-suppressed", "character_id", r.characterID, "tree_count", currentSkillInfoTreeCount, "reason", "existing_actor_context_rebind_must_not_replay_learned_skill_resources") } return } rows, err := r.currentActorSkillResources() if err != nil { if r.service != nil { r.service.logGameEvent(r.session, "game-actor-skill-resources-unavailable", "character_id", r.characterID, "error", err) } rows = nil } if len(rows) > 0 && r.service != nil { r.service.logGameEvent(r.session, "game-actor-skill-resources-built", "character_id", r.characterID, "tree_count", currentSkillInfoTreeCount, "skill_ranks", fmt.Sprint(rows), "body_source", "mode1_native_u16_skill_u8_persisted_rank") } w.writeByte(0xff) // Current EXE sub_2003420 consumes both groups even for the selected // actor, but applies the u16 ID/u8 rank rows only to remote actors. As in // op19, both current skill pages share this persisted learned-skill set. for tree := 0; tree < currentSkillInfoTreeCount; tree++ { w.writeByte(byte(len(rows))) for _, row := range rows { w.writeUint16(row.id) w.writeByte(row.rank) } } }