228 lines
6.9 KiB
Go
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
|
|
})
|
|
}
|