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 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 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 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)); } }