Implement hunting AP and progress, event tasks, exchanges, games, stories and actions; connect battle and reward handling. Add CashShop plugin and installation, server purchases and entitlements, recharge rules and plugin version metadata. Distribute binary calendars independently; cache GameData in memory and fix single-connection query deadlocks. Improve automatic login and recovery diagnostics; document configuration and release flows.
238 lines
6.0 KiB
Go
238 lines
6.0 KiB
Go
package eventplay
|
|
|
|
import (
|
|
"bd2server/internal/server/gamedata"
|
|
"bd2server/internal/server/wire"
|
|
"fmt"
|
|
"math"
|
|
"strings"
|
|
)
|
|
|
|
func (s *Service) validateStage(f string, id, mode uint64) error {
|
|
switch f {
|
|
case "Rhythm":
|
|
r, e := s.design.Row("RhythmGameMusicTable", 5, id)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
if mode > 2 {
|
|
return fmt.Errorf("eventplay: unknown rhythm mode")
|
|
}
|
|
_ = r
|
|
case "Hopscotch":
|
|
_, e := s.design.Row("HopscotchStageTable", 6, id)
|
|
return e
|
|
case "Sichuan":
|
|
_, e := s.design.Row("SichuanStageTable", 2, id)
|
|
return e
|
|
case "Survival":
|
|
if len(s.design.Tables["FieldMiniGameSurvivalTable"]) == 0 {
|
|
return fmt.Errorf("eventplay: survival design missing")
|
|
}
|
|
case "Action":
|
|
if len(s.design.Tables["ActionGameDefaultTable"]) == 0 {
|
|
return fmt.Errorf("eventplay: action design missing")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
func (s *Service) endScore(f string, req []byte, a Run) (uint64, error) {
|
|
var score uint64
|
|
switch f {
|
|
case "Run":
|
|
score = num(req, 3)
|
|
case "Field":
|
|
score = a.Score
|
|
case "Action":
|
|
score = num(req, 7)
|
|
case "Rhythm":
|
|
score = num(req, 4)
|
|
r, e := s.design.Row("RhythmGameMusicTable", 5, a.Stage)
|
|
if e != nil {
|
|
return 0, e
|
|
}
|
|
maxField := 8
|
|
if a.Mode == 1 {
|
|
maxField = 2
|
|
} else if a.Mode == 2 {
|
|
maxField = 1
|
|
}
|
|
if score > num(r, maxField) {
|
|
return 0, fmt.Errorf("eventplay: rhythm score exceeds static maximum")
|
|
}
|
|
judgments := uint64(0)
|
|
err := wire.Walk(req, func(f wire.Field) error {
|
|
if f.Number == 3 {
|
|
if f.Type != 2 {
|
|
return wire.ErrMalformed
|
|
}
|
|
typ, count := num(f.Value, 1), num(f.Value, 2)
|
|
if typ > 10 || count > math.MaxInt32 {
|
|
return fmt.Errorf("eventplay: invalid note judgment")
|
|
}
|
|
judgments += count
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
noteField := 10
|
|
if a.Mode > 0 {
|
|
noteField = 4
|
|
}
|
|
if judgments > num(r, noteField) {
|
|
return 0, fmt.Errorf("eventplay: judgment count exceeds designed notes")
|
|
}
|
|
case "Sichuan":
|
|
score = num(req, 4)
|
|
case "Hopscotch":
|
|
score = num(req, 2)
|
|
if score > 10000 {
|
|
return 0, fmt.Errorf("eventplay: captured area exceeds 100 percent")
|
|
}
|
|
case "Survival":
|
|
score = num(req, 3)
|
|
d := s.design.Tables["FieldMiniGameSurvivalTable"][0]
|
|
if score > num(d, 24) {
|
|
return 0, fmt.Errorf("eventplay: survival time exceeds design")
|
|
}
|
|
case "Defense":
|
|
score = num(req, 3)
|
|
}
|
|
if score > math.MaxInt32 {
|
|
return 0, fmt.Errorf("eventplay: submitted score exceeds protocol range")
|
|
}
|
|
return score, nil
|
|
}
|
|
func (s *Service) scoreRewards(game, score uint64) (uint64, []gamedata.BattleReward, error) {
|
|
d, e := s.design.Row("PackEventMiniGameTable", 8, game)
|
|
if e != nil {
|
|
return 0, nil, e
|
|
}
|
|
return s.design.ScoreRewards(num(d, 10), score)
|
|
}
|
|
func (s *Service) progress(path string, req []byte, rk string, next *snapshot, key string) ([]byte, error) {
|
|
a, ok := next.Runs[rk]
|
|
if !ok {
|
|
return nil, fmt.Errorf("eventplay: no active run")
|
|
}
|
|
var out []byte
|
|
switch path {
|
|
case "/MiniGameFieldScore":
|
|
d, e := s.design.Field(a.Game)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
ids, e := list(req, 3)
|
|
if e != nil || len(ids) == 0 {
|
|
return nil, fmt.Errorf("eventplay: missing field objects")
|
|
}
|
|
for _, id := range ids {
|
|
v, ok := d.Objects[id]
|
|
if !ok || has(a.Objects, id) {
|
|
return nil, fmt.Errorf("eventplay: object invalid or already scored")
|
|
}
|
|
a.Objects = append(a.Objects, id)
|
|
if v.Type == 1 {
|
|
a.Score += v.Point
|
|
} else if v.Type == 2 {
|
|
a.HP += v.Point
|
|
}
|
|
}
|
|
out = wire.AppendVarint(nil, 1, a.Score)
|
|
out = wire.AppendVarint(out, 2, a.HP)
|
|
case "/MiniGameSurvivalPlay":
|
|
exp, coin, e := s.survivalProgress(req, &a)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
if s.onProgress != nil {
|
|
total := uint64(0)
|
|
for id, count := range a.Killed {
|
|
previous := next.Runs[rk].Killed[id]
|
|
if count > previous {
|
|
total += count - previous
|
|
}
|
|
}
|
|
if total > 0 {
|
|
if e = s.onProgress(319, a.Game, total); e != nil {
|
|
return nil, e
|
|
}
|
|
}
|
|
}
|
|
out = wire.AppendVarint(nil, 1, coin)
|
|
out = wire.AppendVarint(out, 2, exp)
|
|
case "/MiniGameSurvivalSkillSelectList":
|
|
var e error
|
|
out, e = s.survivalOffers(&a)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
case "/MiniGameSurvivalSkillUp":
|
|
id := num(req, 2)
|
|
if a.SkillCredits == 0 || !has(a.Offers, id) {
|
|
return nil, fmt.Errorf("eventplay: skill not earned or offered")
|
|
}
|
|
group, err := s.design.Row("FieldMiniGameSkillGroupTable", 1, id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("eventplay: invalid skill")
|
|
}
|
|
if item := num(req, 3); item != 0 {
|
|
if _, e := s.design.Row("FieldMiniGameSurvivalItemTable", 1, item); e != nil {
|
|
return nil, e
|
|
}
|
|
}
|
|
if a.SkillLevels == nil {
|
|
a.SkillLevels = map[uint64]uint64{}
|
|
}
|
|
level := a.SkillLevels[id] + 1
|
|
if level > num(group, 2) {
|
|
return nil, fmt.Errorf("eventplay: survival skill at maximum")
|
|
}
|
|
capacity := uint64(0)
|
|
for _, r := range s.design.Tables["FieldMiniGameSurvivalTable"] {
|
|
if num(r, 21) > capacity {
|
|
capacity = num(r, 21)
|
|
}
|
|
}
|
|
if !has(a.Skills, id) && uint64(len(a.SkillLevels)) >= capacity {
|
|
return nil, fmt.Errorf("eventplay: survival skill capacity reached")
|
|
}
|
|
a.SkillLevels[id] = level
|
|
a.SkillCredits--
|
|
a.Offers = nil
|
|
if level == num(group, 2) && s.onProgress != nil {
|
|
if e := s.onProgress(318, a.Game, 1); e != nil {
|
|
return nil, e
|
|
}
|
|
if e := s.onProgress(321, id, 1); e != nil {
|
|
return nil, e
|
|
}
|
|
}
|
|
if !has(a.Skills, id) {
|
|
a.Skills = append(a.Skills, id)
|
|
}
|
|
v := wire.AppendVarint(nil, 1, id)
|
|
v = wire.AppendVarint(v, 2, level)
|
|
out = wire.AppendBytes(nil, 1, v)
|
|
default:
|
|
if strings.Contains(path, "QuickReward") || strings.Contains(path, "Reward") {
|
|
return nil, fmt.Errorf("eventplay: reward requires a completed unpaid score record")
|
|
}
|
|
return nil, fmt.Errorf("eventplay: unsupported run transition")
|
|
}
|
|
next.Runs[rk] = a
|
|
_ = key
|
|
return out, nil
|
|
}
|
|
func has(a []uint64, id uint64) bool {
|
|
for _, x := range a {
|
|
if x == id {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
func seqSeed(n uint64) uint64 { return n & 2147483647 }
|