fix: Overhauls murder system (#1419)
## MAJOR CHANGE (API BREAKING) Added a player murder system to facilitate reporting murders. This should make it easier to extend to create a bounty system or other related game content. Player murders will be saved in a folder called _PlayerMurders_. ### Motivation The motivation was two-fold, performance, and bug fixes. First, murders are one of two systems that do a pre-world-save check on _every mobile in the game_ to decay kills and set their expiring murders. This is taxing since it freezes the world and makes world saves take longer. Every mobile has ShortTermMurders even though it is a player concept. And next, 90%+ of players are not murderers but had an ever increasing MurderElapse time that was being tracked against GameTime. These properties were also serialized unnecessarily for all mobs. Second, when I tried to optimize/refactor the code, it was obvious that the system has bugs. ### Major API Changes - [X] Created a player murder system and moved `ShortTermMurders`, `ShortTermElapse`, and `LongTermElapse` to the system. - [X] Added convenience property `PlayerMobile.ShortTermMurders`. - [X] Added convenience properties `PlayerMobile.ShortTermMurderExpiration` and `PlayerMobile.LongTermMurderExpiration` - [X] Moved ReportMurdererGump.cs - [X] Adds an `EventSink.PlayerDeleted` event. ### Notes The system currently does not support NPCs. To support expiring murders on NPCs I highly recommend a different architecture for large servers (500k+ mobs including players). Specifically switching from looping through all MurderContext to a time-order link list.
This commit is contained in:
parent
d65e5bb37b
commit
26f784f45d
13 changed files with 650 additions and 335 deletions
|
|
@ -1,200 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Server.Misc;
|
||||
using Server.Mobiles;
|
||||
using Server.Network;
|
||||
using Server.SkillHandlers;
|
||||
|
||||
namespace Server.Gumps
|
||||
{
|
||||
public class ReportMurdererGump : Gump
|
||||
{
|
||||
private readonly List<Mobile> m_Killers;
|
||||
private int m_Idx;
|
||||
|
||||
private ReportMurdererGump(List<Mobile> killers, int idx = 0) : base(0, 0)
|
||||
{
|
||||
m_Killers = killers;
|
||||
m_Idx = idx;
|
||||
BuildGump();
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
EventSink.PlayerDeath += EventSink_PlayerDeath;
|
||||
}
|
||||
|
||||
public static void EventSink_PlayerDeath(Mobile m)
|
||||
{
|
||||
var killers = new List<Mobile>();
|
||||
var toGive = new List<Mobile>();
|
||||
|
||||
foreach (var ai in m.Aggressors)
|
||||
{
|
||||
if (ai.Attacker.Player && ai.CanReportMurder && !ai.Reported)
|
||||
{
|
||||
if (!Core.SE || !((PlayerMobile)m).RecentlyReported.Contains(ai.Attacker))
|
||||
{
|
||||
killers.Add(ai.Attacker);
|
||||
ai.Reported = true;
|
||||
ai.CanReportMurder = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ai.Attacker.Player && Core.Now - ai.LastCombatTime < TimeSpan.FromSeconds(30.0) &&
|
||||
!toGive.Contains(ai.Attacker))
|
||||
{
|
||||
toGive.Add(ai.Attacker);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var ai in m.Aggressed)
|
||||
{
|
||||
if (ai.Defender.Player && Core.Now - ai.LastCombatTime < TimeSpan.FromSeconds(30.0) &&
|
||||
!toGive.Contains(ai.Defender))
|
||||
{
|
||||
toGive.Add(ai.Defender);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var g in toGive)
|
||||
{
|
||||
var n = Notoriety.Compute(g, m);
|
||||
|
||||
var ourKarma = g.Karma;
|
||||
var innocent = n == Notoriety.Innocent;
|
||||
var criminal = n is Notoriety.Criminal or Notoriety.Murderer;
|
||||
|
||||
var fameAward = m.Fame / 200;
|
||||
var karmaAward = 0;
|
||||
|
||||
if (innocent)
|
||||
{
|
||||
karmaAward = ourKarma > -2500 ? -850 : -110 - m.Karma / 100;
|
||||
}
|
||||
else if (criminal)
|
||||
{
|
||||
karmaAward = 50;
|
||||
}
|
||||
|
||||
Titles.AwardFame(g, fameAward, false);
|
||||
Titles.AwardKarma(g, karmaAward, true);
|
||||
}
|
||||
|
||||
if (m is PlayerMobile mobile && mobile.NpcGuild == NpcGuild.ThievesGuild)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (killers.Count > 0)
|
||||
{
|
||||
new GumpTimer(m, killers).Start();
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildGump()
|
||||
{
|
||||
AddBackground(265, 205, 320, 290, 5054);
|
||||
Closable = false;
|
||||
Resizable = false;
|
||||
|
||||
AddPage(0);
|
||||
|
||||
AddImageTiled(225, 175, 50, 45, 0xCE); // Top left corner
|
||||
AddImageTiled(267, 175, 315, 44, 0xC9); // Top bar
|
||||
AddImageTiled(582, 175, 43, 45, 0xCF); // Top right corner
|
||||
AddImageTiled(225, 219, 44, 270, 0xCA); // Left side
|
||||
AddImageTiled(582, 219, 44, 270, 0xCB); // Right side
|
||||
AddImageTiled(225, 489, 44, 43, 0xCC); // Lower left corner
|
||||
AddImageTiled(267, 489, 315, 43, 0xE9); // Lower Bar
|
||||
AddImageTiled(582, 489, 43, 43, 0xCD); // Lower right corner
|
||||
|
||||
AddPage(1);
|
||||
|
||||
AddHtml(260, 234, 300, 140, m_Killers[m_Idx].Name); // Player's Name
|
||||
AddHtmlLocalized(260, 254, 300, 140, 1049066); // Would you like to report...
|
||||
|
||||
AddButton(260, 300, 0xFA5, 0xFA7, 1);
|
||||
AddHtmlLocalized(300, 300, 300, 50, 1046362); // Yes
|
||||
|
||||
AddButton(360, 300, 0xFA5, 0xFA7, 2);
|
||||
AddHtmlLocalized(400, 300, 300, 50, 1046363); // No
|
||||
}
|
||||
|
||||
public static void ReportedListExpiry_Callback(PlayerMobile from, Mobile killer)
|
||||
{
|
||||
if (from.RecentlyReported.Contains(killer))
|
||||
{
|
||||
from.RecentlyReported.Remove(killer);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnResponse(NetState state, RelayInfo info)
|
||||
{
|
||||
var from = (PlayerMobile)state.Mobile;
|
||||
|
||||
switch (info.ButtonID)
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
var killer = m_Killers[m_Idx];
|
||||
if (killer?.Deleted == false)
|
||||
{
|
||||
killer.Kills++;
|
||||
killer.ShortTermMurders++;
|
||||
|
||||
if (Core.SE)
|
||||
{
|
||||
from.RecentlyReported.Add(killer);
|
||||
Timer.StartTimer(TimeSpan.FromMinutes(10), () => ReportedListExpiry_Callback(from, killer));
|
||||
}
|
||||
|
||||
if (killer is PlayerMobile pk)
|
||||
{
|
||||
pk.ResetKillTime();
|
||||
pk.SendLocalizedMessage(1049067); // You have been reported for murder!
|
||||
|
||||
if (pk.Kills == 5)
|
||||
{
|
||||
pk.SendLocalizedMessage(502134); // You are now known as a murderer!
|
||||
}
|
||||
else if (Stealing.SuspendOnMurder && pk.Kills == 1 && pk.NpcGuild == NpcGuild.ThievesGuild)
|
||||
{
|
||||
pk.SendLocalizedMessage(501562); // You have been suspended by the Thieves Guild.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_Idx++;
|
||||
if (m_Idx < m_Killers.Count)
|
||||
{
|
||||
from.SendGump(new ReportMurdererGump( m_Killers, m_Idx));
|
||||
}
|
||||
}
|
||||
|
||||
private class GumpTimer : Timer
|
||||
{
|
||||
private readonly List<Mobile> m_Killers;
|
||||
private readonly Mobile m_Victim;
|
||||
|
||||
public GumpTimer(Mobile victim, List<Mobile> killers) : base(TimeSpan.FromSeconds(4.0))
|
||||
{
|
||||
m_Victim = victim;
|
||||
m_Killers = killers;
|
||||
}
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
m_Victim.SendGump(new ReportMurdererGump(m_Killers));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -95,23 +95,11 @@ namespace Server.Gumps
|
|||
AddRadio(30, 175, 9727, 9730, false, 0);
|
||||
AddHtmlLocalized(65, 178, 300, 25, 1060016, 0x7FFF); // I'd rather stay dead, you scoundrel!!!
|
||||
|
||||
AddHtmlLocalized(
|
||||
30,
|
||||
20,
|
||||
360,
|
||||
35,
|
||||
1060017,
|
||||
0x7FFF
|
||||
); // Wishing to rejoin the living, are you? I can restore your body... for a price of course...
|
||||
// Wishing to rejoin the living, are you? I can restore your body... for a price of course...
|
||||
AddHtmlLocalized(30, 20, 360, 35, 1060017, 0x7FFF);
|
||||
|
||||
AddHtmlLocalized(
|
||||
30,
|
||||
105,
|
||||
345,
|
||||
40,
|
||||
1060018,
|
||||
0x5B2D
|
||||
); // Do you accept the fee, which will be withdrawn from your bank?
|
||||
// Do you accept the fee, which will be withdrawn from your bank?
|
||||
AddHtmlLocalized(30, 105, 345, 40, 1060018, 0x5B2D);
|
||||
|
||||
AddImage(65, 72, 5605);
|
||||
|
||||
|
|
@ -156,20 +144,16 @@ namespace Server.Gumps
|
|||
{
|
||||
if (Banker.Withdraw(from, m_Price))
|
||||
{
|
||||
from.SendLocalizedMessage(
|
||||
1060398,
|
||||
m_Price.ToString()
|
||||
); // ~1_AMOUNT~ gold has been withdrawn from your bank box.
|
||||
from.SendLocalizedMessage(
|
||||
1060022,
|
||||
Banker.GetBalance(from).ToString()
|
||||
); // You have ~1_AMOUNT~ gold in cash remaining in your bank box.
|
||||
// ~1_AMOUNT~ gold has been withdrawn from your bank box.
|
||||
from.SendLocalizedMessage(1060398, m_Price.ToString());
|
||||
|
||||
// You have ~1_AMOUNT~ gold in cash remaining in your bank box.
|
||||
from.SendLocalizedMessage(1060022, Banker.GetBalance(from).ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendLocalizedMessage(
|
||||
1060020
|
||||
); // Unfortunately, you do not have enough cash in your bank to cover the cost of the healing.
|
||||
// Unfortunately, you do not have enough cash in your bank to cover the cost of the healing.
|
||||
from.SendLocalizedMessage(1060020);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -198,12 +182,14 @@ namespace Server.Gumps
|
|||
};
|
||||
}
|
||||
|
||||
if (m_FromSacrifice && from is PlayerMobile mobile)
|
||||
{
|
||||
mobile.AvailableResurrects -= 1;
|
||||
var player = from as PlayerMobile;
|
||||
|
||||
var pack = mobile.Backpack;
|
||||
var corpse = mobile.Corpse;
|
||||
if (m_FromSacrifice && player != null)
|
||||
{
|
||||
player.AvailableResurrects -= 1;
|
||||
|
||||
var pack = player.Backpack;
|
||||
var corpse = player.Corpse;
|
||||
|
||||
if (pack != null && corpse != null)
|
||||
{
|
||||
|
|
@ -228,9 +214,9 @@ namespace Server.Gumps
|
|||
Titles.AwardFame(from, -amount, true);
|
||||
}
|
||||
|
||||
if (!Core.AOS && from.ShortTermMurders >= 5)
|
||||
if (!Core.AOS && player?.ShortTermMurders >= 5)
|
||||
{
|
||||
var loss = (100.0 - (4.0 + from.ShortTermMurders / 5.0)) / 100.0; // 5 to 15% loss
|
||||
var loss = (100.0 - (4.0 + player.ShortTermMurders / 5.0)) / 100.0; // 5 to 15% loss
|
||||
|
||||
if (loss < 0.85)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue