199 lines
5.9 KiB
Go
199 lines
5.9 KiB
Go
package world
|
|
|
|
import (
|
|
"bd2server/internal/server/gamedata"
|
|
"bd2server/internal/server/player"
|
|
"bd2server/internal/server/wire"
|
|
"bytes"
|
|
"fmt"
|
|
)
|
|
|
|
func (s *Service) grantFieldMonster(pack int, m gamedata.FieldMonsterDesign, identity string) ([]byte, error) {
|
|
if s.researchEconomy == nil {
|
|
return nil, fmt.Errorf("world: monster economy unavailable")
|
|
}
|
|
var rewards []gamedata.Reward
|
|
if m.Reward.Type != 0 && m.Reward.Count > 0 {
|
|
rewards = append(rewards, m.Reward)
|
|
} else if m.BattleDeck != 0 {
|
|
if s.monsterRewards == nil {
|
|
return nil, fmt.Errorf("world: monster reward loader unavailable")
|
|
}
|
|
rows, e := s.monsterRewards(pack, m.BattleDeck)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
for _, r := range rows {
|
|
rewards = append(rewards, gamedata.Reward{Type: r.Type, ID: r.ID, Count: r.Count})
|
|
}
|
|
}
|
|
return s.researchEconomy.Apply(identity, nil, rewards)
|
|
}
|
|
func monsterBundleFields(bundle []byte, itemField, equipField int) ([]byte, error) {
|
|
var b []byte
|
|
err := wire.Walk(bundle, func(f wire.Field) error {
|
|
if f.Type == 2 && f.Number == 1 {
|
|
b = wire.AppendBytes(b, itemField, f.Value)
|
|
} else if f.Type == 2 && f.Number == 4 {
|
|
b = wire.AppendBytes(b, equipField, f.Value)
|
|
}
|
|
return nil
|
|
})
|
|
return b, err
|
|
}
|
|
func (s *Service) handleFieldMonsterEvent(path string, request []byte) (int, []byte, bool, error) {
|
|
seqCheck, present, seqErr := wire.Varint(request, 1)
|
|
if seqErr != nil || !present || seqCheck == 0 || seqCheck > 0x7fffffff {
|
|
return 0, nil, true, ErrInvalidRequest
|
|
}
|
|
code := 83
|
|
if path == "/FieldMonsterDamage" {
|
|
code = 85
|
|
}
|
|
id, e := requestPack(request)
|
|
if e != nil {
|
|
return code, nil, true, e
|
|
}
|
|
pack, e := s.CurrentPackID()
|
|
if e != nil || !s.packUnlocked(pack) {
|
|
return code, nil, true, ErrInvalidRequest
|
|
}
|
|
m, found, e := s.findFieldMonster(pack, id)
|
|
if e != nil || !found || !s.monsterEligible(pack, m) {
|
|
return code, nil, true, ErrInvalidRequest
|
|
}
|
|
if path == "/FieldMonsterEvent" && m.Type != 2 && m.Type != 3 {
|
|
return code, nil, true, ErrInvalidRequest
|
|
}
|
|
if e = s.authorizeMonsterMap(pack, id); e != nil {
|
|
return code, nil, true, e
|
|
}
|
|
v, e := s.loadMonsterState()
|
|
if e != nil {
|
|
return code, nil, true, e
|
|
}
|
|
seq, _, _ := wire.Varint(request, 1)
|
|
if s.monsterSession == "" {
|
|
return code, nil, true, fmt.Errorf("world: missing monster session")
|
|
}
|
|
key := fmt.Sprintf("%s:%s:%d", path, s.monsterSession, seq)
|
|
if r, ok := v.Requests[key]; ok {
|
|
if !bytes.Equal(r.Request, request) {
|
|
return code, nil, true, ErrInvalidRequest
|
|
}
|
|
return code, r.Response, true, nil
|
|
}
|
|
state, e := s.monsterState(&v, pack, m)
|
|
if e != nil {
|
|
return code, nil, true, e
|
|
}
|
|
var b []byte
|
|
active := !state.Defeated && state.Respawn <= s.monsterTime().UnixMilli()
|
|
instance := fmt.Sprintf("fieldmonster:%s:%d", monsterKey(pack, s.questDifficulty(pack), id), state.Generation)
|
|
if active {
|
|
if m.Type == 2 && path == "/FieldMonsterEvent" && !v.Claims[instance] {
|
|
bundle, e := s.grantFieldMonster(pack, m, instance)
|
|
if e != nil {
|
|
return code, nil, true, e
|
|
}
|
|
b, e = monsterBundleFields(bundle, 3, 4)
|
|
if e != nil {
|
|
return code, nil, true, e
|
|
}
|
|
v.Claims[instance] = true
|
|
}
|
|
dash, _, _ := wire.Varint(request, 3)
|
|
if m.FieldBuff > 0 && (path == "/FieldMonsterDamage" || dash == 0) {
|
|
if s.monsterDamage == nil {
|
|
return code, nil, true, fmt.Errorf("world: monster damage runtime unavailable")
|
|
}
|
|
rows, e := s.monsterDamage(pack, m.FieldBuff, key)
|
|
if e != nil {
|
|
return code, nil, true, e
|
|
}
|
|
for _, r := range rows {
|
|
b = wire.AppendBytes(b, 1, r)
|
|
}
|
|
}
|
|
if path == "/FieldMonsterEvent" {
|
|
state.Defeated = true
|
|
state.Respawn = s.nextMonsterSpawn(m)
|
|
v.Monsters[monsterKey(pack, s.questDifficulty(pack), id)] = state
|
|
}
|
|
}
|
|
if path == "/FieldMonsterEvent" {
|
|
b = wire.AppendBytes(b, 2, monsterWire(m, state, true))
|
|
}
|
|
v.Requests[key] = fieldMonsterReply{Request: append([]byte(nil), request...), Response: b}
|
|
if e = s.saveMonsterState(v); e != nil {
|
|
return code, nil, true, e
|
|
}
|
|
return code, b, true, nil
|
|
}
|
|
|
|
// Current clients receive collision rewards in FieldMonsterEventResponse;
|
|
// FieldMonsterReward has no sender in this version and no pending reward queue.
|
|
func (s *Service) handleFieldMonsterReward(request []byte) (int, []byte, bool, error) {
|
|
seq, present, e := wire.Varint(request, 1)
|
|
if e != nil || !present || seq == 0 {
|
|
return 84, nil, true, ErrInvalidRequest
|
|
}
|
|
return 84, []byte{}, true, nil
|
|
}
|
|
func (s *Service) ApplyTalentMonsterSummon(identity string, c player.Character, r gamedata.TalentUseRule, ids []uint64) ([]byte, error) {
|
|
if identity == "" || len(ids) == 0 || r.Class != 20 {
|
|
return nil, ErrInvalidRequest
|
|
}
|
|
pack, e := s.CurrentPackID()
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
v, e := s.loadMonsterState()
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
seen := map[uint64]bool{}
|
|
var b []byte
|
|
for _, id := range ids {
|
|
if seen[id] {
|
|
return nil, ErrInvalidRequest
|
|
}
|
|
seen[id] = true
|
|
m, found, e := s.findFieldMonster(pack, int(id))
|
|
if e != nil || !found || m.Type != 2 || !s.monsterEligible(pack, m) {
|
|
return nil, ErrInvalidRequest
|
|
}
|
|
if e = s.authorizeMonsterMap(pack, int(id)); e != nil {
|
|
return nil, e
|
|
}
|
|
state, e := s.monsterState(&v, pack, m)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
if state.Defeated || state.Respawn > s.monsterTime().UnixMilli() {
|
|
return nil, fmt.Errorf("world: summon target is not spawned")
|
|
}
|
|
instance := fmt.Sprintf("fieldmonster:%s:%d", monsterKey(pack, s.questDifficulty(pack), m.ID), state.Generation)
|
|
if !v.Claims[instance] {
|
|
bundle, e := s.grantFieldMonster(pack, m, instance)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
extra, e := monsterBundleFields(bundle, 3, 4)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
b = append(b, extra...)
|
|
v.Claims[instance] = true
|
|
}
|
|
state.Defeated = true
|
|
state.Respawn = s.nextMonsterSpawn(m)
|
|
v.Monsters[monsterKey(pack, s.questDifficulty(pack), m.ID)] = state
|
|
b = wire.AppendBytes(b, 9, monsterWire(m, state, true))
|
|
}
|
|
if e = s.saveMonsterState(v); e != nil {
|
|
return nil, e
|
|
}
|
|
return b, nil
|
|
}
|