using System;
using ACE.Common;
using ACE.Entity.Enum;
using ACE.Server.Managers;
using ACE.Server.Network.GameMessages.Messages;
using ACE.Server.WorldObjects;
namespace ACE.Server.Entity
{
public class Cloak
{
///
/// Opt out of the standarad ACE formula and proc at a custom rate
/// This setting will affect cloaks with any proc, including -200
///
private static bool UseCustomMath => PropertyManager.GetBool("use_cloak_proc_custom_scale", false).Item;
///
/// The maximum frequency of cloak procs, in seconds
///
private static double MinDelay
{
get
{
if (!UseCustomMath)
{
return 5.0;
}
return PropertyManager.GetDouble("cloak_cooldown_seconds").Item;
}
}
///
/// The base maximum percentage at which cloaks will proc
///
private static float MaxProcBase
{
get
{
if (!UseCustomMath)
{
return 0.25f;
}
return Convert.ToSingle(PropertyManager.GetDouble("cloak_max_proc_base").Item);
}
}
///
/// The minimum proc chance of a cloak
///
private static float MinProc
{
get
{
if (!UseCustomMath)
{
return 0f;
}
return Convert.ToSingle(PropertyManager.GetDouble("cloak_min_proc_base", 0f).Item);
}
}
///
/// The base maxiumum percentage at which cloaks with
/// -200 will proc
///
private static float MaxProcBase200
{
get
{
if (!UseCustomMath)
{
return 0.15f;
}
return Convert.ToSingle(PropertyManager.GetDouble("cloak_max_proc_base").Item);
}
}
private const float TwoThirds = 2.0f / 3.0f;
///
/// Rolls for a chance at procing a cloak spell
/// If successful, casts the spell
///
public static bool TryProcSpell(Creature defender, WorldObject attacker, WorldObject cloak, float damage_percent)
{
if (cloak == null) return false;
if (!RollProc(cloak, damage_percent))
return false;
return HandleProcSpell(defender, attacker, cloak);
}
///
/// Rolls for a chance at procing a cloak spell
///
/// The percent of MaxHealth inflicted by an enemy's hit
///
public static bool RollProc(WorldObject cloak, float damage_percent)
{
// TODO: find retail formula
var currentTime = Time.GetUnixTime();
if (currentTime - cloak.UseTimestamp < MinDelay)
return false;
var itemLevel = cloak.ItemLevel ?? 0;
if (itemLevel < 1) return false;
var maxProcBase = MaxProcBase;
if (HasDamageProc(cloak))
{
maxProcBase = MaxProcBase200;
damage_percent *= TwoThirds;
}
var maxProcRate = maxProcBase + (itemLevel - 1) * 0.0125f;
if (UseCustomMath)
{
// The proc chance should only plateau for damage above a certain configured percentage
var maxProcAtDamagePercent = Convert.ToSingle(PropertyManager.GetDouble("cloak_max_proc_damage_percentage", 30.0).Item);
// Reduce the damage percent for the calculation if necessary to a fraction of the percentage based on the configuration
damage_percent = maxProcRate * (damage_percent / maxProcAtDamagePercent);
}
// take the lowest of the chance between damage and proc rate, then override
// with min proc if necessary
var chance = Math.Max(Math.Min(damage_percent, maxProcRate), MinProc);
var rng = ThreadSafeRandom.Next(0.0f, 1.0f);
if (rng < chance)
{
cloak.UseTimestamp = currentTime;
return true;
}
else
return false;
}
///
/// Casts the cloak proc spell
///
public static bool HandleProcSpell(Creature defender, WorldObject attacker, WorldObject cloak)
{
if (cloak.ProcSpell == null) return false;
var spell = new Spell(cloak.ProcSpell.Value);
if (spell.NotFound)
{
if (defender is Player player)
{
if (spell._spellBase == null)
player.Session.Network.EnqueueSend(new GameMessageSystemChat($"SpellId {cloak.ProcSpell} Invalid.", ChatMessageType.System));
else
player.Session.Network.EnqueueSend(new GameMessageSystemChat($"{spell.Name} spell not implemented, yet!", ChatMessageType.System));
}
return false;
}
var targetSelf = spell.Flags.HasFlag(SpellFlags.SelfTargeted);
var untargeted = spell.NonComponentTargetType == ItemType.None;
var target = attacker;
if (untargeted)
target = null;
else if (targetSelf)
target = defender;
// cloak range?
var msg = new GameMessageSystemChat($"The cloak of {defender.Name} weaves the magic of {spell.Name}!", ChatMessageType.Spellcasting);
defender.EnqueueBroadcast(msg, WorldObject.LocalBroadcastRange, ChatMessageType.Spellcasting);
defender.TryCastSpell(spell, target, cloak, cloak, true, true, false);
return true;
}
///
/// Returns TRUE if object is cloak
///
public static bool IsCloak(WorldObject wo)
{
return wo.ValidLocations == EquipMask.Cloak;
}
///
/// The amount of damage reduced by a cloak proced with PropertyInt.CloakWeaveProc=2
///
public const int DamageReductionAmount = 200;
public static int GetDamageReductionAmount(WorldObject source)
{
var damageReductionAmount = DamageReductionAmount;
// https://asheron.fandom.com/wiki/Master_of_Arms
// Cloaks with the chance to reduce incoming damage by 200 have been reduced to 100 for PvP circumstances.
if (source is Player)
damageReductionAmount /= 2;
return damageReductionAmount;
}
///
/// Returns the reduced damage amount when a cloak procs
/// with PropertyInt.CloakWeaveProc=2
///
public static uint GetReducedAmount(WorldObject source, uint damage)
{
var damageReductionAmount = GetDamageReductionAmount(source);
if (damage > damageReductionAmount)
return (uint)(damage - damageReductionAmount);
else
return 0;
}
public static int GetReducedAmount(WorldObject source, int damage)
{
var damageReductionAmount = GetDamageReductionAmount(source);
return Math.Max(0, damage - damageReductionAmount);
}
public static float GetReducedAmount(WorldObject source, float damage)
{
var damageReductionAmount = GetDamageReductionAmount(source);
return Math.Max(0, damage - damageReductionAmount);
}
///
/// Sends the message to attacker and defender when cloak is proced with PropertyInt.CloakWeaveProc=2
///
public static void ShowMessage(Creature defender, WorldObject attacker, int origDamage, int reducedDamage)
{
var suffix = $"reduced the damage from {origDamage} down to {reducedDamage}!";
if (defender is Player playerDefender)
playerDefender.Session.Network.EnqueueSend(new GameMessageSystemChat($"Your cloak {suffix}", ChatMessageType.Magic));
// send message to attacker?
if (attacker is Player playerAttacker)
playerAttacker.Session.Network.EnqueueSend(new GameMessageSystemChat($"The cloak of {defender.Name} {suffix}", ChatMessageType.Magic));
}
public static void ShowMessage(Creature defender, WorldObject attacker, float origDamage, float reducedDamage)
{
ShowMessage(defender, attacker, (int)Math.Round(origDamage), (int)Math.Round(reducedDamage));
}
///
/// Returns TRUE If cloak has a damage reduction proc
/// Matches client logic
///
public static bool HasDamageProc(WorldObject cloak)
{
return cloak?.CloakWeaveProc == 2;
}
///
/// Returns TRUE if cloak has a spell proc
///
public static bool HasProcSpell(WorldObject cloak)
{
return cloak?.ProcSpell != null;
}
}
}