191 lines
5.6 KiB
Go
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
|
|
}
|
|
}
|
|
}
|