yuanshen/Yuanshen.Gameserver/Game/CombatManager.cs
wisedev a4b3b669a8 Use the named retcode and reason constants
Half the handlers were still answering with a bare 0 or -1 even though the whole
retcode table is generated. Values on the wire are unchanged, they just read
properly now.

Same for the item hint reasons. ActionReasonType is a generated enum and we were
redeclaring six of its members as private consts in four different files.
2026-09-04 11:21:12 +03:00

173 lines
5.8 KiB
C#

using Microsoft.Extensions.Logging;
using Yuanshen.Gameserver.Database;
using Yuanshen.Gameserver.Network.Session;
using Yuanshen.Protocol;
namespace Yuanshen.Gameserver.Game;
internal sealed class CombatManager
{
private const uint FightPropCurHp = 1010;
private const uint FightPropMaxHp = 2000;
private const uint MoraItemId = 202;
private const uint PrimogemItemId = 201;
private static readonly Random Random = new();
private readonly NetSession _session;
private readonly Player _player;
private readonly PlayerPersistence _persistence;
private readonly DropTable _drops;
private readonly ILogger _logger;
public CombatManager(NetSession session, Player player, PlayerPersistence persistence, DropTable drops,
ILogger<CombatManager> logger)
{
_session = session;
_player = player;
_persistence = persistence;
_drops = drops;
_logger = logger;
}
public async ValueTask HandleAttackAsync(AttackResult attack)
{
GameMonster? monster = _player.Monsters.FirstOrDefault(m => m.EntityId == attack.DefenseId);
if (monster is null)
{
_logger.LogDebug("hit on non-monster entity {id}", attack.DefenseId);
return;
}
if (!monster.IsAlive || attack.Damage <= 0f)
{
_logger.LogDebug("hit ignored on {id}: alive={alive} damage={dmg}", attack.DefenseId, monster.IsAlive, attack.Damage);
return;
}
_logger.LogInformation("monster {mid} takes {dmg} damage ({hp} -> {left})",
monster.MonsterId, attack.Damage, monster.CurrentHp, monster.CurrentHp - attack.Damage);
float remaining = monster.ApplyDamage(attack.Damage);
await _session.NotifyAsync(CmdType.EntityFightPropUpdateNotify, new EntityFightPropUpdateNotify
{
EntityId = monster.EntityId,
FightPropMap =
{
{ FightPropCurHp, remaining }
}
});
if (remaining > 0f)
return;
_logger.LogInformation("Monster {monsterId} killed by entity {attacker}", monster.MonsterId, attack.AttackerId);
if (monster.NodeId != 0)
_player.MarkNodeKilled(monster.NodeId);
await GrantDropsAsync(monster);
if (monster.NodeId != 0)
await _persistence.SaveAsync();
await _session.NotifyAsync(CmdType.LifeStateChangeNotify, new LifeStateChangeNotify
{
EntityId = monster.EntityId,
LifeState = 2,
SourceEntityId = attack.AttackerId,
DieType = PlayerDieType.PlayerDieKillByMonster
});
await _session.NotifyAsync(CmdType.SceneEntityDisappearNotify, new SceneEntityDisappearNotify
{
DisappearType = VisionType.VisionDie,
EntityList = { monster.EntityId }
});
_player.Monsters.Remove(monster);
}
private async ValueTask GrantDropsAsync(GameMonster monster)
{
uint dropId = _drops.DropIdFor(monster.MonsterId, monster.Level);
if (dropId == 0)
return;
Dictionary<uint, uint> rolled = _drops.Roll(dropId, Random);
if (rolled.Count == 0)
return;
StoreItemChangeNotify store = new() { StoreType = StoreType.StorePack };
ItemAddHintNotify hint = new()
{
Reason = monster.IsWildlife ? (uint)ActionReasonType.ActionReasonMonsterEnvAnimal : (uint)ActionReasonType.ActionReasonMonsterDie,
Position = monster.Pos,
IsPositionValid = true
};
List<string> granted = new();
bool currencyChanged = false;
foreach ((uint itemId, uint count) in rolled)
{
switch (_drops.TypeOf(itemId))
{
case "weapon":
for (uint made = 0; made < Math.Min(count, 10u); made++)
store.ItemList.Add(_player.CreateWeapon(itemId).AsItem());
break;
case "virtual" when itemId == MoraItemId:
_player.Scoin += count;
currencyChanged = true;
break;
case "virtual" when itemId == PrimogemItemId:
_player.Hcoin += count;
currencyChanged = true;
break;
case "virtual":
continue;
case "reliquary":
for (uint made = 0; made < Math.Min(count, 5u); made++)
{
GameReliquary? reliquary = _player.CreateReliquary(itemId, Random);
if (reliquary is not null)
store.ItemList.Add(reliquary.AsItem());
}
break;
default:
_player.GiveMaterial(itemId, count);
store.ItemList.Add(new Item
{
ItemId = itemId,
Guid = _player.MaterialGuid(itemId),
Material = new Material { Count = _player.Materials.GetValueOrDefault(itemId) }
});
break;
}
hint.ItemList.Add(new ItemHint { ItemId = itemId, Count = count, IsNew = false });
granted.Add($"{itemId}x{count}");
}
if (hint.ItemList.Count == 0)
return;
if (store.ItemList.Count != 0)
await _session.NotifyAsync(CmdType.StoreItemChangeNotify, store);
if (currencyChanged)
await _session.NotifyAsync(CmdType.PlayerPropNotify, _player.BuildCurrencyProps());
await _session.NotifyAsync(CmdType.ItemAddHintNotify, hint);
_logger.LogInformation("Monster {monsterId} lvl {level} dropped {items}",
monster.MonsterId, monster.Level, string.Join(", ", granted));
}
}