Files
GaCy-GO/internal/gameserver/battle_rules.go
T

191 lines
5.6 KiB
Go

package gameserver
import (
"encoding/json"
"fmt"
"strings"
"sync"
"gac-hkrpg-go/internal/common"
goproto "gac-hkrpg-go/protocol/proto"
)
// Game IDs and character exceptions are resource data with an upstream revision,
// not executable defaults. Missing or invalid rules must not silently produce a
// different battle.
type battleRules struct {
EntryAttackBuffs map[string]uint32 `json:"entry_attack_buffs"`
Source struct {
Revision string `json:"revision"`
} `json:"source"`
TechniqueBuffs map[uint32][]struct {
ID uint32 `json:"id"`
Owner string `json:"owner"`
DynamicValues map[string]float32 `json:"dynamic_values"`
} `json:"technique_buffs"`
GlobalPassives []struct {
Setting string `json:"setting"`
BuffID uint32 `json:"buff_id"`
OwnerSlot uint32 `json:"owner_slot"`
} `json:"global_passives"`
IgnoreToughness struct {
Avatars []uint32 `json:"avatars"`
BuffID uint32 `json:"buff_id"`
} `json:"ignore_toughness"`
BattleTargets []struct {
BattleType string `json:"battle_type"`
Targets map[uint32][]struct {
ID uint32 `json:"id"`
Progress uint32 `json:"progress"`
TotalProgress uint32 `json:"total_progress"`
} `json:"targets"`
} `json:"battle_targets"`
InvasionBuffs map[uint32]uint32 `json:"invasion_buffs"`
InvasionSupportBuff uint32 `json:"invasion_support_buff"`
}
var battleRulesOnce sync.Once
var loadedBattleRules battleRules
var battleRulesErr error
func loadBattleRules() (*battleRules, error) {
battleRulesOnce.Do(func() {
raw, err := common.ReadResourceFile("resources/BattleRules.json", "BattleRules.json")
if err != nil {
battleRulesErr = err
return
}
if err := json.Unmarshal(raw, &loadedBattleRules); err != nil {
battleRulesErr = err
return
}
if loadedBattleRules.Source.Revision == "" {
battleRulesErr = fmt.Errorf("battle rules need a source revision")
return
}
for _, rules := range loadedBattleRules.TechniqueBuffs {
for _, rule := range rules {
if rule.ID == 0 || (rule.Owner != "avatar" && rule.Owner != "leader") {
battleRulesErr = fmt.Errorf("invalid technique buff rule: %+v", rule)
return
}
}
}
for _, rule := range loadedBattleRules.GlobalPassives {
if rule.BuffID == 0 || rule.OwnerSlot >= 4 {
battleRulesErr = fmt.Errorf("invalid global passive rule: %+v", rule)
return
}
switch rule.Setting {
case "enable_sw_global", "enable_castorice_global", "enable_aha_global":
default:
battleRulesErr = fmt.Errorf("unknown global passive setting %q", rule.Setting)
return
}
}
})
if battleRulesErr != nil {
return nil, fmt.Errorf("load resources/BattleRules.json: %w", battleRulesErr)
}
return &loadedBattleRules, nil
}
func battleBuff(id, owner uint32, dynamic map[string]float32) *goproto.BattleBuff {
values := map[string]float32{"SkillIndex": 0}
for key, value := range dynamic {
values[key] = value
}
return &goproto.BattleBuff{Id: id, Level: 1, OwnerId: owner,
WaveFlag: ^uint32(0), TargetIndexList: []uint32{0}, DynamicValues: values}
}
func (r *battleRules) techniqueBuffs(a *common.AvatarJson, slot, leader uint32, imported []*goproto.BattleBuff) []*goproto.BattleBuff {
if len(a.Techniques) == 0 {
return nil
}
buffs := imported
// Explicit character rules in PearlSR replace the generic technique. The
// user's empty techniques array still disables techniques for that avatar.
if rules, ok := r.TechniqueBuffs[a.AvatarID]; ok {
buffs = nil
for _, rule := range rules {
owner := slot
if rule.Owner == "leader" {
owner = leader
}
buffs = append(buffs, battleBuff(rule.ID, owner, rule.DynamicValues))
}
}
if containsU32(r.IgnoreToughness.Avatars, a.AvatarID) {
found := false
for _, buff := range buffs {
if buff.Id == r.IgnoreToughness.BuffID {
found = true
}
}
if !found {
buffs = append(buffs, battleBuff(r.IgnoreToughness.BuffID, slot, nil))
}
}
// Global passives are handled once, with their configured owner and setting.
result := make([]*goproto.BattleBuff, 0, len(buffs))
for _, buff := range buffs {
global := false
for _, rule := range r.GlobalPassives {
if buff.Id == rule.BuffID {
global = true
break
}
}
if !global {
result = append(result, buff)
}
}
return result
}
func (r *battleRules) addGlobalPassives(battle *goproto.SceneBattleInfo, player *common.FreesrData) {
for _, rule := range r.GlobalPassives {
var enabled *bool
switch rule.Setting {
case "enable_sw_global":
enabled = player.EnableSWGlobal
case "enable_castorice_global":
enabled = player.EnableCastoriceGlobal
case "enable_aha_global":
enabled = player.EnableAhaGlobal
}
if enabled != nil && !*enabled {
continue
}
owner := rule.OwnerSlot
// A shorter lineup must not refer to an absent actor.
present := false
for _, avatar := range battle.BattleAvatarList {
if avatar.Index == owner {
present = true
}
}
if !present {
owner = battle.BattleAvatarList[0].Index
}
battle.BuffList = append(battle.BuffList, battleBuff(rule.BuffID, owner, nil))
}
}
func (r *battleRules) addTargets(battle *goproto.SceneBattleInfo, kind common.BattleType) {
for _, rule := range r.BattleTargets {
if !strings.EqualFold(rule.BattleType, string(kind)) {
continue
}
for key, targets := range rule.Targets {
list := &goproto.BattleTargetList{}
for _, target := range targets {
list.BattleTargetList = append(list.BattleTargetList, &goproto.BattleTarget{
Id: target.ID, Progress: target.Progress, TotalProgress: target.TotalProgress})
}
battle.BattleTargetInfo[key] = list
}
}
}