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.
173 lines
5.8 KiB
C#
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));
|
|
}
|
|
}
|