Add ActionLanded to make some checks cleaner.

Split physical and magic text id dictionaries.

Add calculation for resists.
This commit is contained in:
Yogurt 2019-05-27 16:52:56 -07:00
parent d0dca62a91
commit f6104812a5
3 changed files with 76 additions and 43 deletions

View File

@ -1109,7 +1109,7 @@ namespace FFXIVClassic_Map_Server.Actors
lua.LuaEngine.CallLuaBattleCommandFunction(this, command, folder, "onSkillFinish", this, chara, command, action, actions); lua.LuaEngine.CallLuaBattleCommandFunction(this, command, folder, "onSkillFinish", this, chara, command, action, actions);
//cached script //cached script
//skill.CallLuaFunction(owner, "onSkillFinish", this, chara, command, action, actions); //skill.CallLuaFunction(owner, "onSkillFinish", this, chara, command, action, actions);
if (action.hitType > HitType.Evade && action.hitType != HitType.Resist) if (action.ActionLanded())
{ {
hitTarget = true; hitTarget = true;
hitCount++; hitCount++;

View File

@ -17,36 +17,54 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
static class BattleUtils static class BattleUtils
{ {
public static Dictionary<HitType, ushort> SingleHitTypeTextIds = new Dictionary<HitType, ushort>() public static Dictionary<HitType, ushort> PhysicalHitTypeTextIds = new Dictionary<HitType, ushort>()
{ {
{ HitType.Miss, 30311 }, { HitType.Miss, 30311 },
{ HitType.Evade, 30310 }, { HitType.Evade, 30310 },
{ HitType.Parry, 30308 }, { HitType.Parry, 30308 },
{ HitType.Block, 30306 }, { HitType.Block, 30306 },
{ HitType.Resist, 30310 }, //Resists seem to use the evade text id
{ HitType.Hit, 30301 }, { HitType.Hit, 30301 },
{ HitType.Crit, 30302 } { HitType.Crit, 30302 }
}; };
public static Dictionary<HitType, ushort> MagicalHitTypeTextIds = new Dictionary<HitType, ushort>()
{
{ HitType.SingleResist,30318 },
{ HitType.DoubleResist,30317 },
{ HitType.TripleResist, 30316 },//Triple Resists seem to use the same text ID as full resists
{ HitType.FullResist,30316 },
{ HitType.Hit, 30319 },
{ HitType.Crit, 30392 } //Unsure why crit is separated from the rest of the ids
};
public static Dictionary<HitType, ushort> MultiHitTypeTextIds = new Dictionary<HitType, ushort>() public static Dictionary<HitType, ushort> MultiHitTypeTextIds = new Dictionary<HitType, ushort>()
{ {
{ HitType.Miss, 30449 }, //The attack misses. { HitType.Miss, 30449 }, //The attack misses.
{ HitType.Evade, 0 }, //Evades were removed before multi hit skills got their own messages, so this doesnt exist { HitType.Evade, 0 }, //Evades were removed before multi hit skills got their own messages, so this doesnt exist
{ HitType.Parry, 30448 }, //[Target] parries, taking x points of damage. { HitType.Parry, 30448 }, //[Target] parries, taking x points of damage.
{ HitType.Block, 30447 }, //[Target] blocks, taking x points of damage. { HitType.Block, 30447 }, //[Target] blocks, taking x points of damage.
{ HitType.Resist, 0 }, //No spells are multi-hit, so this doesn't exist
{ HitType.Hit, 30443 }, //[Target] tales x points of damage { HitType.Hit, 30443 }, //[Target] tales x points of damage
{ HitType.Crit, 30444 } //Critical! [Target] takes x points of damage. { HitType.Crit, 30444 } //Critical! [Target] takes x points of damage.
}; };
public static Dictionary<HitType, HitEffect> HitTypeEffects = new Dictionary<HitType, HitEffect>() public static Dictionary<HitType, HitEffect> HitTypeEffectsPhysical = new Dictionary<HitType, HitEffect>()
{ {
{ HitType.Miss, 0 }, { HitType.Miss, 0 },
{ HitType.Evade, HitEffect.Evade }, { HitType.Evade, HitEffect.Evade },
{ HitType.Parry, HitEffect.Parry }, { HitType.Parry, HitEffect.Parry },
{ HitType.Block, HitEffect.Block }, { HitType.Block, HitEffect.Block },
{ HitType.Resist, HitEffect.RecoilLv1 },//Probably don't need this, resists are handled differently to the rest
{ HitType.Hit, HitEffect.Hit }, { HitType.Hit, HitEffect.Hit },
{ HitType.Crit, HitEffect.Crit | HitEffect.CriticalHit }
};
//Magic attacks can't miss, be blocked, or parried. Resists are technically evades
public static Dictionary<HitType, HitEffect> HitTypeEffectsMagical = new Dictionary<HitType, HitEffect>()
{
{ HitType.SingleResist, HitEffect.WeakResist },
{ HitType.DoubleResist, HitEffect.WeakResist },
{ HitType.TripleResist, HitEffect.WeakResist },
{ HitType.FullResist, HitEffect.FullResist },
{ HitType.Hit, HitEffect.NoResist },
{ HitType.Crit, HitEffect.Crit } { HitType.Crit, HitEffect.Crit }
}; };
@ -206,7 +224,8 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
//Or we could have HitTypes for DoubleResist, TripleResist, and FullResist that get used here. //Or we could have HitTypes for DoubleResist, TripleResist, and FullResist that get used here.
public static void CalculateResistDamage(Character attacker, Character defender, BattleCommand skill, CommandResult action) public static void CalculateResistDamage(Character attacker, Character defender, BattleCommand skill, CommandResult action)
{ {
double percentResist = 0.5; //Every tier of resist is a 25% reduction in damage. ie SingleResist is 25% damage taken down, Double is 50% damage taken down, etc
double percentResist = 0.25 * (action.hitType - HitType.SingleResist + 1);
action.amountMitigated = (ushort)(action.amount * (1 - percentResist)); action.amountMitigated = (ushort)(action.amount * (1 - percentResist));
action.amount = (ushort)(action.amount * percentResist); action.amount = (ushort)(action.amount * percentResist);
@ -360,11 +379,26 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
return false; return false;
} }
//This probably isn't totally correct but it's close enough for now.
//Full Resists seem to be calculated in a different way because the resist rates don't seem to line up with kanikan's testing (their tests didn't show any full resists)
//Non-spells with elemental damage can be resisted, it just doesnt say in the chat that they were. As far as I can tell, all mob-specific attacks are considered not to be spells
public static bool TryResist(Character attacker, Character defender, BattleCommand skill, CommandResult action) public static bool TryResist(Character attacker, Character defender, BattleCommand skill, CommandResult action)
{ {
if ((Program.Random.NextDouble() * 100) <= action.resistRate) //The rate degrades for each check. Meaning with 100% resist, the attack will always be resisted, but it won't necessarily be a triple or full resist
//Rates beyond 100 still increase the chance for higher resist tiers though
double rate = action.resistRate;
int i = -1;
while ((Program.Random.NextDouble() * 100) <= rate && i < 4)
{ {
action.hitType = HitType.Resist; rate /= 2;
i++;
}
if (i != -1)
{
action.hitType = (HitType) ((int) HitType.SingleResist + i);
CalculateResistDamage(attacker, defender, skill, action); CalculateResistDamage(attacker, defender, skill, action);
return true; return true;
} }
@ -455,7 +489,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
} }
//Actions have different text ids depending on whether they're a part of a multi-hit ws or not. //Actions have different text ids depending on whether they're a part of a multi-hit ws or not.
Dictionary<HitType, ushort> textIds = SingleHitTypeTextIds; Dictionary<HitType, ushort> textIds = PhysicalHitTypeTextIds;
//If this is the first hit of a multi hit command, add the "You use [command] on [target]" action //If this is the first hit of a multi hit command, add the "You use [command] on [target]" action
//Needs to be done here because certain buff messages appear before it. //Needs to be done here because certain buff messages appear before it.
@ -484,17 +518,20 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
public static void FinishActionSpell(Character attacker, Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null) public static void FinishActionSpell(Character attacker, Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
{ {
//Determine the hit type of the action //I'm assuming that like physical attacks stoneskin is taken into account before mitigation
if (!TryMiss(attacker, defender, skill, action))
{
HandleStoneskin(defender, action); HandleStoneskin(defender, action);
if (!TryCrit(attacker, defender, skill, action))
//Determine the hit type of the action
//Spells don't seem to be able to miss, instead magic acc/eva is used for resists (which are generally called evades in game)
//Unlike blocks and parries, crits do not go through resists.
if (!TryResist(attacker, defender, skill, action)) if (!TryResist(attacker, defender, skill, action))
{
if (!TryCrit(attacker, defender, skill, action))
action.hitType = HitType.Hit; action.hitType = HitType.Hit;
} }
//There are no multi-hit spells //There are no multi-hit spells, so we don't need to take that into account
action.worldMasterTextId = SingleHitTypeTextIds[action.hitType]; action.worldMasterTextId = MagicalHitTypeTextIds[action.hitType];
//Set the hit effect //Set the hit effect
SetHitEffectSpell(attacker, defender, skill, action); SetHitEffectSpell(attacker, defender, skill, action);
@ -547,10 +584,10 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
hitEffect |= HitEffect.RecoilLv3; hitEffect |= HitEffect.RecoilLv3;
} }
hitEffect |= HitTypeEffects[hitType]; hitEffect |= HitTypeEffectsPhysical[hitType];
//For combos that land, add the combo effect //For combos that land, add the combo effect
if (skill != null && skill.isCombo && hitType > HitType.Evade && hitType != HitType.Evade && !skill.comboEffectAdded) if (skill != null && skill.isCombo && action.ActionLanded() && !skill.comboEffectAdded)
{ {
hitEffect |= (HitEffect)(skill.comboStep << 15); hitEffect |= (HitEffect)(skill.comboStep << 15);
skill.comboEffectAdded = true; skill.comboEffectAdded = true;
@ -560,7 +597,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
if (hitType >= HitType.Parry) if (hitType >= HitType.Parry)
{ {
//Protect / Shell only show on physical/ magical attacks respectively. //Protect / Shell only show on physical/ magical attacks respectively.
if (defender.statusEffects.HasStatusEffect(StatusEffectId.Protect)) if (defender.statusEffects.HasStatusEffect(StatusEffectId.Protect) || defender.statusEffects.HasStatusEffect(StatusEffectId.Protect2))
if (action != null) if (action != null)
hitEffect |= HitEffect.Protect; hitEffect |= HitEffect.Protect;
@ -577,20 +614,8 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
var hitEffect = HitEffect.MagicEffectType; var hitEffect = HitEffect.MagicEffectType;
HitType hitType = action.hitType; HitType hitType = action.hitType;
//Recoil levels for spells are a bit different than physical. Recoil levels are used for resists.
//Lv1 is for larger resists, Lv2 is for smaller resists and Lv3 is for no resists. Crit is still used for crits
if (hitType == HitType.Resist)
{
//todo: calculate resist levels and figure out what the difference between Lv1 and 2 in retail was. For now assuming a full resist with 0 damage dealt is Lv1, all other resists Lv2
if (action.amount == 0)
hitEffect |= HitEffect.RecoilLv1;
else
hitEffect |= HitEffect.RecoilLv2;
}
else
hitEffect |= HitEffect.RecoilLv3;
hitEffect |= HitTypeEffects[hitType]; hitEffect |= HitTypeEffectsMagical[hitType];
if (skill != null && skill.isCombo && !skill.comboEffectAdded) if (skill != null && skill.isCombo && !skill.comboEffectAdded)
{ {
@ -599,16 +624,15 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
} }
//if attack hit the target, take into account protective status effects //if attack hit the target, take into account protective status effects
if (hitType >= HitType.Block) if (action.ActionLanded())
{ {
//Protect / Shell only show on physical/ magical attacks respectively. //Protect / Shell only show on physical/ magical attacks respectively.
//The magic hit effect category only has a flag for shell (and another shield effect that seems unused)
//Even though traited protect gives magic defense, the shell effect doesn't play on attacks
//This also means stoneskin doesnt show, but it does reduce damage
if (defender.statusEffects.HasStatusEffect(StatusEffectId.Shell)) if (defender.statusEffects.HasStatusEffect(StatusEffectId.Shell))
if (action != null) if (action != null)
hitEffect |= HitEffect.Shell; hitEffect |= HitEffect.MagicShell;
if (defender.statusEffects.HasStatusEffect(StatusEffectId.Stoneskin))
if (action != null)
hitEffect |= HitEffect.Stoneskin;
} }
action.effectId = (uint)hitEffect; action.effectId = (uint)hitEffect;
} }
@ -659,7 +683,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
double rand = Program.Random.NextDouble(); double rand = Program.Random.NextDouble();
//Statuses only land for non-resisted attacks and attacks that hit //Statuses only land for non-resisted attacks and attacks that hit
if (skill != null && skill.statusId != 0 && (action.hitType > HitType.Evade && action.hitType != HitType.Resist) && rand < skill.statusChance) if (skill != null && skill.statusId != 0 && (action.ActionLanded()) && rand < skill.statusChance)
{ {
StatusEffect effect = Server.GetWorldManager().GetStatusEffect(skill.statusId); StatusEffect effect = Server.GetWorldManager().GetStatusEffect(skill.statusId);
//Because combos might change duration or tier //Because combos might change duration or tier

View File

@ -195,9 +195,12 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
Evade = 1, Evade = 1,
Parry = 2, Parry = 2,
Block = 3, Block = 3,
Resist = 4, SingleResist = 4,
Hit = 5, DoubleResist = 5,
Crit = 6 TripleResist = 6,
FullResist = 7,
Hit = 8,
Crit = 9
} }
//Type of action //Type of action
@ -387,5 +390,11 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
{ {
return (ushort)hitType; return (ushort)hitType;
} }
//Whether this action didn't miss, and wasn't evaded or resisted
public bool ActionLanded()
{
return hitType > HitType.Evade && hitType != HitType.SingleResist && hitType != HitType.DoubleResist && hitType != HitType.FullResist;
}
} }
} }