226 lines
5.9 KiB
Go
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)
|
|
}
|