145 lines
4.3 KiB
Go
145 lines
4.3 KiB
Go
package deck
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"sort"
|
|
|
|
"bd2server/internal/server/gamedata"
|
|
"bd2server/internal/server/wire"
|
|
)
|
|
|
|
func (s *Store) ConfigureWaypoints(load func(uint64) (gamedata.WaypointPack, error), validate func(uint64, bool) error) error {
|
|
if load == nil || validate == nil {
|
|
return errors.New("deck: incomplete waypoint runtime")
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.waypointDesign = load
|
|
s.waypointPack = validate
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) ActivatedWaypoint(pack, id uint64) bool {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return hasWaypoint(s.state.Waypoints[pack], id)
|
|
}
|
|
func hasWaypoint(points []uint64, id uint64) bool {
|
|
for _, p := range points {
|
|
if p == id {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func validWaypointState(packs map[uint64][]uint64) error {
|
|
for pack, points := range packs {
|
|
if pack == 0 || pack > math.MaxInt32 || points == nil {
|
|
return errors.New("deck: invalid saved waypoint pack")
|
|
}
|
|
seen := map[uint64]bool{}
|
|
for _, id := range points {
|
|
if id == 0 || id > math.MaxInt32 || seen[id] {
|
|
return errors.New("deck: invalid saved waypoint activation")
|
|
}
|
|
seen[id] = true
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) handleWaypoint(path string, req []byte) (int, []byte, bool, error) {
|
|
seq, err := requestSequence(req)
|
|
if err != nil {
|
|
return 0, nil, true, err
|
|
}
|
|
pack, found, err := wire.Varint(req, 2)
|
|
if err != nil || !found || pack == 0 || pack > math.MaxInt32 {
|
|
return 0, nil, true, errors.New("deck: invalid waypoint pack")
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
if s.waypointDesign == nil || s.waypointPack == nil {
|
|
return 0, nil, true, errors.New("deck: waypoint runtime unavailable")
|
|
}
|
|
if err = s.waypointPack(pack, path == "/WaypointUse"); err != nil {
|
|
return 0, nil, true, err
|
|
}
|
|
design, err := s.waypointDesign(pack)
|
|
if err != nil {
|
|
return 0, nil, true, err
|
|
}
|
|
if path == "/WaypointInfo" {
|
|
ids := append([]uint64(nil), s.state.Waypoints[pack]...)
|
|
sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
|
|
var packed []byte
|
|
for _, id := range ids {
|
|
if _, known := design.Points[id]; !known {
|
|
return 0, nil, true, fmt.Errorf("deck: saved waypoint %d absent from pack%d", id, pack)
|
|
}
|
|
packed = binary.AppendUvarint(packed, id)
|
|
}
|
|
if len(packed) == 0 {
|
|
return 31, nil, true, nil
|
|
}
|
|
return 31, wire.AppendBytes(nil, 1, packed), true, nil
|
|
}
|
|
id, found, err := wire.Varint(req, 3)
|
|
if err != nil || !found || id == 0 || id > math.MaxInt32 {
|
|
return 0, nil, true, errors.New("deck: invalid waypoint")
|
|
}
|
|
if _, known := design.Points[id]; !known {
|
|
return 0, nil, true, errors.New("deck: unknown waypoint")
|
|
}
|
|
if path == "/WaypointSave" {
|
|
if hasWaypoint(s.state.Waypoints[pack], id) {
|
|
return 32, nil, true, nil
|
|
}
|
|
next := clone(s.state)
|
|
next.Waypoints[pack] = append(next.Waypoints[pack], id)
|
|
err = s.commit(next)
|
|
return 32, nil, true, err
|
|
}
|
|
end, found, err := wire.Varint(req, 4)
|
|
if err != nil || !found || end == 0 || end > math.MaxInt32 || end == id {
|
|
return 0, nil, true, errors.New("deck: invalid waypoint destination")
|
|
}
|
|
target, known := design.Points[end]
|
|
if !known || target.MapID == 0 || !hasWaypoint(s.state.Waypoints[pack], end) || !hasWaypoint(s.state.Waypoints[pack], id) {
|
|
return 0, nil, true, errors.New("deck: waypoint is not activated")
|
|
}
|
|
moves, found, err := wire.Varint(req, 5)
|
|
if err != nil || !found || moves != 1 {
|
|
return 0, nil, true, errors.New("deck: invalid waypoint move count")
|
|
}
|
|
if reply, ok := s.cachedReplyLocked("waypoint-use", seq); ok {
|
|
return reply.code, reply.body, true, nil
|
|
}
|
|
if design.PriceUnit != 0 {
|
|
if s.wallet == nil || s.sessionID == "" {
|
|
return 0, nil, true, errors.New("deck: waypoint wallet session unavailable")
|
|
}
|
|
identity := fmt.Sprintf("waypoint:%s:%d", s.sessionID, seq)
|
|
switch design.PriceType {
|
|
case 4:
|
|
_, err = s.wallet.SpendGoldOnce(identity, design.PriceUnit)
|
|
case 3:
|
|
_, err = s.wallet.SpendFreeJewelryOnce(identity, design.PriceUnit)
|
|
case 2:
|
|
_, err = s.wallet.SpendJewelryOnce(identity, design.PriceUnit)
|
|
default:
|
|
err = errors.New("deck: unsupported waypoint currency")
|
|
}
|
|
if err != nil {
|
|
return 0, nil, true, err
|
|
}
|
|
}
|
|
// The client performs its warp and sends SaveUserPosition with scene coordinates.
|
|
s.rememberReplyLocked("waypoint-use", seq, 33, nil)
|
|
return 33, nil, true, nil
|
|
}
|