#W# Confict: Fixed BaseCreature git merge conflict.
This commit is contained in:
commit
ad38314ca7
112 changed files with 5397 additions and 616 deletions
|
|
@ -6,11 +6,22 @@ using MimeKit;
|
|||
using Server.Accounting;
|
||||
using Server.Configurations;
|
||||
using Server.Engines.Help;
|
||||
using Server.Logging;
|
||||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public static class Email
|
||||
{
|
||||
private static readonly ILogger _logger = LogFactory.GetLogger(typeof(Email));
|
||||
|
||||
// How long SendCrashEmail holds up the exit after a crash: at the default settings, room for the first
|
||||
// attempts (3 and 6 s apart; a fourth starts at 21 s when they fail fast), and no longer when the SMTP
|
||||
// server is dead.
|
||||
private static readonly TimeSpan CrashEmailTimeout = TimeSpan.FromSeconds(30);
|
||||
|
||||
// A retry wait stops doubling at an hour: no multi-day waits, and nothing left to overflow.
|
||||
private const int MaxRetryDelaySeconds = 3600;
|
||||
|
||||
/// <summary>
|
||||
/// Sends Queue-Page request using Email
|
||||
/// </summary>
|
||||
|
|
@ -69,16 +80,17 @@ namespace Server.Misc
|
|||
}.ToMessageBody();
|
||||
}
|
||||
|
||||
SendAsync(message);
|
||||
_ = SendAsync(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends crash email
|
||||
/// Sends crash email, blocking for up to the crash timeout (30 s) while it is sent: the core exits
|
||||
/// as soon as the crash handlers return, and a send left running dies with it.
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
public static void SendCrashEmail(string filePath)
|
||||
{
|
||||
if (EmailConfiguration.EmailEnabled)
|
||||
if (!EmailConfiguration.EmailEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -94,27 +106,37 @@ namespace Server.Misc
|
|||
};
|
||||
builder.Attachments.Add(filePath);
|
||||
message.Body = builder.ToMessageBody();
|
||||
|
||||
try
|
||||
{
|
||||
if (!SendAsync(message).Wait(CrashEmailTimeout))
|
||||
{
|
||||
_logger.Warning("Crash email not sent after {Timeout}, giving up", CrashEmailTimeout);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Sending crash email failed");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends emails async
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
private static async void SendAsync(MimeMessage message)
|
||||
private static async Task SendAsync(MimeMessage message)
|
||||
{
|
||||
if (!EmailConfiguration.EmailEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var now = Core.Now;
|
||||
var messageID = $"<{now:yyyyMMdd}.{now:HHmmssff}@{EmailConfiguration.EmailServer}>";
|
||||
message.Headers.Add("Message-ID", messageID);
|
||||
message.From.Add(EmailConfiguration.FromAddress);
|
||||
var attempts = EmailConfiguration.EmailSendRetryCount;
|
||||
var retryDelays = RetryDelays(attempts, EmailConfiguration.EmailSendRetryDelay);
|
||||
Exception lastException = null;
|
||||
|
||||
var delay = EmailConfiguration.EmailSendRetryDelay;
|
||||
|
||||
for (var i = 0; i < EmailConfiguration.EmailSendRetryCount; i++)
|
||||
// SendCrashEmail can block the game loop on this task: every await needs ConfigureAwait(false).
|
||||
for (var i = 0; i < attempts; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -130,17 +152,40 @@ namespace Server.Misc
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
lastException = ex;
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
Console.WriteLine(ex.StackTrace);
|
||||
_logger.Warning(ex, "Sending email {Subject} failed", message.Subject);
|
||||
}
|
||||
|
||||
delay *= delay;
|
||||
|
||||
await Task.Delay(delay * 1000).ConfigureAwait(false);
|
||||
if (i < retryDelays.Length)
|
||||
{
|
||||
await Task.Delay(retryDelays[i]).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Error(lastException, "Email {Subject} not sent after {Attempts} attempts", message.Subject, attempts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The waits between send attempts: the configured delay, doubling each time, and none after the last.
|
||||
/// </summary>
|
||||
/// <param name="attempts"></param>
|
||||
/// <param name="firstDelaySeconds"></param>
|
||||
internal static TimeSpan[] RetryDelays(int attempts, int firstDelaySeconds)
|
||||
{
|
||||
var delays = new TimeSpan[Math.Max(attempts, 1) - 1];
|
||||
var seconds = Math.Clamp(firstDelaySeconds, 0, MaxRetryDelaySeconds);
|
||||
|
||||
for (var i = 0; i < delays.Length; i++)
|
||||
{
|
||||
delays[i] = TimeSpan.FromSeconds(seconds);
|
||||
seconds = Math.Min(seconds * 2, MaxRetryDelaySeconds);
|
||||
}
|
||||
|
||||
return delays;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -228,16 +228,11 @@ namespace Server.Misc
|
|||
}
|
||||
|
||||
if (bcTarg?.Controlled == true
|
||||
|| bcTarg?.Summoned == true && bcTarg.SummonMaster != from && bcTarg.SummonMaster?.Player == true)
|
||||
|| bcTarg?.SummonMaster is { Player: true } summoner && summoner != from)
|
||||
{
|
||||
return false; // Cannot harm other controlled mobiles from players
|
||||
}
|
||||
|
||||
if (pmFrom == null && bcFrom != null && bcFrom.Summoned && target.Player)
|
||||
{
|
||||
return true; // Summons from monsters can attack players
|
||||
}
|
||||
|
||||
if (target.Player)
|
||||
{
|
||||
return false; // Cannot harm other players
|
||||
|
|
@ -275,7 +270,24 @@ namespace Server.Misc
|
|||
return Notoriety.CanBeAttacked;
|
||||
}
|
||||
|
||||
Body body = target.Amount;
|
||||
// BaseCreatures are deleted on death, so target.Owner is null after a server restart.
|
||||
// The OwnerWasBaseCreature flag is the persisted snapshot that survives the live mobile.
|
||||
var creature = target.Owner as BaseCreature;
|
||||
var ownerWasCreature = target.OwnerWasBaseCreature || creature != null;
|
||||
|
||||
// A player's murderer and criminal status outrank guild standing, as on the live mobile.
|
||||
if (!ownerWasCreature)
|
||||
{
|
||||
if (target.Murderer)
|
||||
{
|
||||
return Notoriety.Murderer;
|
||||
}
|
||||
|
||||
if (target.Criminal && (target.Map?.Rules & MapRules.HarmfulRestrictions) == 0)
|
||||
{
|
||||
return Notoriety.Criminal;
|
||||
}
|
||||
}
|
||||
|
||||
var sourceGuild = GetGuildFor(source.Guild as Guild, source);
|
||||
var targetGuild = GetGuildFor(target.Guild, target.Owner);
|
||||
|
|
@ -297,11 +309,7 @@ namespace Server.Misc
|
|||
}
|
||||
}
|
||||
|
||||
// BaseCreatures are deleted on death, so target.Owner is null after a server restart.
|
||||
// The OwnerWasBaseCreature flag is the persisted snapshot that survives the live mobile.
|
||||
var creature = target.Owner as BaseCreature;
|
||||
|
||||
if (target.OwnerWasBaseCreature || creature != null)
|
||||
if (ownerWasCreature)
|
||||
{
|
||||
if (srcFaction != null && trgFaction != null && srcFaction != trgFaction && source.Map == Faction.Facet)
|
||||
{
|
||||
|
|
@ -314,6 +322,7 @@ namespace Server.Misc
|
|||
}
|
||||
|
||||
var actual = Notoriety.CanBeAttacked;
|
||||
Body body = target.Amount;
|
||||
|
||||
if (target.Murderer || body.IsMonster && target.OwnerWasSummoned || target.OwnerWasAnimatedDead)
|
||||
{
|
||||
|
|
@ -338,16 +347,6 @@ namespace Server.Misc
|
|||
return Notoriety.Innocent;
|
||||
}
|
||||
|
||||
if (target.Murderer || body.IsMonster)
|
||||
{
|
||||
return Notoriety.Murderer;
|
||||
}
|
||||
|
||||
if (target.Criminal && (target.Map?.Rules & MapRules.HarmfulRestrictions) == 0)
|
||||
{
|
||||
return Notoriety.Criminal;
|
||||
}
|
||||
|
||||
if (srcFaction != null && trgFaction != null && srcFaction != trgFaction && source.Map == Faction.Facet)
|
||||
{
|
||||
for (var i = 0; i < list.Count; ++i)
|
||||
|
|
@ -422,7 +421,7 @@ namespace Server.Misc
|
|||
return Notoriety.CanBeAttacked;
|
||||
}
|
||||
|
||||
master = bcTarg.ControlMaster;
|
||||
master = bcTarg.Controlled ? bcTarg.ControlMaster : null;
|
||||
|
||||
if (Core.ML && master != null)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue