diff --git a/logic/controller/fight_boss野怪和地图怪.go b/logic/controller/fight_boss野怪和地图怪.go index 3ec0f067e..8e9a41b3a 100644 --- a/logic/controller/fight_boss野怪和地图怪.go +++ b/logic/controller/fight_boss野怪和地图怪.go @@ -41,7 +41,6 @@ func (Controller) PlayerFightBoss(req *ChallengeBossInboundInfo, p *player.Playe if err != 0 { return nil, err } - leadMonster := &monsterInfo.PetList[0] p.Fightinfo.Status = fightinfo.BattleMode.FIGHT_WITH_NPC p.Fightinfo.Mode = resolveMapNodeFightMode(mapNode) @@ -53,7 +52,12 @@ func (Controller) PlayerFightBoss(req *ChallengeBossInboundInfo, p *player.Playe var fightC *fight.FightC fightC, err = startMapBossFight(mapNode, p, ai, func(foi model.FightOverInfo) { - handleMapBossFightRewards(p, fightC, foi, mapNode, bossConfigs[0], leadMonster) + if mapNode.WinBonusID == 0 { + return + } + if shouldGrantBossWinBonus(fightC, p.Info.UserID, bossConfigs[0], foi) { + p.SptCompletedTask(mapNode.WinBonusID, 1) + } }) if err != 0 { return nil, err @@ -230,95 +234,6 @@ func shouldGrantBossWinBonus(fightC *fight.FightC, playerID uint32, bossConfig c return true } -func handleMapBossFightRewards( - p *player.Player, - fightC *fight.FightC, - foi model.FightOverInfo, - mapNode *configmodel.MapNode, - bossConfig configmodel.BossConfig, - leadMonster *model.PetInfo, -) { - rewards := grantMonsterFightRewards(p, foi, leadMonster) - if mapNode != nil && mapNode.WinBonusID != 0 && shouldGrantBossWinBonus(fightC, p.Info.UserID, bossConfig, foi) { - appendBossTaskReward(p, mapNode.WinBonusID, 1, rewards) - } - if rewards != nil && rewards.HasReward() { - p.SendPackCmd(8004, rewards) - } -} - -func grantMonsterFightRewards(p *player.Player, foi model.FightOverInfo, monster *model.PetInfo) *fightinfo.S2C_GET_BOSS_MONSTER { - rewards := &fightinfo.S2C_GET_BOSS_MONSTER{} - if p == nil || monster == nil || foi.Reason != 0 || foi.WinnerId != p.Info.UserID || !p.CanGet() { - return rewards - } - - petCfg, ok := xmlres.PetMAP[int(monster.ID)] - if !ok { - return rewards - } - - exp := uint32(petCfg.YieldingExp) * monster.Level / 7 - addlevel, poolevel := p.CanGetExp() - addexp := gconv.Float32(addlevel * gconv.Float32(exp)) - poolexp := gconv.Float32(poolevel) * gconv.Float32(exp) - - p.ItemAdd(3, int64(poolexp+addexp)) - rewards.AddItem(rewardItemExpPool, uint32(poolexp)) - p.AddPetExp(foi.Winpet, int64(addexp)) - - if p.CanGetItem() { - itemID := p.GetSpace().GetDrop() - if itemID != 0 { - count := uint32(grand.N(1, 2)) - if p.ItemAdd(itemID, int64(count)) { - rewards.AddItem(uint32(itemID), count) - } - } - } - - petType := int64(petCfg.Type) - if monster.IsShiny() && p.CanGetXUAN() && petType < 16 { - xuanID := uint32(400686 + petType) - count := uint32(grand.N(1, 2)) - if p.ItemAdd(int64(xuanID), int64(count)) { - rewards.AddItem(xuanID, count) - } - } - - if foi.Winpet != nil { - foi.Winpet.AddEV(petCfg.YieldingEVValues) - } - return rewards -} - -func appendBossTaskReward(p *player.Player, taskID int, outState int, rewards *fightinfo.S2C_GET_BOSS_MONSTER) { - if p == nil || rewards == nil || !p.IsLogin || taskID <= 0 { - return - } - if p.Info.GetTask(taskID) == model.Completed { - return - } - - granted, err := p.ApplyTaskCompletion(uint32(taskID), outState, nil) - if err != 0 { - return - } - - p.Info.SetTask(taskID, model.Completed) - rewards.BonusID = uint32(taskID) - if granted == nil { - return - } - if granted.Pet != nil { - rewards.PetID = granted.Pet.ID - rewards.CaptureTm = granted.Pet.CatchTime - } - for _, item := range granted.Items { - rewards.AddItemInfo(item) - } -} - func buildNpcMonsterInfo(refPet player.OgrePetInfo, mapID uint32) (*model.PetInfo, *model.PlayerInfo, errorcode.ErrorCode) { if refPet.ID == 0 { return nil, nil, errorcode.ErrorCodes.ErrPokemonNotExists @@ -354,8 +269,46 @@ func buildNpcMonsterInfo(refPet player.OgrePetInfo, mapID uint32) (*model.PetInf } func handleNpcFightRewards(p *player.Player, foi model.FightOverInfo, monster *model.PetInfo) { - rewards := grantMonsterFightRewards(p, foi, monster) + if foi.Reason != 0 || foi.WinnerId != p.Info.UserID || !p.CanGet() { + return + } + + petCfg, ok := xmlres.PetMAP[int(monster.ID)] + if !ok { + return + } + + exp := uint32(petCfg.YieldingExp) * monster.Level / 7 + addlevel, poolevel := p.CanGetExp() + addexp := gconv.Float32(addlevel * gconv.Float32(exp)) + poolexp := gconv.Float32(poolevel) * gconv.Float32(exp) + rewards := &fightinfo.S2C_GET_BOSS_MONSTER{} + + p.ItemAdd(3, int64(poolexp+addexp)) + rewards.AddItem(rewardItemExpPool, uint32(poolexp)) + p.AddPetExp(foi.Winpet, int64(addexp)) + + if p.CanGetItem() { + itemID := p.GetSpace().GetDrop() + if itemID != 0 { + count := uint32(grand.N(1, 2)) + if p.ItemAdd(itemID, int64(count)) { + rewards.AddItem(uint32(itemID), count) + } + } + } + + petType := int64(petCfg.Type) + if monster.IsShiny() && p.CanGetXUAN() && petType < 16 { + xuanID := uint32(400686 + petType) + count := uint32(grand.N(1, 2)) + if p.ItemAdd(int64(xuanID), int64(count)) { + rewards.AddItem(xuanID, count) + } + } + if rewards.HasReward() { p.SendPackCmd(8004, rewards) } + foi.Winpet.AddEV(petCfg.YieldingEVValues) } diff --git a/logic/controller/item_use.go b/logic/controller/item_use.go index 874415e9b..cfc781d8b 100644 --- a/logic/controller/item_use.go +++ b/logic/controller/item_use.go @@ -15,6 +15,8 @@ import ( const ( // ItemDefaultLeftTime 道具默认剩余时间(毫秒) ItemDefaultLeftTime = 360000 + // UniversalNatureItemID 全能性格转化剂Ω + UniversalNatureItemID uint32 = 300136 ) // GetUserItemList 获取用户道具列表 @@ -187,6 +189,14 @@ func (h Controller) ResetNature(data *C2S_PET_RESET_NATURE, c *player.Player) (r return nil, errorcode.ErrorCodes.Err10401 } + if data.ItemId != UniversalNatureItemID { + return nil, errorcode.ErrorCodes.ErrItemUnusable + } + + if _, ok := xmlres.NatureRootMap[int(data.Nature)]; !ok { + return nil, errorcode.ErrorCodes.ErrItemUnusable + } + if c.Service.Item.CheakItem(data.ItemId) <= 0 { return nil, errorcode.ErrorCodes.ErrInsufficientItems } diff --git a/logic/service/fight/action.go b/logic/service/fight/action.go index 6b3e0b07d..8a06dc769 100644 --- a/logic/service/fight/action.go +++ b/logic/service/fight/action.go @@ -175,7 +175,7 @@ func (f *FightC) Over(c common.PlayerI, res model.EnumBattleOverReason) { // } f.overl.Do(func() { - f.Reason = res + f.Reason = normalizeFightOverReason(res) if f.GetInputByPlayer(c, true) != nil { f.WinnerId = f.GetInputByPlayer(c, true).UserID } diff --git a/logic/service/fight/fight_over_payload.go b/logic/service/fight/fight_over_payload.go index 0e773e707..995393d09 100644 --- a/logic/service/fight/fight_over_payload.go +++ b/logic/service/fight/fight_over_payload.go @@ -7,12 +7,21 @@ import "blazing/modules/player/model" // 0=normal end 1=player lost/offline 2=overtime 3=draw 4=system error 5=npc escape. func buildFightOverPayload(over model.FightOverInfo) *model.FightOverInfo { payload := over - payload.Reason = mapFightOverReasonFor2506(over.Reason) + payload.Reason = model.EnumBattleOverReason(mapUnifiedFightOverReason(over.Reason)) return &payload } -func mapFightOverReasonFor2506(reason model.EnumBattleOverReason) model.EnumBattleOverReason { - switch reason { +func normalizeFightOverReason(reason model.EnumBattleOverReason) model.EnumBattleOverReason { + if reason == model.BattleOverReason.DefaultEnd { + return 0 + } + return reason +} + +func mapUnifiedFightOverReason(reason model.EnumBattleOverReason) uint32 { + switch normalizeFightOverReason(reason) { + case 0, model.BattleOverReason.Cacthok: + return 0 case model.BattleOverReason.PlayerOffline: return 1 case model.BattleOverReason.PlayerOVerTime: @@ -20,12 +29,12 @@ func mapFightOverReasonFor2506(reason model.EnumBattleOverReason) model.EnumBatt case model.BattleOverReason.NOTwind: return 3 case model.BattleOverReason.PlayerEscape: - // Player-initiated escape is handled by 2410 on the flash side; 2506 should - // still land in a non-error bucket instead of "system error". - return 1 - case model.BattleOverReason.Cacthok, model.BattleOverReason.DefaultEnd: - return 0 + return 5 default: return 4 } } + +func mapFightOverReasonFor2506(reason model.EnumBattleOverReason) model.EnumBattleOverReason { + return model.EnumBattleOverReason(mapUnifiedFightOverReason(reason)) +} diff --git a/logic/service/fight/fightc.go b/logic/service/fight/fightc.go index a640969e4..b866a4991 100644 --- a/logic/service/fight/fightc.go +++ b/logic/service/fight/fightc.go @@ -522,9 +522,9 @@ func (f *FightC) TURNOVER(cur *input.Input) { if f.IsWin(f.GetInputByPlayer(cur.Player, true)) { //然后检查是否战斗结束 f.FightOverInfo.WinnerId = f.GetInputByPlayer(cur.Player, true).UserID - f.FightOverInfo.Reason = model.BattleOverReason.DefaultEnd + f.FightOverInfo.Reason = normalizeFightOverReason(model.BattleOverReason.DefaultEnd) f.WinnerId = f.FightOverInfo.WinnerId - f.Reason = model.BattleOverReason.DefaultEnd + f.Reason = f.FightOverInfo.Reason f.closefight = true // break diff --git a/logic/service/fight/group_legacy.go b/logic/service/fight/group_legacy.go index 334bbca80..6a1f26d59 100644 --- a/logic/service/fight/group_legacy.go +++ b/logic/service/fight/group_legacy.go @@ -426,38 +426,15 @@ func (f *FightC) buildLegacyGroupOverInfo(over *model.FightOverInfo) *legacyGrou } func mapLegacyGroupFightOverReason(reason model.EnumBattleOverReason) uint32 { - switch reason { - case model.BattleOverReason.PlayerOffline: - return 2 - case model.BattleOverReason.PlayerOVerTime: - return 3 - case model.BattleOverReason.NOTwind: - return 4 - case model.BattleOverReason.DefaultEnd: - return 1 - case model.BattleOverReason.PlayerEscape: - return 6 - default: - return 5 - } + return mapUnifiedFightOverReason(reason) } func resolveLegacyGroupFightOverReason(over *model.FightOverInfo) uint32 { if over == nil { - return 5 - } - switch over.Reason { - case model.BattleOverReason.PlayerOffline: - return 2 - case model.BattleOverReason.PlayerOVerTime: - return 3 - case model.BattleOverReason.PlayerEscape: - return 6 - case model.BattleOverReason.NOTwind: - return 4 + return mapUnifiedFightOverReason(0) } if over.WinnerId != 0 { - return 1 + return mapUnifiedFightOverReason(0) } return mapLegacyGroupFightOverReason(over.Reason) } diff --git a/logic/service/fight/loop.go b/logic/service/fight/loop.go index 89a390dc3..30a2e1918 100644 --- a/logic/service/fight/loop.go +++ b/logic/service/fight/loop.go @@ -76,7 +76,7 @@ func (f *FightC) battleLoop() { if player := f.primaryOppPlayer(); player != nil { f.WinnerId = player.GetInfo().UserID } - f.Reason = model.BattleOverReason.DefaultEnd + f.Reason = normalizeFightOverReason(model.BattleOverReason.DefaultEnd) f.FightOverInfo.WinnerId = f.WinnerId f.FightOverInfo.Reason = f.Reason f.closefight = true @@ -86,7 +86,7 @@ func (f *FightC) battleLoop() { if player := f.primaryOurPlayer(); player != nil { f.WinnerId = player.GetInfo().UserID } - f.Reason = model.BattleOverReason.DefaultEnd + f.Reason = normalizeFightOverReason(model.BattleOverReason.DefaultEnd) f.FightOverInfo.WinnerId = f.WinnerId f.FightOverInfo.Reason = f.Reason f.closefight = true diff --git a/logic/service/player/pet.go b/logic/service/player/pet.go index fcdea4c56..de151c105 100644 --- a/logic/service/player/pet.go +++ b/logic/service/player/pet.go @@ -27,33 +27,24 @@ func (p *Player) AddPetExp(petInfo *model.PetInfo, addExp int64) { if petInfo == nil || addExp <= 0 { return } - if petInfo.Level >= 100 { - petInfo.Level = 100 - petInfo.Exp = 0 + if petInfo.Level > 100 { + currentHP := petInfo.Hp petInfo.Update(false) petInfo.CalculatePetPane(100) - if petInfo.Hp > petInfo.MaxHp { - petInfo.Hp = petInfo.MaxHp - } - return + petInfo.Hp = utils.Min(currentHP, petInfo.MaxHp) } addExp = utils.Min(addExp, p.Info.ExpPool) originalLevel := petInfo.Level exp := int64(petInfo.Exp) + addExp p.Info.ExpPool -= addExp //减去已使用的经验 gainedExp := exp //已获得的经验 - for petInfo.Level < 100 && exp >= int64(petInfo.NextLvExp) { + for exp >= int64(petInfo.NextLvExp) { petInfo.Level++ exp -= int64(petInfo.LvExp) petInfo.Update(true) } - if petInfo.Level >= 100 { - p.Info.ExpPool += exp // 超出100级上限的经验退回经验池 - gainedExp -= exp - exp = 0 - } petInfo.Exp = (exp) // 重新计算面板 if originalLevel != petInfo.Level { diff --git a/logic/service/player/pet_test.go b/logic/service/player/pet_test.go index 00ccfe199..c054a6fcb 100644 --- a/logic/service/player/pet_test.go +++ b/logic/service/player/pet_test.go @@ -17,12 +17,16 @@ func firstPetIDForTest(t *testing.T) int { return 0 } -func TestAddPetExpStopsAtLevel100(t *testing.T) { +func TestAddPetExpAllowsLevelBeyond100WhilePanelStaysCapped(t *testing.T) { petID := firstPetIDForTest(t) - petInfo := playermodel.GenPetInfo(petID, 31, 0, 0, 99, nil, 0) + petInfo := playermodel.GenPetInfo(petID, 31, 0, 0, 100, nil, 0) + expectedPanel := playermodel.GenPetInfo(petID, 31, 0, 0, 100, nil, 0) if petInfo == nil { t.Fatalf("failed to generate test pet") } + if expectedPanel == nil { + t.Fatalf("failed to generate expected test pet") + } player := &Player{ baseplayer: baseplayer{ @@ -34,21 +38,29 @@ func TestAddPetExpStopsAtLevel100(t *testing.T) { player.AddPetExp(petInfo, petInfo.NextLvExp+10_000) - if petInfo.Level != 100 { - t.Fatalf("expected pet level to stop at 100, got %d", petInfo.Level) + if petInfo.Level <= 100 { + t.Fatalf("expected pet level to continue beyond 100, got %d", petInfo.Level) } - if petInfo.Exp != 0 { - t.Fatalf("expected pet exp to reset at level cap, got %d", petInfo.Exp) + if petInfo.MaxHp != expectedPanel.MaxHp { + t.Fatalf("expected max hp to stay capped at 100-level panel, got %d want %d", petInfo.MaxHp, expectedPanel.MaxHp) + } + if petInfo.Prop != expectedPanel.Prop { + t.Fatalf("expected props to stay capped at 100-level panel, got %+v want %+v", petInfo.Prop, expectedPanel.Prop) } } -func TestAddPetExpDoesNotConsumePoolAboveLevel100(t *testing.T) { +func TestAddPetExpRecalculatesPanelForLevelAbove100(t *testing.T) { petID := firstPetIDForTest(t) petInfo := playermodel.GenPetInfo(petID, 31, 0, 0, 100, nil, 0) + expectedPanel := playermodel.GenPetInfo(petID, 31, 0, 0, 100, nil, 0) if petInfo == nil { t.Fatalf("failed to generate test pet") } + if expectedPanel == nil { + t.Fatalf("failed to generate expected test pet") + } petInfo.Level = 101 + petInfo.Exp = 7 petInfo.MaxHp = 1 petInfo.Hp = 999999 @@ -62,19 +74,16 @@ func TestAddPetExpDoesNotConsumePoolAboveLevel100(t *testing.T) { player.AddPetExp(petInfo, 12_345) - if petInfo.Level != 100 { - t.Fatalf("expected level to be normalized to 100, got %d", petInfo.Level) + if petInfo.Level < 101 { + t.Fatalf("expected level above 100 to be preserved, got %d", petInfo.Level) } - if player.Info.ExpPool != 50_000 { - t.Fatalf("expected exp pool to remain unchanged, got %d", player.Info.ExpPool) - } - if petInfo.Exp != 0 { - t.Fatalf("expected exp to reset after normalization, got %d", petInfo.Exp) - } - if petInfo.MaxHp <= 1 { - t.Fatalf("expected pet panel to be recalculated, got max hp %d", petInfo.MaxHp) + if petInfo.MaxHp != expectedPanel.MaxHp { + t.Fatalf("expected max hp to be recalculated using level 100 cap, got %d want %d", petInfo.MaxHp, expectedPanel.MaxHp) } if petInfo.Hp != petInfo.MaxHp { t.Fatalf("expected hp to be clamped to recalculated max hp, got hp=%d maxHp=%d", petInfo.Hp, petInfo.MaxHp) } + if player.Info.ExpPool != 50_000-12_345 { + t.Fatalf("expected exp pool to be consumed normally, got %d", player.Info.ExpPool) + } }