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

102 lines
3.0 KiB
Go

package world
import (
"bd2server/internal/server/domain/command"
"bd2server/internal/server/protocol/wire"
"encoding/json"
"fmt"
"slices"
)
func (s *Service) CompleteNPCReputation(ctx command.Context, identity string, pack int, group uint64) ([]byte, error) {
if s.npcReputation == nil || identity == "" || group == 0 || !s.packUnlocked(ctx, pack) {
return nil, ErrInvalidRequest
}
d, err := s.npcReputation.load(pack)
if err != nil {
return nil, err
}
rule, ok := d.Groups[group]
if !ok {
return nil, fmt.Errorf("world: unknown reputation group")
}
v, err := s.npcReputation.snapshot(ctx)
if err != nil {
return nil, err
}
claim := fmt.Sprintf("%s:%d:%d", identity, pack, group)
if !v.Claims[claim] {
v.Claims[claim] = true
v.Starts[reputationKey(pack, group)] = s.npcReputation.now().Unix()
b, e := json.Marshal(v)
if e != nil {
return nil, e
}
if e = s.npcReputation.store.Save(ctx.State, "npcreputation", b); e != nil {
return nil, e
}
}
state, elapsed, err := s.reputationState(ctx, pack, rule)
if err != nil {
return nil, err
}
return reputationWire(group, state, elapsed), nil
}
func reputationWire(group, state, elapsed uint64) []byte {
b := wire.AppendVarint(nil, 1, group)
b = wire.AppendVarint(b, 2, state)
if elapsed > 0 {
b = wire.AppendVarint(b, 3, elapsed)
}
return b
}
func (s *Service) npcReputationRows(ctx command.Context, pack int) ([][]byte, error) {
if s.npcReputation == nil {
return nil, nil
}
d, err := s.npcReputation.load(pack)
if err != nil {
return nil, err
}
var groups []uint64
for id := range d.Groups {
groups = append(groups, id)
}
slices.Sort(groups)
var rows [][]byte
for _, id := range groups {
state, elapsed, e := s.reputationState(ctx, pack, d.Groups[id])
if e != nil {
return nil, e
}
rows = append(rows, reputationWire(id, state, elapsed))
}
return rows, nil
}
// handleQuestUpdate accepts the existing task update packet used when an NPC
// conversation finishes. Ordinary Talk is local and sends AchievementUpdate,
// not a separate NPC dialog packet. QuestUpdate has no NPC identity to validate.
func (s *Service) handleQuestUpdate(ctx command.Context, request []byte) (int, []byte, bool, error) {
quest, pack, err := requestQuest(request)
if err != nil {
return 0, nil, true, err
}
current, err := s.CurrentPackID(ctx)
if err != nil || current != pack || !s.canClear(ctx, pack, quest) {
return 0, nil, true, ErrInvalidRequest
}
if s.state.QuestCleared(quest, pack, s.questDifficultyFor(pack, quest)) {
// A delayed replay must not reinsert a cleared quest into active progress.
return 19, wire.AppendBytes(wire.AppendVarint(nil, 1, uint64(quest)), 2, nil), true, nil
}
if _, err := s.state.UpdateQuest(ctx, request); err != nil {
return 0, nil, true, err
}
// field 2 is RewardDBInfoBundle, not QuestDBInfo. Task completion/claims
// remain in QuestClear; an update alone must not invent or duplicate rewards.
return 19, wire.AppendBytes(wire.AppendVarint(nil, 1, uint64(quest)), 2, nil), true, nil
}