Files
bd2/go/internal/server/domain/world/story_characters.go
T

95 lines
3.4 KiB
Go

package world
import (
"bd2server/internal/server/domain/command"
"bd2server/internal/server/domain/roster"
"fmt"
)
// currentBattleParty reads the player's chosen battle party without changing
// membership or positions. Ordinary story quests do not force their temporary
// guests into battle; fixed-character modes own their separate battle decks.
func (s *Service) currentBattleParty(ctx command.Context) ([]roster.Character, error) {
if s.decks == nil {
return nil, nil
}
var party []roster.Character
for _, entry := range s.decks.CurrentDeck() {
if s.characters == nil {
return nil, fmt.Errorf("world: battle character store unavailable")
}
c, found := s.characters.Find(ctx, entry.CharacterInvenIndex)
if !found {
return nil, fmt.Errorf("world: saved battle character %d unavailable", entry.CharacterInvenIndex)
}
party = append(party, c)
}
return party, nil
}
// resolveStoryCharacters provisions authored temporary guests for the pack's
// character and talent UI. Availability is independent of battle selection;
// StoryCharGroup supplies cosmetic field actors, not inventory identities.
func (s *Service) resolveStoryCharacters(ctx command.Context, packID, questID int) ([]roster.Character, error) {
if s.storyRoster == nil {
return nil, nil
}
if s.characters == nil {
return nil, fmt.Errorf("world: story character store unavailable")
}
_, ok := s.storyRoster.Formation(packID, questID)
if !ok {
return nil, fmt.Errorf("world: missing formation pack%d quest%d", packID, questID)
}
designs, err := s.storyRoster.Characters(packID, questID)
if err != nil {
return nil, err
}
temporary := make([]roster.Character, 0, len(designs))
reused := make([]roster.Character, 0, len(designs))
var authoredOrder []uint64
for _, d := range designs {
var existing roster.Character
for _, c := range s.characters.RawAll() {
if d.TemporaryPack != 0 && c.ID == d.CharacterID && c.Level == d.Level && (c.CostumeID == 0 || c.CostumeID == d.CostumeID) {
existing = c
break
}
}
if existing.InvenIndex != 0 {
reused = append(reused, existing)
authoredOrder = append(authoredOrder, existing.InvenIndex)
continue
}
if packID <= 0 || packID >= 65536 || d.CharacterID == 0 || d.CharacterID >= 1<<32 || d.Level == 0 || d.Level >= 256 {
return nil, fmt.Errorf("world: story instance namespace overflow")
}
index := roster.StoryCharacterIndexBase | uint64(packID)<<40 | d.CharacterID<<8 | d.Level
authoredOrder = append(authoredOrder, index)
temporary = append(temporary, roster.Character{InvenIndex: index, ID: d.CharacterID, HP: d.HP, Level: d.Level, CostumeID: d.CostumeID, TalentLevel: d.InitialTalentLevel})
}
if err := s.characters.EnsureStoryCharacters(ctx, temporary); err != nil {
return nil, err
}
authored := make(map[uint64]roster.Character, len(designs))
for _, c := range reused {
current, ok := s.characters.Find(ctx, c.InvenIndex)
if !ok {
return nil, fmt.Errorf("world: saved story instance unavailable")
}
authored[current.InvenIndex] = current
}
for _, c := range temporary {
current, ok := s.characters.Find(ctx, c.InvenIndex)
if !ok {
return nil, fmt.Errorf("world: story character was not saved")
}
authored[current.InvenIndex] = current
}
result := make([]roster.Character, 0, len(authoredOrder))
for _, index := range authoredOrder {
result = append(result, authored[index])
}
return result, nil
}