Files
GaCy-GO/internal/gameserver/currency_wars_equipment.go
2026-10-28 18:54:43 +07:00

228 lines
6.9 KiB
Go

package gameserver
import (
"fmt"
"slices"
"gac-hkrpg-go/internal/common"
pb "gac-hkrpg-go/protocol/proto"
)
// These discriminators are GridFightEquipmentType / GridFightEquipDressType
// definitions, mirrored by the reference equipment rule switch. Item IDs,
// limits, role restrictions and properties always come from Client resources.
const (
cwEquipNormal uint32 = iota
cwEquipImplant
)
const (
cwDressNotUnique uint32 = iota
cwDressUnique
cwDressEmpty
cwDressLeader
cwDressTraitOnly
cwDressUniqueExclusiveTrait
cwDressRoleOnly
)
func cwEquipmentBag(run *pb.MPIMAEMCEFH) *pb.CONONEALNOF {
for _, component := range run.NAGDJGMMCBE {
if bag := component.GetGCKLIONKMJC(); bag != nil {
return bag
}
}
return nil
}
func cwCanDressEquipment(c *common.CurrencyWarsCatalog, equipment map[uint32]common.CurrencyWarsEquipment, bag *pb.CONONEALNOF, role *pb.CDEAABODKCP, gear *pb.IJDLENJJBHJ) error {
row, ok := equipment[gear.GridFightEquipmentId]
if !ok {
return fmt.Errorf("equipment configuration is not captured")
}
config, err := c.CapturedRole(role.Id)
if err != nil {
return err
}
limitName := "GridFight_AvatarDressEquipNum"
if row.EquipType == cwEquipImplant {
limitName = "GridFight_AvatarDressImplantsNum"
} else if row.EquipType != cwEquipNormal {
return fmt.Errorf("equipment type is unsupported")
}
limit, err := c.IntegerRule(limitName)
if err != nil || limit < 0 {
return fmt.Errorf("equipment slot limit is missing")
}
var sameType int64
unique := true
for _, uid := range role.PNJIPHJJONG {
index := slices.IndexFunc(bag.GridFightEquipmentList, func(x *pb.IJDLENJJBHJ) bool { return x != nil && x.UniqueId == uid })
if index < 0 {
return fmt.Errorf("equipped item is absent from inventory")
}
other, exists := equipment[bag.GridFightEquipmentList[index].GridFightEquipmentId]
if !exists {
return fmt.Errorf("equipped item configuration is missing")
}
if other.EquipType == row.EquipType {
sameType++
}
if other.ItemID == row.ItemID {
unique = false
}
if row.EquipType == cwEquipNormal && other.EquipType == cwEquipNormal && other.DressRule == cwDressEmpty {
return fmt.Errorf("existing equipment requires all other slots empty")
}
}
if row.NeedEquipSlot && sameType >= limit {
return fmt.Errorf("equipment slots are full")
}
allowed := false
switch row.DressRule {
case cwDressNotUnique:
allowed = true
case cwDressUnique:
allowed = unique
case cwDressEmpty:
allowed = sameType == 0
case cwDressLeader:
allowed = role.Pos == 1
case cwDressTraitOnly:
for _, trait := range row.DressRuleParamList {
if slices.Contains(config.Config.TraitList, trait) {
allowed = true
}
}
case cwDressUniqueExclusiveTrait:
allowed = unique
for _, trait := range row.DressRuleParamList {
if slices.Contains(config.Config.TraitList, trait) {
allowed = false
}
}
case cwDressRoleOnly:
allowed = slices.Contains(row.DressRuleParamList, role.Id)
}
if !allowed {
return fmt.Errorf("equipment dress restriction does not match target")
}
return nil
}
func init() {
cwHandle("GridFightEquipDress", func(s *Session, req *pb.GridFightEquipDressCsReq, rsp *pb.GridFightEquipDressScRsp) error {
rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_PARAM_NOT_MATCH)
catalog, data, err := cwPreparingData(s)
if err != nil {
return nil
}
run := data.DHFOFLFCLAH
_, team, _ := cwComponents(run)
bag := cwEquipmentBag(run)
if team == nil || bag == nil || req.PEBCBLBLMPH == 0 || req.MDNAGANGNKK == 0 {
return nil
}
if _, ok := pb.DMGOKGLBNJJ_name[int32(req.CPFCNOAJCGK)]; !ok {
return nil
}
// All entities have run-global unique IDs. CPFCNOAJCGK is the operation
// source (None/Manual/Recommend), not the target's entity kind.
var role *pb.CDEAABODKCP
for _, x := range team.GridGameRoleList {
if x.UniqueId == req.MDNAGANGNKK {
role = x
break
}
}
if role == nil {
return nil
}
var gear *pb.IJDLENJJBHJ
for _, x := range bag.GridFightEquipmentList {
if x.UniqueId == req.PEBCBLBLMPH {
gear = x
break
}
}
if gear == nil || slices.Contains(role.PNJIPHJJONG, gear.UniqueId) {
return nil
}
equipment, err := common.LoadCurrencyWarsEquipment(catalog)
if err != nil {
PacketLog("CW_EQUIP_DRESS rejected: " + err.Error())
return nil
}
// Dressing a component onto an existing component crafts the pair on
// that role. Resolve the pair through the captured current recipe table.
recipes, err := common.LoadCurrencyWarsCrafts(catalog)
if err != nil {
PacketLog("CW_EQUIP_DRESS rejected: " + err.Error())
return nil
}
for _, equippedUID := range role.PNJIPHJJONG {
var other *pb.IJDLENJJBHJ
for _, x := range bag.GridFightEquipmentList {
if x.UniqueId == equippedUID {
other = x
break
}
}
if other == nil {
return nil
}
recipe, recipeErr := cwCraftRecipe(recipes, 0, []uint32{gear.GridFightEquipmentId, other.GridFightEquipmentId})
if recipeErr != nil {
continue
}
// Auto-craft cannot take a component from another wearer.
for _, x := range team.GridGameRoleList {
if x.UniqueId != role.UniqueId && slices.Contains(x.PNJIPHJJONG, gear.UniqueId) {
return nil
}
}
notify, craftErr := cwCraftEquipmentOnRole(catalog, run, []uint32{gear.UniqueId, other.UniqueId}, recipe.CraftEquipID, role)
if craftErr != nil {
PacketLog("CW_EQUIP_DRESS auto-craft rejected: " + craftErr.Error())
return nil
}
if err = s.saveCurrencyWarsData(data, catalog); err != nil {
return err
}
if err = cwSend(s, notify); err != nil {
return err
}
rsp.Retcode = 0
PacketLog(fmt.Sprintf("CW_EQUIP_DRESS auto-crafted role=%d target=%d revision=%d", role.UniqueId, recipe.CraftEquipID, s.currencyWarsRevision))
return nil
}
if err = cwCanDressEquipment(catalog, equipment, bag, role, gear); err != nil {
PacketLog("CW_EQUIP_DRESS rejected: " + err.Error())
return nil
}
var changed []*pb.KFNCAIHIHCM
for _, other := range team.GridGameRoleList {
if slices.Contains(other.PNJIPHJJONG, gear.UniqueId) {
other.PNJIPHJJONG = slices.DeleteFunc(other.PNJIPHJJONG, func(id uint32) bool { return id == gear.UniqueId })
changed = append(changed, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_PIDDDHKPDDM{PIDDDHKPDDM: other}})
}
}
// Moving from NPC equipment needs the verified NPC-update sync arm.
for _, npc := range team.GridGameNpcList {
if slices.Contains(npc.PNJIPHJJONG, gear.UniqueId) {
return nil
}
}
role.PNJIPHJJONG = append(role.PNJIPHJJONG, gear.UniqueId)
changed = append(changed, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_PIDDDHKPDDM{PIDDDHKPDDM: role}})
if err = s.saveCurrencyWarsData(data, catalog); err != nil {
return err
}
if err = cwSend(s, cwNotify(changed...)); err != nil {
return err
}
rsp.Retcode = 0
PacketLog(fmt.Sprintf("CW_EQUIP_DRESS saved role=%d equipment=%d revision=%d", role.UniqueId, gear.UniqueId, s.currencyWarsRevision))
return nil
})
}