Files
bd2/go/internal/server/world/field_object_effects.go
T

226 lines
5.9 KiB
Go

package world
import (
"bd2server/internal/server/gamedata"
"bd2server/internal/server/player"
"bd2server/internal/server/wire"
"fmt"
"math"
)
func (s *Service) openFieldObjectResponse(pack, group, id int) ([]byte, error) {
design, err := s.fieldObjectDesign(pack)
if err != nil {
return nil, err
}
obj, ok := design.Objects[id]
if !ok || obj.GroupID != group || !s.packUnlocked(pack) || !s.fieldObjectCurrentPack(pack) {
return nil, ErrInvalidRequest
}
if err := s.validateFieldObjectMap(pack, obj.MapID); err != nil {
return nil, err
}
period, err := s.fieldObjectPeriodFor(pack, obj)
if err != nil {
return nil, err
}
opened, err := s.state.FieldRewardOpened(pack, id, period)
if err != nil {
return nil, err
}
if opened {
return wire.AppendBytes(nil, 1, nil), nil
}
var effects []byte
if obj.BuffID != 0 {
buff, exists := s.fieldBuffs[uint64(obj.BuffID)]
if !exists {
return nil, fmt.Errorf("world: missing field object buff %d", obj.BuffID)
}
row, chars, e := s.applyFieldObjectBuff(pack, buff)
if e != nil {
return nil, e
}
effects = wire.AppendBytes(effects, 2, row)
for _, char := range chars {
effects = wire.AppendBytes(effects, 3, char)
}
} else if obj.MonsterID != 0 && len(obj.Rewards) == 0 {
if s.monsterLoader == nil || s.monsterStore == nil {
return nil, fmt.Errorf("world: dynamic field monster runtime unavailable")
}
monsters, e := s.monsterLoader(pack)
if e != nil {
return nil, e
}
found := false
for _, monster := range monsters {
if monster.ID != obj.MonsterID {
continue
}
if !s.monsterEligible(pack, monster) {
return nil, ErrInvalidRequest
}
snapshot, e := s.loadMonsterState()
if e != nil {
return nil, e
}
state, e := s.monsterState(&snapshot, pack, monster)
if e != nil {
return nil, e
}
if e = s.saveMonsterState(snapshot); e != nil {
return nil, e
}
effects = wire.AppendBytes(effects, 4, monsterWire(monster, state, true))
found = true
break
}
if !found {
return nil, fmt.Errorf("world: missing field object monster %d", obj.MonsterID)
}
}
if obj.BuffID != 0 {
if err := s.state.MarkFieldRewardOpened(pack, id, period); err != nil {
return nil, err
}
return append(wire.AppendBytes(nil, 1, nil), effects...), nil
}
bundle, err := s.openFieldObject(pack, group, id)
if err != nil {
return nil, err
}
return append(wire.AppendBytes(nil, 1, bundle), effects...), nil
}
func (s *Service) applyFieldObjectBuff(pack int, buff gamedata.FieldBuffDesign) ([]byte, [][]byte, error) {
if buff.ID == 0 || buff.Type > 5 || buff.TargetType > 2 || math.IsNaN(buff.Value) || math.IsInf(buff.Value, 0) || buff.Value < 0 || math.IsNaN(buff.Time) || math.IsInf(buff.Time, 0) || buff.Time < 0 {
return nil, nil, fmt.Errorf("world: invalid field object buff")
}
row := wire.AppendVarint(nil, 1, buff.ID)
if buff.Type <= 1 {
if buff.Time <= 0 || buff.Time > float64(^uint32(0)>>1) {
return nil, nil, fmt.Errorf("world: invalid persistent field buff duration")
}
if buff.Type == 0 {
row = wire.AppendVarint(row, 2, uint64(buff.Time))
} else {
row = wire.AppendVarint(row, 3, uint64(s.monsterTime().UnixMilli()+int64(buff.Time*1000)))
}
prior, err := s.state.FieldBuffs()
if err != nil {
return nil, nil, err
}
for _, raw := range prior {
id, _, e := wire.Varint(raw, 1)
if e != nil {
return nil, nil, e
}
old, found := s.fieldBuffs[id]
if !found {
return nil, nil, fmt.Errorf("world: saved field buff absent from design")
}
if old.Type == buff.Type {
if e := s.state.RemoveFieldBuff(id); e != nil {
return nil, nil, e
}
}
}
if err := s.state.SaveFieldBuff(buff.ID, row); err != nil {
return nil, nil, err
}
return row, nil, nil
}
if buff.Type == 4 || buff.Type == 5 {
chars, err := s.applyMonsterFieldDamage(pack, buff.ID, "")
return row, chars, err
}
if s.characters == nil || s.decks == nil {
return nil, nil, fmt.Errorf("world: field healing runtime unavailable")
}
indices, err := s.fieldBuffTargets(pack, buff.TargetType)
if err != nil {
return nil, nil, err
}
var chars [][]byte
for _, index := range indices {
c, found := s.characters.Find(index)
if !found {
return nil, nil, fmt.Errorf("world: field healing character missing")
}
hp, err := s.characters.CurrentHealth(index)
if err != nil {
return nil, nil, err
}
if hp == 0 {
continue
}
max, err := s.characters.MaxHealth(index)
if err != nil {
return nil, nil, err
}
amount := buff.Value
if buff.Type == 3 {
amount *= float64(max)
}
remaining := max
if hp < max && amount < float64(max-hp) {
remaining = hp + uint64(amount)
}
if err = s.characters.SetCurrentHealth(index, remaining); err != nil {
return nil, nil, err
}
c.HP = remaining
chars = append(chars, player.CharacterWire(c))
}
return row, chars, nil
}
// ConsumeFieldBattleBuff runs inside the successful BattleEnter transaction.
// A persisted battle identity makes retries and reconnects safe.
func (s *Service) ConsumeFieldBattleBuff(identity string) error {
if identity == "" {
return ErrInvalidRequest
}
rows, err := s.state.FieldBuffs()
if err != nil {
return err
}
var active []byte
for _, row := range rows {
id, _, e := wire.Varint(row, 1)
if e != nil {
return e
}
buff, found := s.fieldBuffs[id]
if !found {
return fmt.Errorf("world: saved battle field buff absent from design")
}
if buff.Type == 0 {
active = row
break
}
}
if active == nil {
return nil
}
claimed, err := s.state.ClaimFieldBuffBattle(identity)
if err != nil || !claimed {
return err
}
id, _, err := wire.Varint(active, 1)
if err != nil {
return err
}
count, _, err := wire.Varint(active, 2)
if err != nil {
return err
}
if count <= 1 {
return s.state.RemoveFieldBuff(id)
}
updated := wire.AppendVarint(nil, 1, id)
updated = wire.AppendVarint(updated, 2, count-1)
return s.state.SaveFieldBuff(id, updated)
}