ModernUO/Projects/UOContent/Engines/ExileHunterBestiary/Hooks/HunterDeathHook.cs

245 lines
No EOL
5.8 KiB
C#

using System;
using System.Collections.Generic;
using Server.Custom.ExileHunterBestiary.Items;
using Server.Items;
using Server.Mobiles;
namespace Server.Custom.ExileHunterBestiary.Hooks;
public static class HunterDeathHook
{
private static readonly HashSet<Serial> _processedDeaths = [];
public static string OnCreatureDeath(BaseCreature creature, Mobile killer)
{
if (!HunterBestiaryEngine.Enabled)
{
return "system disabled";
}
if (creature == null)
{
return "creature is null";
}
if (creature.Summoned || creature.IsBonded)
{
return "creature filtered (summoned/bonded)";
}
var entry = BestiaryRegistry.FindEntry(creature.GetType());
if (entry == null)
{
return $"NOT REGISTERED: {creature.GetType().FullName}";
}
if (killer == null || killer.Deleted)
{
killer = creature.LastKiller;
}
if (killer == null || killer.Deleted)
{
killer = HunterCombatTracker.GetLastHitter(creature);
}
if (killer == null || killer.Deleted)
{
return $"{entry.Id}: no killer resolved";
}
var master = killer;
if (killer is BaseCreature killerCreature)
{
master = killerCreature.GetMaster() ?? killer;
}
if (master is not PlayerMobile player)
{
HunterCombatTracker.Forget(creature.Serial);
return $"{entry.Id}: killer is not a player";
}
if (!_processedDeaths.Add(creature.Serial))
{
return $"{entry.Id}: already processed";
}
var location = creature.Location;
var map = creature.Map;
if (map == null || map == Map.Internal)
{
HunterCombatTracker.TryGetDeathLocation(
creature,
out location,
out map
);
}
HunterCombatTracker.Forget(creature.Serial);
var profile = HunterBestiaryEngine.GetProfile(player);
if (profile == null)
{
_processedDeaths.Remove(creature.Serial);
return $"{entry.Id}: profile is null";
}
if (profile.HasLearned(entry.Id))
{
return $"{entry.Id}: already learned by this player";
}
var chance = HunterBestiaryEngine.GlobalDropChance >= 0
? HunterBestiaryEngine.GlobalDropChance
: entry.DropChance;
chance = Math.Clamp(chance, 0.0, 1.0);
var pityDrop = profile.RegisterKill(
entry.Id,
HunterBestiaryEngine.PityThreshold
);
if (!pityDrop && Utility.RandomDouble() >= chance)
{
return $"{entry.Id}: rolled above drop chance ({chance:P0})";
}
if (map == null || map == Map.Internal)
{
_processedDeaths.Remove(creature.Serial);
return $"{entry.Id}: death location unavailable";
}
var treatise = new HunterTreatise(entry.Id);
var corpse = FindCorpseAt(location, map, creature);
if (corpse is { Deleted: false })
{
corpse.DropItem(treatise);
return null;
}
DropIntoCorpseLater(
treatise,
entry.Id,
creature.Serial,
location,
map,
0
);
return null;
}
private static void DropIntoCorpseLater(
HunterTreatise treatise,
string creatureId,
Serial creatureSerial,
Point3D location,
Map map,
int attempt
)
{
Timer.DelayCall(
TimeSpan.FromMilliseconds(100),
() =>
{
if (treatise.Deleted)
{
return;
}
var corpse = FindCorpseAt(location, map, null);
if (corpse is { Deleted: false })
{
corpse.DropItem(treatise);
return;
}
if (attempt < 10)
{
DropIntoCorpseLater(
treatise,
creatureId,
creatureSerial,
location,
map,
attempt + 1
);
return;
}
treatise.Delete();
_processedDeaths.Remove(creatureSerial);
}
);
}
private static Corpse FindCorpseAt(
Point3D location,
Map map,
BaseCreature owner
)
{
if (map == null || map == Map.Internal)
{
return null;
}
Corpse nearest = null;
var nearestDistance = int.MaxValue;
foreach (var item in World.Items.Values)
{
if (item is not Corpse corpse ||
corpse.Deleted ||
corpse.Map != map)
{
continue;
}
var corpseLocation = corpse.Location;
var distance = Math.Max(
Math.Abs(corpseLocation.X - location.X),
Math.Abs(corpseLocation.Y - location.Y)
);
if (distance > 3)
{
continue;
}
if (owner != null && corpse.Owner == owner)
{
return corpse;
}
if (distance < nearestDistance)
{
nearest = corpse;
nearestDistance = distance;
}
}
return nearest;
}
public static void ClearProcessedDeath(Serial serial)
{
_processedDeaths.Remove(serial);
}
public static void ClearProcessedDeaths()
{
_processedDeaths.Clear();
}
}