Merged in takhlaq/ffxiv-classic-server (pull request #61)

Combat changes and fixes.

Approved-by: Filip Maj <filipmaj@gmail.com>
This commit is contained in:
yogurt 2019-06-08 22:58:02 +00:00 committed by Filip Maj
commit ec85cfd590
169 changed files with 2595 additions and 1449 deletions

View File

@ -7,6 +7,7 @@ namespace FFXIVClassic.Common
public static class Utils
{
private static readonly uint[] _lookup32 = CreateLookup32();
private static readonly DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private static uint[] CreateLookup32()
{
@ -106,6 +107,11 @@ namespace FFXIVClassic.Common
return unixTimeStamp;
}
public static DateTime UnixTimeStampToDateTime(uint timestamp)
{
return epoch.AddSeconds(timestamp);
}
public static ulong SwapEndian(ulong input)
{
return 0x00000000000000FF & (input >> 56) |

View File

@ -925,13 +925,13 @@ namespace FFXIVClassic_Map_Server
{
while (reader.Read())
{
var id = reader.GetUInt32(0);
var duration = reader.GetUInt32(1);
var magnitude = reader.GetUInt64(2);
var tick = reader.GetUInt32(3);
var tier = reader.GetByte(4);
var extra = reader.GetUInt64(5);
var id = reader.GetUInt32("statusId");
var duration = reader.GetUInt32("duration");
var magnitude = reader.GetUInt64("magnitude");
var tick = reader.GetUInt32("tick");
var tier = reader.GetByte("tier");
var extra = reader.GetUInt64("extra");
var effect = Server.GetWorldManager().GetStatusEffect(id);
if (effect != null)
{
@ -942,7 +942,7 @@ namespace FFXIVClassic_Map_Server
effect.SetExtra(extra);
// dont wanna send ton of messages on login (i assume retail doesnt)
player.statusEffects.AddStatusEffect(effect, null, true);
player.statusEffects.AddStatusEffect(effect, null);
}
}
}
@ -2287,7 +2287,7 @@ namespace FFXIVClassic_Map_Server
{
conn.Open();
var query = @"SELECT id, name, flags, overwrite, tickMs FROM server_statuseffects;";
var query = @"SELECT id, name, flags, overwrite, tickMs, hidden, silentOnGain, silentOnLoss, statusGainTextId, statusLossTextId FROM server_statuseffects;";
MySqlCommand cmd = new MySqlCommand(query, conn);
@ -2300,7 +2300,14 @@ namespace FFXIVClassic_Map_Server
var flags = reader.GetUInt32("flags");
var overwrite = reader.GetByte("overwrite");
var tickMs = reader.GetUInt32("tickMs");
var effect = new StatusEffect(id, name, flags, overwrite, tickMs);
var hidden = reader.GetBoolean("hidden");
var silentOnGain = reader.GetBoolean("silentOnGain");
var silentOnLoss = reader.GetBoolean("silentOnLoss");
var statusGainTextId = reader.GetUInt16("statusGainTextId");
var statusLossTextId = reader.GetUInt16("statusLossTextId");
var effect = new StatusEffect(id, name, flags, overwrite, tickMs, hidden, silentOnGain, silentOnLoss, statusGainTextId, statusLossTextId);
lua.LuaEngine.LoadStatusEffectScript(effect);
effects.Add(id, effect);
}
@ -2392,8 +2399,8 @@ namespace FFXIVClassic_Map_Server
battleCommand.job = reader.GetByte("classJob");
battleCommand.level = reader.GetByte("lvl");
battleCommand.requirements = (BattleCommandRequirements)reader.GetUInt16("requirements");
battleCommand.mainTarget = (ValidTarget)reader.GetByte("mainTarget");
battleCommand.validTarget = (ValidTarget)reader.GetByte("validTarget");
battleCommand.mainTarget = (ValidTarget)reader.GetUInt16("mainTarget");
battleCommand.validTarget = (ValidTarget)reader.GetUInt16("validTarget");
battleCommand.aoeType = (TargetFindAOEType)reader.GetByte("aoeType");
battleCommand.basePotency = reader.GetUInt16("basePotency");
battleCommand.numHits = reader.GetByte("numHits");
@ -2410,8 +2417,8 @@ namespace FFXIVClassic_Map_Server
battleCommand.castTimeMs = reader.GetUInt32("castTime");
battleCommand.maxRecastTimeSeconds = reader.GetUInt32("recastTime");
battleCommand.recastTimeMs = battleCommand.maxRecastTimeSeconds * 1000;
battleCommand.mpCost = reader.GetUInt16("mpCost");
battleCommand.tpCost = reader.GetUInt16("tpCost");
battleCommand.mpCost = reader.GetInt16("mpCost");
battleCommand.tpCost = reader.GetInt16("tpCost");
battleCommand.animationType = reader.GetByte("animationType");
battleCommand.effectAnimation = reader.GetUInt16("effectAnimation");
battleCommand.modelAnimation = reader.GetUInt16("modelAnimation");
@ -2433,7 +2440,26 @@ namespace FFXIVClassic_Map_Server
battleCommand.actionType = (ActionType)reader.GetInt16("actionType");
battleCommand.accuracyModifier = reader.GetFloat("accuracyMod");
battleCommand.worldMasterTextId = reader.GetUInt16("worldMasterTextId");
lua.LuaEngine.LoadBattleCommandScript(battleCommand, "weaponskill");
string folderName = "";
switch (battleCommand.commandType)
{
case CommandType.AutoAttack:
folderName = "autoattack";
break;
case CommandType.WeaponSkill:
folderName = "weaponskill";
break;
case CommandType.Ability:
folderName = "ability";
break;
case CommandType.Spell:
folderName = "magic";
break;
}
lua.LuaEngine.LoadBattleCommandScript(battleCommand, folderName);
battleCommandDict.Add(id, battleCommand);
Tuple<byte, short> tuple = Tuple.Create<byte, short>(battleCommand.job, battleCommand.level);

View File

@ -478,7 +478,7 @@ namespace FFXIVClassic_Map_Server
battleNpcGenusMods.TryGetValue(battleNpc.genusId, out battleNpc.genusMods);
battleNpcSpawnMods.TryGetValue(battleNpc.GetBattleNpcId(), out battleNpc.spawnMods);
battleNpc.SetMod((uint)Modifier.Speed, reader.GetByte("speed"));
battleNpc.SetMod((uint)Modifier.MovementSpeed, reader.GetByte("speed"));
battleNpc.neutral = reader.GetByte("aggroType") == 0;
battleNpc.SetDetectionType(reader.GetUInt32("detection"));
@ -603,7 +603,7 @@ namespace FFXIVClassic_Map_Server
reader.GetUInt16("actorState"), reader.GetUInt32("animationId"), "");
battleNpc.SetBattleNpcId(reader.GetUInt32("bnpcId"));
battleNpc.SetMod((uint)Modifier.Speed, reader.GetByte("speed"));
battleNpc.SetMod((uint)Modifier.MovementSpeed, reader.GetByte("speed"));
battleNpc.neutral = reader.GetByte("aggroType") == 0;
// set mob mods

View File

@ -29,6 +29,7 @@ namespace FFXIVClassic_Map_Server.Actors
Stats = 0x100,
Status = 0x200,
StatusTime = 0x400,
Hotbar = 0x800,
AllNpc = 0xDF,
AllPlayer = 0x13F
@ -92,6 +93,7 @@ namespace FFXIVClassic_Map_Server.Actors
this.moveSpeeds[1] = SetActorSpeedPacket.DEFAULT_WALK;
this.moveSpeeds[2] = SetActorSpeedPacket.DEFAULT_RUN;
this.moveSpeeds[3] = SetActorSpeedPacket.DEFAULT_ACTIVE;
positionUpdates = new List<Vector3>();
}
public void SetPushCircleRange(string triggerName, float size)
@ -650,7 +652,7 @@ namespace FFXIVClassic_Map_Server.Actors
public void LookAt(Actor actor)
{
if (actor != null && actor != this)
if (actor != null)
{
LookAt(actor.positionX, actor.positionZ);
}
@ -670,16 +672,20 @@ namespace FFXIVClassic_Map_Server.Actors
public void LookAt(float x, float z)
{
var rot1 = this.rotation;
//Don't rotate if the lookat position is same as our current position
if (positionX != x || positionZ != z)
{
var rot1 = this.rotation;
var dX = this.positionX - x;
var dY = this.positionZ - z;
var rot2 = Math.Atan2(dY, dX);
var dRot = Math.PI - rot2 + Math.PI / 2;
var dX = this.positionX - x;
var dY = this.positionZ - z;
var rot2 = Math.Atan2(dY, dX);
var dRot = Math.PI - rot2 + Math.PI / 2;
// pending move, dont need to unset it
this.updateFlags |= ActorUpdateFlags.Position;
rotation = (float)dRot;
// pending move, dont need to unset it
this.updateFlags |= ActorUpdateFlags.Position;
rotation = (float)dRot;
}
}
// todo: is this legit?

View File

@ -149,8 +149,8 @@ namespace FFXIVClassic_Map_Server.Actors
// todo: move this somewhere more appropriate
// todo: base this on equip and shit
SetMod((uint)Modifier.AttackRange, 3);
SetMod((uint)Modifier.AttackDelay, (Program.Random.Next(30, 60) * 100));
SetMod((uint)Modifier.Speed, (uint)moveSpeeds[2]);
SetMod((uint)Modifier.Delay, (Program.Random.Next(30, 60) * 100));
SetMod((uint)Modifier.MovementSpeed, (uint)moveSpeeds[2]);
spawnX = positionX;
spawnY = positionY;
@ -238,8 +238,7 @@ namespace FFXIVClassic_Map_Server.Actors
public void DoBattleAction(ushort commandId, uint animationId, CommandResult[] results)
{
int currentIndex = 0;
//AoE abilities only ever hit 16 people, so we probably won't need this loop anymore
//Apparently aoe are limited to 8?
while (true)
{
if (results.Length - currentIndex >= 10)
@ -253,9 +252,6 @@ namespace FFXIVClassic_Map_Server.Actors
}
else
break;
//I think aoe effects play on all hit enemies. Firaga does at least
//animationId = 0; //If more than one packet is sent out, only send the animation once to avoid double playing.
}
}
@ -317,7 +313,7 @@ namespace FFXIVClassic_Map_Server.Actors
else
modifiers.Add((Modifier)modifier, val);
if (modifier <= 35)
if (modifier >= 3 && modifier <= 35)
updateFlags |= ActorUpdateFlags.Stats;
}
@ -344,6 +340,29 @@ namespace FFXIVClassic_Map_Server.Actors
SetMod(modifier, newVal);
}
public void MultiplyMod(Modifier modifier, double val)
{
MultiplyMod((uint)modifier, val);
}
public void MultiplyMod(uint modifier, double val)
{
double newVal = GetMod(modifier) * val;
SetMod(modifier, newVal);
}
public void DivideMod(Modifier modifier, double val)
{
DivideMod((uint)modifier, val);
}
public void DivideMod(uint modifier, double val)
{
double newVal = GetMod(modifier) / val;
SetMod(modifier, newVal);
}
public virtual void OnPath(Vector3 point)
{
//lua.LuaEngine.CallLuaBattleFunction(this, "onPath", this, point);
@ -380,7 +399,7 @@ namespace FFXIVClassic_Map_Server.Actors
if ((updateFlags & ActorUpdateFlags.SubState) != 0)
{
//packets.Add(SetActorSubStatePacket.BuildPacket(actorId, currentSubState));
packets.Add(SetActorSubStatePacket.BuildPacket(actorId, currentSubState));
//packets.Add(CommandResultX00Packet.BuildPacket(actorId, 0x72000062, 0));
//packets.Add(CommandResultX01Packet.BuildPacket(actorId, 0x7C000062, 21001, new CommandResult(actorId, 0, 1)));
@ -390,6 +409,7 @@ namespace FFXIVClassic_Map_Server.Actors
if ((updateFlags & ActorUpdateFlags.Status) != 0)
{
List<SubPacket> statusPackets = statusEffects.GetStatusPackets();
packets.AddRange(statusPackets);
statusPackets.Clear();
@ -428,24 +448,15 @@ namespace FFXIVClassic_Map_Server.Actors
return true;
}
public virtual bool CanCast(Character target, BattleCommand spell)
public virtual bool CanUse(Character target, BattleCommand skill, CommandResult error = null)
{
return false;
}
public virtual bool CanWeaponSkill(Character target, BattleCommand skill)
{
return false;
}
public virtual bool CanUseAbility(Character target, BattleCommand ability)
{
return false;
}
public virtual uint GetAttackDelayMs()
{
return (uint)GetMod((uint)Modifier.AttackDelay);
return (uint)GetMod((uint)Modifier.Delay);
}
public virtual uint GetAttackRange()
@ -523,8 +534,8 @@ namespace FFXIVClassic_Map_Server.Actors
aiContainer.Reset();
// todo: reset hp/mp/tp etc here
ChangeState(SetActorStatePacket.MAIN_STATE_PASSIVE);
charaWork.parameterSave.hp = charaWork.parameterSave.hpMax;
charaWork.parameterSave.mp = charaWork.parameterSave.mpMax;
SetHP((uint) GetMaxHP());
SetMP((uint) GetMaxMP());
RecalculateStats();
}
@ -609,8 +620,8 @@ namespace FFXIVClassic_Map_Server.Actors
public void SetMP(uint mp)
{
charaWork.parameterSave.mpMax = (short)mp;
if (mp > charaWork.parameterSave.hpMax[0])
charaWork.parameterSave.mp = (short)mp;
if (mp > charaWork.parameterSave.mpMax)
SetMaxMP(mp);
updateFlags |= ActorUpdateFlags.HpTpMp;
@ -621,8 +632,9 @@ namespace FFXIVClassic_Map_Server.Actors
charaWork.parameterSave.mp = (short)mp;
updateFlags |= ActorUpdateFlags.HpTpMp;
}
// todo: the following functions are virtuals since we want to check hidden item bonuses etc on player for certain conditions
public virtual void AddHP(int hp)
public virtual void AddHP(int hp, CommandResultContainer resultContainer = null)
{
// dont wanna die ded, don't want to send update if hp isn't actually changed
if (IsAlive() && hp != 0)
@ -634,6 +646,9 @@ namespace FFXIVClassic_Map_Server.Actors
charaWork.parameterSave.hp[0] = (short)addHp;
updateFlags |= ActorUpdateFlags.HpTpMp;
if (charaWork.parameterSave.hp[0] < 1)
Die(Program.Tick, resultContainer);
}
}
@ -675,9 +690,9 @@ namespace FFXIVClassic_Map_Server.Actors
}
}
public void DelHP(int hp)
public void DelHP(int hp, CommandResultContainer resultContainer = null)
{
AddHP((short)-hp);
AddHP((short)-hp, resultContainer);
}
public void DelMP(int mp)
@ -743,7 +758,7 @@ namespace FFXIVClassic_Map_Server.Actors
public virtual float GetSpeed()
{
// todo: for battlenpc/player calculate speed
return (float) GetMod((uint)Modifier.Speed);
return (float) GetMod((uint)Modifier.MovementSpeed);
}
public virtual void OnAttack(State state, CommandResult action, ref CommandResult error)
@ -821,55 +836,55 @@ namespace FFXIVClassic_Map_Server.Actors
}
public virtual void OnDamageDealt(Character defender, CommandResult action, CommandResultContainer actionContainer = null)
public virtual void OnDamageDealt(Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
{
switch (action.hitType)
{
case (HitType.Miss):
OnMiss(this, action, actionContainer);
OnMiss(defender, skill, action, actionContainer);
break;
case (HitType.Crit):
OnCrit(defender, skill, action, actionContainer);
OnHit(defender, skill, action, actionContainer);
break;
default:
OnHit(defender, action, actionContainer);
OnHit(defender, skill, action, actionContainer);
break;
}
//TP is only gained from autoattacks and abilities
if (action.commandType == CommandType.AutoAttack || action.commandType == CommandType.Ability)
if ((action.commandType == CommandType.AutoAttack || action.commandType == CommandType.Ability) && action.hitType != HitType.Miss)
{
//TP gained on an attack is usually 100 * delay.
//Store TP seems to add .1% per point.
double weaponDelay = GetMod(Modifier.AttackDelay) / 1000.0;
var storeTPPercent = 1 + (GetMod(Modifier.StoreTP) * 0.001);
double weaponDelay = GetMod(Modifier.Delay) / 1000.0;
var storeTPPercent = 1 + (GetMod(Modifier.StoreTp) * 0.001);
AddTP((int)(weaponDelay * 100 * storeTPPercent));
}
}
public virtual void OnDamageTaken(Character attacker, CommandResult action, CommandResultContainer actionContainer = null)
public virtual void OnDamageTaken(Character attacker, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
{
switch (action.hitType)
{
case (HitType.Miss):
OnEvade(attacker, action, actionContainer);
OnEvade(attacker, skill, action, actionContainer);
break;
case (HitType.Parry):
OnParry(attacker, action, actionContainer);
OnParry(attacker, skill, action, actionContainer);
break;
case (HitType.Block):
OnBlock(attacker, action, actionContainer);
OnBlock(attacker, skill, action, actionContainer);
break;
}
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnDamageTaken, "onDamageTaken", attacker, this, action);
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnDamageTaken, "onDamageTaken", attacker, this, skill, action, actionContainer);
//TP gain formula seems to be something like 5 * e ^ ( -0.667 * [defender's level] ) * damage taken, rounded up
//This should be completely accurate at level 50, but isn't totally accurate at lower levels.
//Don't know if store tp impacts this
double tpModifier = 5 * Math.Pow(Math.E, (-0.0667 * GetLevel()));
AddTP((int)Math.Ceiling(tpModifier * action.amount));
if (charaWork.parameterSave.hp[0] < 1)
Die(Program.Tick, actionContainer);
}
public UInt64 GetTempVar(string name)
@ -978,8 +993,7 @@ namespace FFXIVClassic_Map_Server.Actors
{
StatusEffect procEffect = Server.GetWorldManager().GetStatusEffect(effectId);
procEffect.SetDuration(5);
procEffect.SetSilent(true);
statusEffects.AddStatusEffect(procEffect, this, true, true);
statusEffects.AddStatusEffect(procEffect, this);
}
//Otherwise we're reseting a proc, remove the status
else
@ -1013,36 +1027,41 @@ namespace FFXIVClassic_Map_Server.Actors
}
//Called when this character evades attacker's action
public void OnEvade(Character attacker, CommandResult action, CommandResultContainer actionContainer = null)
public void OnEvade(Character attacker, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
{
SetProc((ushort)HitType.Evade);
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnEvade, "onEvade", attacker, this, action, actionContainer);
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnEvade, "onEvade", attacker, this, skill, action, actionContainer);
}
//Called when this character blocks attacker's action
public void OnBlock(Character attacker, CommandResult action, CommandResultContainer actionContainer = null)
public void OnBlock(Character attacker, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
{
SetProc((ushort)HitType.Block);
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnBlock, "onBlock", attacker, this, action, actionContainer);
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnBlock, "onBlock", attacker, this, skill, action, actionContainer);
}
//Called when this character parries attacker's action
public void OnParry(Character attacker, CommandResult action, CommandResultContainer actionContainer = null)
public void OnParry(Character attacker, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
{
SetProc((ushort)HitType.Parry);
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnParry, "onParry", attacker, this, action, actionContainer);
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnParry, "onParry", attacker, this, skill, action, actionContainer);
}
//Called when this character misses
public void OnMiss(Character defender, CommandResult action, CommandResultContainer actionContainer = null)
public void OnMiss(Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
{
SetProc((ushort)HitType.Miss);
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnMiss, "onMiss", this, defender, action, actionContainer);
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnMiss, "onMiss", this, defender, skill, action, actionContainer);
}
public void OnHit(Character defender, CommandResult action, CommandResultContainer actionContainer = null)
public void OnHit(Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
{
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnHit, "onHit", this, defender, action, actionContainer);
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnHit, "onHit", this, defender, skill, action, actionContainer);
}
public void OnCrit(Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
{
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnCrit, "onCrit", this, defender, skill, action, actionContainer);
}
//The order of messages that appears after using a command is:
@ -1056,7 +1075,7 @@ namespace FFXIVClassic_Map_Server.Actors
//5. The hit itself. For single hit commands this message is "Your [command] hits [target] for x damage" for multi hits it's "[Target] takes x points of damage"
//6. Stoneskin falling off
//6. Buffs that activate after a command hits, like Aegis Boon and Divine Veil
//After all hits
//7. If it's a multi-hit command there's a "{numhits]fold attack..." message or if all hits miss an "All attacks missed" message
//8. Buffs that fall off after the skill ends, like Excruciate
@ -1092,7 +1111,7 @@ namespace FFXIVClassic_Map_Server.Actors
lua.LuaEngine.CallLuaBattleCommandFunction(this, command, folder, "onSkillFinish", this, chara, command, action, actions);
//cached script
//skill.CallLuaFunction(owner, "onSkillFinish", this, chara, command, action, actions);
if (action.hitType > HitType.Evade && action.hitType != HitType.Resist)
if (action.ActionLanded())
{
hitTarget = true;
hitCount++;
@ -1116,18 +1135,20 @@ namespace FFXIVClassic_Map_Server.Actors
actions.AddAction(new CommandResult(actorId, 30202, 0));
}
DelMP(command.CalculateMpCost(this));
DelTP(command.CalculateTpCost(this));
//Now that we know if we hit the target we can check if the combo continues
if (this is Player)
{
if (command.isCombo && hitTarget)
((Player)this).SetCombos(command.comboNextCommandId);
else
//Only reset combo if the command is a spell or weaponskill, since abilities can be used between combo skills
else if (command.commandType == CommandType.Spell || command.commandType == CommandType.WeaponSkill)
((Player)this).SetCombos();
}
CommandResult error = new CommandResult(actorId, 0, 0);
DelMP(command.CalculateMpCost(this));
DelTP(command.CalculateTpCost(this));
actions.CombineLists();
DoBattleAction(command.id, command.battleAnimation, actions.GetList());
}
@ -1135,8 +1156,8 @@ namespace FFXIVClassic_Map_Server.Actors
public List<Character> GetPartyMembersInRange(uint range)
{
TargetFind targetFind = new TargetFind(this);
targetFind.SetAOEType(ValidTarget.PartyMember, TargetFindAOEType.Circle, TargetFindAOETarget.Self, range, 0, 10, 0, 0);
targetFind.FindWithinArea(this, ValidTarget.PartyMember, TargetFindAOETarget.Self);
targetFind.SetAOEType(ValidTarget.Party, TargetFindAOEType.Circle, TargetFindAOETarget.Self, range, 0, 10, 0, 0);
targetFind.FindWithinArea(this, ValidTarget.Party, TargetFindAOETarget.Self);
return targetFind.GetTargets();
}

View File

@ -7,88 +7,157 @@ namespace FFXIVClassic_Map_Server.actors.chara
//Also, 0-35 should probably match with up BattleTemp
enum Modifier : UInt32
{
NAMEPLATE_SHOWN = 0,
TARGETABLE = 1,
NAMEPLATE_SHOWN2 = 2,
//NAMEPLATE_SHOWN2 = 3,
//These line up with ParamNames starting at 15001 and appear on gear
//Health
Hp = 0, //Max HP
Mp = 1, //Max MP
Tp = 2, //Max TP
Strength = 3,
Vitality = 4,
Dexterity = 5,
Intelligence = 6,
Mind = 7,
Piety = 8,
//Main stats
Strength = 3,
Vitality = 4,
Dexterity = 5,
Intelligence = 6,
Mind = 7,
Piety = 8,
ResistFire = 9,
ResistIce = 10,
ResistWind = 11,
ResistLightning = 12,
ResistEarth = 13,
ResistWater = 14,
//Elemental Resistances
FireResistance = 9, //Lowers Fire damage taken
IceResistance = 10, //Lowers Ice damage taken
WindResistance = 11, //Lowers Wind damage taken
EarthResistance = 12, //Lowers Earth damage taken
LightningResistance = 13, //Lowers Lightning damage taken
WaterResistance = 14, //Lowers Water damage taken
Accuracy = 15,
Evasion = 16,
Attack = 17,
Defense = 18, //Is there a magic defense stat? 19 maybe?
MagicAttack = 23,
MagicHeal = 24,
MagicEnhancePotency = 25,
MagicEnfeeblingPotency = 26,
//Physical Secondary stats
Accuracy = 15, //Increases chance to hit with physical attacks
Evasion = 16, //Decreases chance to be hit by physical attacks
Attack = 17, //Increases damage done with physical attacks
Defense = 18, //Decreases damage taken from physical attacks
MagicAccuracy = 27,
MagicEvasion = 28,
//Physical crit stats
CriticalHitRating = 19, //Increases chance to crit with physical attacks
CriticalHitEvasion = 20, //Decreases chance to be crit by physical attacks
CriticalHitAttackPower = 21, //Increases damage done by critical physical attacks
CriticalHitResilience = 22, //Decreases damage taken from critical physical attacks
CraftProcessing = 30,
CraftMagicProcessing = 31,
CraftProcessControl = 32,
//Magic secondary stats
AttackMagicPotency = 23, //Increases damage done with magical attacks
HealingMagicPotency = 24, //Increases healing done with magic healing
EnhancementMagicPotency = 25, //Increases effect of enhancement magic
EnfeeblingMagicPotency = 26, //Increases effect of enfeebling magic
MagicAccuracy = 27, //Decreases chance for magic to be evaded
MagicEvasion = 28, //Increases chance to evade magic
HarvestPotency = 33,
HarvestLimit = 34,
HarvestRate = 35,
//Crafting stats
Craftsmanship = 29,
MagicCraftsmanship = 30,
Control = 31,
Gathering = 32,
Output = 33,
Perception = 34,
None = 36,
Hp = 37,
HpPercent = 38,
Mp = 39,
MpPercent = 40,
Tp = 41,
TpPercent = 42,
Regen = 43,
Refresh = 44,
//Magic crit stats
MagicCriticalHitRating = 35, //Increases chance to crit with magical attacks
MagicCriticalHitEvasion = 36, //Decreases chance to be crit by magical attacks
MagicCriticalHitPotency = 37, //Increases damage done by critical magical attacks
MagicCriticalHitResilience = 38, //Decreases damage taken from critical magical attacks
AttackRange = 45,
Speed = 46,
AttackDelay = 47,
//Blocking stats
Parry = 39, //Increases chance to parry
BlockRate = 40, //Increases chance to block
Block = 41, //Reduces damage taken from blocked attacks
Raise = 48,
MinimumHpLock = 49, // hp cannot fall below this value
AttackType = 50, // slashing, piercing, etc
BlockRate = 51,
Block = 52,
CritRating = 53,
HasShield = 54, // Need this because shields are required for blocks. Could have used BlockRate or Block but BlockRate is provided by Gallant Sollerets and Block is provided by some buffs.
HitCount = 55, // Amount of hits in an auto attack. Usually 1, 2 for h2h, 3 with spinning heel
//Elemental Potencies
FireMagicPotency = 42, //Increases damage done by Fire Magic
IceMagicPotency = 43, //Increases damage done by Ice Magic
WindMagicPotency = 44, //Increases damage done by Wind Magic
EarthMagicPotency = 45, //Increases damage done by Earth Magic
LightningMagicPotency = 46, //Increases damage done by Lightning Magic
WaterMagicPotency = 47, //Increases damage done by Water Magic
//Flat percent increases to these rates. Probably a better way to do this
RawEvadeRate = 56,
RawParryRate = 57,
RawBlockRate = 58,
RawResistRate = 59,
RawHitRate = 60,
RawCritRate = 61,
//Miscellaneous
Regen = 48, //Restores health over time
Refresh = 49, //Restores MP over time
StoreTp = 50, //Increases TP gained by auto attacks and damaging abiltiies
Enmity = 51, //Increases enmity gained from actions
Spikes = 52, //Deals damage or status to attacker when hit
Haste = 53, //Increases attack speed
//54 and 55 didn't have names and seem to be unused
ReducedDurabilityLoss = 56, //Reduces durability loss
IncreasedSpiritbondGain = 57, //Increases rate of spiritbonding
Damage = 58, //Increases damage of auto attacks
Delay = 59, //Increases rate of auto attacks
Fastcast = 60, //Increases speed of casts
MovementSpeed = 61, //Increases movement speed
Exp = 62, //Increases experience gained
RestingHp = 63, //?
RestingMp = 64, //?
DamageTakenDown = 62, // Percent damage taken down
StoreTP = 63, //.1% extra tp per point. Lancer trait is 50 StoreTP
PhysicalCritRate = 64, //CritRating but only for physical attacks. Increases chance of critting.
PhysicalCritEvasion = 65, //Opposite of CritRating. Reduces chance of being crit by phyiscal attacks
PhysicalCritAttack = 66, //Increases damage done by Physical Critical hits
PhysicalCritResilience = 67, //Decreases damage taken by Physical Critical hits
Parry = 68, //Increases chance to parry
MagicCritPotency = 69, //Increases
Regain = 70, //TP regen, should be -90 out of combat, Invigorate sets to 100+ depending on traits
RegenDown = 71, //Damage over time effects. Separate from normal Regen because of how they are displayed in game
Stoneskin = 72, //Nullifies damage
MinimumTpLock = 73, //Don't let TP fall below this, used in openings
KnockbackImmune = 74 //Immune to knockback effects when above 0
//Attack property resistances
SlashingResistance = 65, //Reduces damage taken by slashing attacks
PiercingResistance = 66, //Reduces damage taken by piercing attacks
BluntResistance = 67, //Reduces damage taken by blunt attacks
ProjectileResistance = 68, //Reduces damage taken by projectile attacks
SonicResistance = 69, //Reduces damage taken by sonic attacks
BreathResistance = 70, //Reduces damage taken by breath attacks
PhysicalResistance = 71, //Reduces damage taken by physical attacks
MagicResistance = 72, //Reduces damage taken by magic attacks
//Status resistances
SlowResistance = 73, //Reduces chance to be inflicted with slow by status magic
PetrificationResistance = 74, //Reduces chance to be inflicted with petrification by status magic
ParalysisResistance = 75, //Reduces chance to be inflicted with paralysis by status magic
SilenceResistance = 76, //Reduces chance to be inflicted with silence by status magic
BlindResistance = 77, //Reduces chance to be inflicted with blind by status magic
PoisonResistance = 78, //Reduces chance to be inflicted with poison by status magic
StunResistance = 79, //Reduces chance to be inflicted with stun by status magic
SleepResistance = 80, //Reduces chance to be inflicted with sleep by status magic
BindResistance = 81, //Reduces chance to be inflicted with bind by status magic
HeavyResistance = 82, //Reduces chance to be inflicted with heavy by status magic
DoomResistance = 83, //Reduces chance to be inflicted with doom by status magic
//84-101 didn't have names and seem to be unused
//Miscellaneous
ConserveMp = 101, //Chance to reduce mp used by actions
SpellInterruptResistance = 102, //Reduces chance to be interrupted by damage while casting
DoubleDownOdds = 103, //Increases double down odds
HqDiscoveryRate = 104,
//Non-gear mods
None = 105,
NAMEPLATE_SHOWN = 106,
TARGETABLE = 107,
NAMEPLATE_SHOWN2 = 108,
HpPercent = 109,
MpPercent = 110,
TpPercent = 111,
AttackRange = 112, //How far away in yalms this character can attack from (probably won't need this when auto attack skills are done)
Raise = 113,
MinimumHpLock = 114, //Stops HP from falling below this value
MinimumMpLock = 115, //Stops MP from falling below this value
MinimumTpLock = 116, //Stops TP from falling below this value
AttackType = 117, //Attack property of auto attacks (might not need this when auto attack skills are done, unsure)
CanBlock = 118, //Whether the character can block attacks. (For players this is only true when they have a shield)
HitCount = 119, //Amount of hits in an auto attack. Usually 1, 2 for h2h, 3 with spinning heel
//Flat percent increases to these rates. Might not need these?
RawEvadeRate = 120,
RawParryRate = 121,
RawBlockRate = 122,
RawResistRate = 123,
RawHitRate = 124,
RawCritRate = 125,
DamageTakenDown = 126, //Percent damage taken down
Regain = 127, //TP regen, should be -90 out of combat, Invigorate sets to 100+ depending on traits
RegenDown = 128, //Damage over time effects. Separate from normal Regen because of how they are displayed in game
Stoneskin = 129, //Nullifies damage
KnockbackImmune = 130, //Immune to knockback effects when above 0
Stealth = 131, //Not visisble when above 0
}
}

View File

@ -109,8 +109,8 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
public uint castTimeMs; //cast time in milliseconds
public uint recastTimeMs; //recast time in milliseconds
public uint maxRecastTimeSeconds; //maximum recast time in seconds
public ushort mpCost;
public ushort tpCost;
public short mpCost; //short in case these casts can have negative cost
public short tpCost; //short because there are certain cases where we want weaponskills to have negative costs (such as Feint)
public byte animationType;
public ushort effectAnimation;
public ushort modelAnimation;
@ -188,10 +188,10 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
return castTimeMs == 0;
}
//Checks whether the skill can be used on the given target
public bool IsValidMainTarget(Character user, Character target)
//Checks whether the skill can be used on the given targets, uses error to return specific text ids for errors
public bool IsValidMainTarget(Character user, Character target, CommandResult error = null)
{
targetFind = new TargetFind(user);
targetFind = new TargetFind(user, target);
if (aoeType == TargetFindAOEType.Box)
{
@ -204,6 +204,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
/*
worldMasterTextId
32511 Target does not exist
32512 cannot be performed on a KO'd target.
32513 can only be performed on a KO'd target.
32514 cannot be performed on yourself.
@ -211,117 +212,112 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
32516 cannot be performed on a friendly target.
32517 can only be performed on a friendly target.
32518 cannot be performed on an enemy.
32519 can only be performed on an enemy,
32556 unable to execute [weaponskill]. Conditions for use are not met.
32519 can only be performed on an enemy.
32547 That command cannot be performed on the current target.
32548 That command cannot be performed on a party member
*/
// cant target dead
if ((mainTarget & (ValidTarget.Corpse | ValidTarget.CorpseOnly)) == 0 && target.IsDead())
if (target == null)
{
// cannot be perfomed on
if (user is Player)
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32512, 0x20, (uint)id);
error?.SetTextId(32511);
return false;
}
//level too high
if (level > user.GetLevel())
//This skill can't be used on a corpse and target is dead
if ((mainTarget & ValidTarget.Corpse) == 0 && target.IsDead())
{
if (user is Player)
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32527, 0x20, (uint)id);
//return false;
}
//Proc requirement
if (procRequirement != BattleCommandProcRequirement.None && !user.charaWork.battleTemp.timingCommandFlag[(int) procRequirement - 1])
{
if (user is Player)
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32556, 0x20, (uint)id);
error?.SetTextId(32512);
return false;
}
//costs too much tp
if (CalculateTpCost(user) > user.GetTP())
//This skill must be used on a corpse and target is alive
if ((mainTarget & ValidTarget.CorpseOnly) != 0 && target.IsAlive())
{
if (user is Player)
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32546, 0x20, (uint)id);
error?.SetTextId(32513);
return false;
}
// todo: calculate cost based on modifiers also (probably in BattleUtils)
if (BattleUtils.CalculateSpellCost(user, target, this) > user.GetMP())
//This skill can't be used on self and target is self
if ((mainTarget & ValidTarget.Self) == 0 && target == user)
{
if (user is Player)
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32545, 0x20, (uint)id);
error?.SetTextId(32514);
return false;
}
// todo: check target requirements
if (requirements != BattleCommandRequirements.None)
//This skill must be used on self and target isn't self
if ((mainTarget & ValidTarget.SelfOnly) != 0 && target != user)
{
if (false)
{
// Unable to execute [@SHEET(xtx/command,$E8(1),2)]. Conditions for use are not met.
if (user is Player)
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32556, 0x20, (uint)id);
return false;
}
error?.SetTextId(32515);
return false;
}
// todo: i dont care to message for each scenario, just the most common ones..
if ((mainTarget & ValidTarget.CorpseOnly) != 0)
//This skill can't be used on an ally and target is an ally
if ((mainTarget & ValidTarget.Ally) == 0 && target.allegiance == user.allegiance)
{
if (target != null && target.IsAlive())
{
if (user is Player)
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32513, 0x20, (uint)id);
return false;
}
error?.SetTextId(32516);
return false;
}
if ((mainTarget & ValidTarget.Enemy) != 0)
//This skill must be used on an ally and target is not an ally
if ((mainTarget & ValidTarget.AllyOnly) != 0 && target.allegiance != user.allegiance)
{
if (target == user || target != null &&
user.allegiance == target.allegiance)
{
if (user is Player)
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32519, 0x20, (uint)id);
return false;
}
error?.SetTextId(32517);
return false;
}
if ((mainTarget & ValidTarget.Ally) != 0)
//This skill can't be used on an enemu and target is an enemy
if ((mainTarget & ValidTarget.Enemy) == 0 && target.allegiance != user.allegiance)
{
if (target == null || target.allegiance != user.allegiance)
{
if (user is Player)
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32517, 0x20, (uint)id);
return false;
}
error?.SetTextId(32518);
return false;
}
if ((mainTarget & ValidTarget.PartyMember) != 0)
//This skill must be used on an enemy and target is an ally
if ((mainTarget & ValidTarget.EnemyOnly) != 0 && target.allegiance == user.allegiance)
{
if (target == null || target.currentParty != user.currentParty)
{
if (user is Player)
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32547, 0x20, (uint)id);
return false;
}
error?.SetTextId(32519);
return false;
}
if ((mainTarget & ValidTarget.Player) != 0)
//This skill can't be used on party members and target is a party member
if ((mainTarget & ValidTarget.Party) == 0 && target.currentParty == user.currentParty)
{
if (!(target is Player))
{
if (user is Player)
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32517, 0x20, (uint)id);
return false;
}
error?.SetTextId(32548);
return false;
}
return true;// targetFind.CanTarget(target, true, true, true); //this will be done later
//This skill must be used on party members and target is not a party member
if ((mainTarget & ValidTarget.PartyOnly) != 0 && target.currentParty != user.currentParty)
{
error?.SetTextId(32547);
return false;
}
//This skill can't be used on NPCs and target is an npc
if ((mainTarget & ValidTarget.NPC) == 0 && target.isStatic)
{
error?.SetTextId(32547);
return false;
}
//This skill must be used on NPCs and target is not an npc
if ((mainTarget & ValidTarget.NPCOnly) != 0 && !target.isStatic)
{
error?.SetTextId(32547);
return false;
}
// todo: why is player always zoning?
// cant target if zoning
if (target is Player && ((Player)target).playerSession.isUpdatesLocked)
{
user.aiContainer.ChangeTarget(null);
return false;
}
if (target.zone != user.zone)
return false;
return true;
}
public ushort CalculateMpCost(Character user)
@ -363,15 +359,15 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
//Calculate TP cost taking into considerating the combo bonus rate for players
//Should this set tpCost or should it be called like CalculateMp where it gets calculated each time?
//Might cause issues with the delay between starting and finishing a WS
public ushort CalculateTpCost(Character user)
public short CalculateTpCost(Character user)
{
ushort tp = tpCost;
short tp = tpCost;
//Calculate tp cost
if (user is Player)
{
var player = user as Player;
if (player.playerWork.comboNextCommandId[0] == id || player.playerWork.comboNextCommandId[1] == id)
tp = (ushort)Math.Ceiling((float)tpCost * (1 - player.playerWork.comboCostBonusRate));
tp = (short)Math.Ceiling((float)tpCost * (1 - player.playerWork.comboCostBonusRate));
}
return tp;
@ -386,5 +382,10 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
{
return (ushort) commandType;
}
public ushort GetActionType()
{
return (ushort) actionType;
}
}
}

View File

@ -320,28 +320,28 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
// custom effects here
// status for having procs fall off
EvadeProc = 253003,
BlockProc = 253004,
ParryProc = 253005,
MissProc = 253006,
EXPChain = 253007
EvadeProc = 300000,
BlockProc = 300001,
ParryProc = 300002,
MissProc = 300003,
EXPChain = 300004
}
[Flags]
enum StatusEffectFlags : uint
{
None = 0,
Silent = 1 << 0, // dont display effect loss message
//Loss flags
LoseOnDeath = 1 << 1, // effects removed on death
LoseOnZoning = 1 << 2, // effects removed on zoning
LoseOnEsuna = 1 << 3, // effects which can be removed with esuna (debuffs)
LoseOnDispel = 1 << 4, // some buffs which player might be able to dispel from mob
LoseOnLogout = 1 << 5, // effects removed on logging out
LoseOnAttacking = 1 << 6, // effects removed when owner attacks another entity
LoseOnCastStart = 1 << 7, // effects removed when owner starts casting
LoseOnAggro = 1 << 8, // effects removed when owner gains enmity (swiftsong)
//Loss flags - Do we need loseonattacking/caststart? Could just be done with activate flags
LoseOnDeath = 1 << 0, // effects removed on death
LoseOnZoning = 1 << 1, // effects removed on zoning
LoseOnEsuna = 1 << 2, // effects which can be removed with esuna (debuffs)
LoseOnDispel = 1 << 3, // some buffs which player might be able to dispel from mob
LoseOnLogout = 1 << 4, // effects removed on logging out
LoseOnAttacking = 1 << 5, // effects removed when owner attacks another entity
LoseOnCastStart = 1 << 6, // effects removed when owner starts casting
LoseOnAggro = 1 << 7, // effects removed when owner gains enmity (swiftsong)
LoseOnClassChange = 1 << 8, //Effect falls off whhen changing class
//Activate flags
ActivateOnCastStart = 1 << 9, //Activates when a cast starts.
@ -368,9 +368,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
PreventMovement = 1 << 26, // effects which prevent movement such as bind, still allows turning in place
PreventTurn = 1 << 27, // effects which prevent turning, such as stun
PreventUntarget = 1 << 28, // effects which prevent changing targets, such as fixation
Stealth = 1 << 29, // sneak/invis
Stance = 1 << 30, // effects that do not have a timer
Stance = 1 << 29 // effects that do not have a timer
}
enum StatusEffectOverwrite : byte
@ -387,19 +385,22 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
private Character owner;
private Character source;
private StatusEffectId id;
private string name; // name of this effect
private DateTime startTime; // when was this effect added
private DateTime endTime; // when this status falls off
private DateTime lastTick; // when did this effect last tick
private uint duration; // how long should this effect last in seconds
private uint tickMs; // how often should this effect proc
private double magnitude; // a value specified by scripter which is guaranteed to be used by all effects
private byte tier; // same effect with higher tier overwrites this
private double extra; // optional value
private StatusEffectFlags flags; // death/erase/dispel etc
private StatusEffectOverwrite overwrite; // how to handle adding an effect with same id (see StatusEfectOverwrite)
private bool silent = false; // do i send a message on losing effect
private bool hidden = false;
private string name; // name of this effect
private DateTime startTime; // when was this effect added
private DateTime endTime; // when this status falls off
private DateTime lastTick; // when did this effect last tick
private uint duration; // how long should this effect last in seconds
private uint tickMs; // how often should this effect proc
private double magnitude; // a value specified by scripter which is guaranteed to be used by all effects
private byte tier; // same effect with higher tier overwrites this
private double extra; // optional value
private StatusEffectFlags flags; // death/erase/dispel etc
private StatusEffectOverwrite overwrite; // how to handle adding an effect with same id (see StatusEfectOverwrite)
private bool silentOnGain = false; //Whether a message is sent when the status is gained
private bool silentOnLoss = false; //Whether a message is sent when the status is lost
private bool hidden = false; //Whether this status is shown. Used for things that aren't really status effects like exp chains and procs
private ushort statusGainTextId = 30328; //The text id used when the status is gained. 30328: [Command] grants you the effect of [status] (Used for buffs)
private ushort statusLossTextId = 30331; //The text id used when the status effect falls off when its time runs out. 30331: You are no longer under the effect of [status] (Used for buffs)
public LuaScript script;
HitEffect animationEffect;
@ -433,26 +434,36 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
this.name = effect.name;
this.flags = effect.flags;
this.overwrite = effect.overwrite;
this.statusGainTextId = effect.statusGainTextId;
this.statusLossTextId = effect.statusLossTextId;
this.extra = effect.extra;
this.script = effect.script;
this.silentOnGain = effect.silentOnGain;
this.silentOnLoss = effect.silentOnLoss;
this.hidden = effect.hidden;
}
public StatusEffect(uint id, string name, uint flags, uint overwrite, uint tickMs)
public StatusEffect(uint id, string name, uint flags, uint overwrite, uint tickMs, bool hidden, bool silentOnGain, bool silentOnLoss, ushort statusGainTextId, ushort statusLossTextId)
{
this.id = (StatusEffectId)id;
this.name = name;
this.flags = (StatusEffectFlags)flags;
this.overwrite = (StatusEffectOverwrite)overwrite;
this.tickMs = tickMs;
this.hidden = hidden;
this.silentOnGain = silentOnGain;
this.silentOnLoss = silentOnLoss;
this.statusGainTextId = statusGainTextId;
this.statusLossTextId = statusLossTextId;
}
// return true when duration has elapsed
public bool Update(DateTime tick)
public bool Update(DateTime tick, CommandResultContainer resultContainer = null)
{
if (tickMs != 0 && (tick - lastTick).TotalMilliseconds >= tickMs)
{
lastTick = tick;
if (LuaEngine.CallLuaStatusEffectFunction(this.owner, this, "onTick", this.owner, this) > 0)
if (LuaEngine.CallLuaStatusEffectFunction(this.owner, this, "onTick", this.owner, this, resultContainer) > 0)
return true;
}
@ -548,9 +559,14 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
return (byte)overwrite;
}
public bool GetSilent()
public bool GetSilentOnGain()
{
return silent;
return silentOnGain;
}
public bool GetSilentOnLoss()
{
return silentOnLoss;
}
public bool GetHidden()
@ -558,6 +574,16 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
return hidden;
}
public ushort GetStatusGainTextId()
{
return statusGainTextId;
}
public ushort GetStatusLossTextId()
{
return statusLossTextId;
}
public void SetStartTime(DateTime time)
{
this.startTime = time;
@ -566,7 +592,15 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
public void SetEndTime(DateTime time)
{
endTime = time;
//If it's a stance, just set endtime to highest number possible for XIV
if ((flags & StatusEffectFlags.Stance) != 0)
{
endTime = Utils.UnixTimeStampToDateTime(4294967295);
}
else
{
endTime = time;
}
}
//Refresh the status, updating the end time based on the duration of the status and broadcasts the new time
@ -629,9 +663,14 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
this.overwrite = (StatusEffectOverwrite)overwrite;
}
public void SetSilent(bool silent)
public void SetSilentOnGain(bool silent)
{
this.silent = silent;
this.silentOnGain = silent;
}
public void SetSilentOnLoss(bool silent)
{
this.silentOnLoss = silent;
}
public void SetHidden(bool hidden)
@ -639,6 +678,17 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
this.hidden = hidden;
}
public void SetStatusGainTextId(ushort textId)
{
this.statusGainTextId = textId;
}
public void SetStatusLossTextId(ushort textId)
{
this.statusLossTextId = textId;
}
public void SetAnimation(uint hitEffect)
{
animationEffect = (HitEffect)hitEffect;

View File

@ -30,39 +30,41 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
public void Update(DateTime tick)
{
CommandResultContainer resultContainer = new CommandResultContainer();
//Regen/Refresh/Regain effects tick every 3 seconds
if ((DateTime.Now - lastTick).Seconds >= 3)
{
RegenTick(tick);
RegenTick(tick, resultContainer);
lastTick = DateTime.Now;
}
// list of effects to remove
// if (owner is Player) UpdateTimeAtIndex(4, 4294967295);
// list of effects to remove
var removeEffects = new List<StatusEffect>();
for (int i = 0; i < effects.Values.Count; i++)
var effectsList = effects.Values;
for (int i = effectsList.Count - 1; i >= 0; i--)
{
// effect's update function returns true if effect has completed
if (effects.Values.ElementAt(i).Update(tick))
removeEffects.Add(effects.Values.ElementAt(i));
if (effectsList.ElementAt(i).Update(tick, resultContainer))
removeEffects.Add(effectsList.ElementAt(i));
}
// remove effects from this list
foreach (var effect in removeEffects)
{
RemoveStatusEffect(effect);
RemoveStatusEffect(effect, resultContainer, effect.GetStatusLossTextId());
}
if (sendUpdate)
resultContainer.CombineLists();
if (resultContainer.GetList().Count > 0)
{
owner.zone.BroadcastPacketsAroundActor(owner, owner.GetActorStatusPackets());
sendUpdate = false;
owner.DoBattleAction(0, 0x7c000062, resultContainer.GetList());
}
}
//regen/refresh/regain
public void RegenTick(DateTime tick)
public void RegenTick(DateTime tick, CommandResultContainer resultContainer)
{
ushort dotTick = (ushort) owner.GetMod(Modifier.RegenDown);
ushort regenTick = (ushort) owner.GetMod(Modifier.Regen);
@ -72,18 +74,24 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
//DoTs tick before regen and the full dot damage is displayed, even if some or all of it is nullified by regen. Only effects like stoneskin actually alter the number shown
if (dotTick > 0)
{
CommandResult action = new CommandResult(owner.actorId, 30331, (uint)(HitEffect.HitEffectType | HitEffect.Hit), dotTick);
//Unsure why 10105 is the textId used
//Also unsure why magicshield is used
CommandResult action = new CommandResult(owner.actorId, 10105, (uint)(HitEffect.MagicEffectType | HitEffect.MagicShield | HitEffect.NoResist), dotTick);
utils.BattleUtils.HandleStoneskin(owner, action);
// todo: figure out how to make red numbers appear for enemies getting hurt by dots
//owner.DelHP(action.amount);
utils.BattleUtils.DamageTarget(owner, owner, action, null);
owner.DoBattleAction(0, 0, action);
resultContainer.AddAction(action);
owner.DelHP(action.amount, resultContainer);
}
//DoTs are the only effect to show numbers, so that doesnt need to be handled for these
owner.AddHP(regenTick);
owner.AddMP(refreshtick);
owner.AddTP(regainTick);
if (regenTick != 0)
owner.AddHP(regenTick);
if (refreshtick != 0)
owner.AddMP(refreshtick);
if (regainTick != 0)
owner.AddTP(regainTick);
}
public bool HasStatusEffect(uint id)
@ -96,55 +104,59 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
return effects.ContainsKey((uint)id);
}
public CommandResult AddStatusForCommandResult(uint id, byte tier = 1, ulong magnitude = 0, uint duration = 0)
{
CommandResult action = null;
if (AddStatusEffect(id, tier, magnitude, duration))
action = new CommandResult(owner.actorId, 30328, id | (uint)HitEffect.StatusEffectType);
return action;
}
public bool AddStatusEffect(uint id)
public bool AddStatusEffect(uint id, CommandResultContainer actionContainer = null, ushort worldmasterTextId = 30328)
{
var se = Server.GetWorldManager().GetStatusEffect(id);
return AddStatusEffect(se, owner);
if (se != null)
{
worldmasterTextId = se.GetStatusGainTextId();
}
return AddStatusEffect(se, owner, actionContainer, worldmasterTextId);
}
public bool AddStatusEffect(uint id, byte tier)
public bool AddStatusEffect(uint id, byte tier, CommandResultContainer actionContainer = null, ushort worldmasterTextId = 30328)
{
var se = Server.GetWorldManager().GetStatusEffect(id);
se.SetTier(tier);
if (se != null)
{
se.SetTier(tier);
worldmasterTextId = se.GetStatusGainTextId();
}
return AddStatusEffect(se, owner);
return AddStatusEffect(se, owner, actionContainer, worldmasterTextId);
}
public bool AddStatusEffect(uint id, byte tier, double magnitude)
public bool AddStatusEffect(uint id, byte tier, double magnitude, CommandResultContainer actionContainer = null, ushort worldmasterTextId = 30328)
{
var se = Server.GetWorldManager().GetStatusEffect(id);
se.SetMagnitude(magnitude);
se.SetTier(tier);
if (se != null)
{
se.SetMagnitude(magnitude);
se.SetTier(tier);
worldmasterTextId = se.GetStatusGainTextId();
}
return AddStatusEffect(se, owner);
return AddStatusEffect(se, owner, actionContainer, worldmasterTextId);
}
public bool AddStatusEffect(uint id, byte tier, double magnitude, uint duration, int tickMs = 3000)
public bool AddStatusEffect(uint id, byte tier, double magnitude, uint duration, int tickMs, CommandResultContainer actionContainer = null, ushort worldmasterTextId = 30328)
{
var se = Server.GetWorldManager().GetStatusEffect(id);
if (se != null)
{
se.SetDuration(duration);
se.SetStartTime(DateTime.Now);
se.SetOwner(owner);
worldmasterTextId = se.GetStatusGainTextId();
}
return AddStatusEffect(se ?? new StatusEffect(this.owner, id, magnitude, 3000, duration, tier), owner);
return AddStatusEffect(se ?? new StatusEffect(this.owner, id, magnitude, 3000, duration, tier), owner, actionContainer, worldmasterTextId);
}
public bool AddStatusEffect(StatusEffect newEffect, Character source, bool silent = false, bool hidden = false)
public bool AddStatusEffect(StatusEffect newEffect, Character source, CommandResultContainer actionContainer = null, ushort worldmasterTextId = 30328)
{
/*
worldMasterTextId
@ -166,9 +178,10 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
if (canOverwrite || effect == null)
{
// send packet to client with effect added message
if (effect != null && (!silent || !effect.GetSilent() || (effect.GetFlags() & (uint)StatusEffectFlags.Silent) == 0))
if (newEffect != null && !newEffect.GetSilentOnGain())
{
// todo: send packet to client with effect added message
if (actionContainer != null)
actionContainer.AddAction(new CommandResult(owner.actorId, worldmasterTextId, newEffect.GetStatusEffectId() | (uint)HitEffect.StatusEffectType));
}
// wont send a message about losing effect here
@ -181,13 +194,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
if (effects.Count < MAX_EFFECTS)
{
if(newEffect.script != null)
newEffect.CallLuaFunction(this.owner, "onGain", this.owner, newEffect);
else
LuaEngine.CallLuaStatusEffectFunction(this.owner, newEffect, "onGain", this.owner, newEffect);
newEffect.CallLuaFunction(this.owner, "onGain", this.owner, newEffect, actionContainer);
effects.Add(newEffect.GetStatusEffectId(), newEffect);
//newEffect.SetSilent(silent);
newEffect.SetHidden(hidden);
if (!newEffect.GetHidden())
{
@ -212,15 +221,19 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
return false;
}
public bool RemoveStatusEffect(StatusEffect effect, bool silent = false)
//playEffect determines whether the effect of the animation that's going to play with actionContainer is going to play on owner
//Generally, for abilities removing an effect, this is true and for effects removing themselves it's false.
public bool RemoveStatusEffect(StatusEffect effect, CommandResultContainer actionContainer = null, ushort worldmasterTextId = 30331, bool playEffect = true)
{
bool removedEffect = false;
if (effect != null && effects.ContainsKey(effect.GetStatusEffectId()))
{
// send packet to client with effect remove message
if (!silent && !effect.GetSilent() && (effect.GetFlags() & (uint)StatusEffectFlags.Silent) == 0)
if (!effect.GetSilentOnLoss())
{
owner.DoBattleAction(0, 0, new CommandResult(owner.actorId, 30331, effect.GetStatusEffectId()));
//Only send a message if we're using an actioncontainer and the effect normally sends a message when it's lost
if (actionContainer != null)
actionContainer.AddAction(new CommandResult(owner.actorId, worldmasterTextId, effect.GetStatusEffectId() | (playEffect ? 0 : (uint)HitEffect.StatusLossType)));
}
//hidden effects not in charawork
@ -230,54 +243,20 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
SetStatusAtIndex(index, 0);
SetTimeAtIndex(index, 0);
}
// function onLose(actor, effect)
effects.Remove(effect.GetStatusEffectId());
if(effect.script != null)
effect.CallLuaFunction(owner, "onLose", owner, effect);
else
LuaEngine.CallLuaStatusEffectFunction(this.owner, effect, "onLose", this.owner, effect);
effect.CallLuaFunction(owner, "onLose", owner, effect, actionContainer);
owner.RecalculateStats();
sendUpdate = true;
removedEffect = true;
}
return removedEffect;
}
public bool RemoveStatusEffect(uint effectId, bool silent = false)
public bool RemoveStatusEffect(uint effectId, CommandResultContainer resultContainer = null, ushort worldmasterTextId = 30331, bool playEffect = true)
{
bool removedEffect = false;
foreach (var effect in effects.Values)
{
if (effect.GetStatusEffectId() == effectId)
{
RemoveStatusEffect(effect, effect.GetSilent() || silent);
removedEffect = true;
break;
}
}
return removedEffect;
}
//Remove status effect and return the CommandResult message instead of sending it immediately
public CommandResult RemoveStatusEffectForCommandResult(uint effectId, ushort worldMasterTextId = 30331)
{
CommandResult action = null;
if (RemoveStatusEffect(effectId, true))
action = new CommandResult(owner.actorId, worldMasterTextId, effectId);
return action;
}
//Remove status effect and return the CommandResult message instead of sending it immediately
public CommandResult RemoveStatusEffectForCommandResult(StatusEffect effect, ushort worldMasterTextId = 30331)
{
CommandResult action = null;
if (RemoveStatusEffect(effect, true))
action = new CommandResult(owner.actorId, worldMasterTextId, effect.GetStatusEffectId());
return action;
return RemoveStatusEffect(GetStatusEffectById(effectId), resultContainer, worldmasterTextId, playEffect);
}
public StatusEffect CopyEffect(StatusEffect effect)
@ -288,14 +267,14 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
return AddStatusEffect(newEffect, effect.GetSource()) ? newEffect : null;
}
public bool RemoveStatusEffectsByFlags(uint flags, bool silent = false)
public bool RemoveStatusEffectsByFlags(uint flags, CommandResultContainer resultContainer = null)
{
// build list of effects to remove
var removeEffects = GetStatusEffectsByFlag(flags);
// remove effects from main list
foreach (var effect in removeEffects)
RemoveStatusEffect(effect, silent);
RemoveStatusEffect(effect, resultContainer, effect.GetStatusLossTextId(), true);
// removed an effect with one of these flags
return removeEffects.Count > 0;
@ -321,6 +300,16 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
return list;
}
public StatusEffect GetRandomEffectByFlag(uint flag)
{
var list = GetStatusEffectsByFlag(flag);
if (list.Count > 0)
return list[Program.Random.Next(list.Count)];
return null;
}
// todo: why the fuck cant c# convert enums/
public bool HasStatusEffectsByFlag(StatusEffectFlags flags)
{
@ -429,7 +418,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
SetStatusAtIndex(index, (ushort) (newEffectId - 200000));
SetTimeAtIndex(index, time);
return new CommandResult(owner.actorId, 30328, (uint) HitEffect.StatusEffectType | newEffectId);
return new CommandResult(owner.actorId, 30330, (uint) HitEffect.StatusEffectType | newEffectId);
}
}
}

View File

@ -210,7 +210,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
Disengage();
return;
}
owner.SetMod((uint)Modifier.Speed, 5);
owner.SetMod((uint)Modifier.MovementSpeed, 5);
if ((tick - lastCombatTickScript).TotalSeconds > 3)
{
Move();
@ -223,7 +223,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
protected virtual void Move()
{
if (!owner.aiContainer.CanFollowPath())
if (!owner.aiContainer.CanFollowPath() || owner.statusEffects.HasStatusEffectsByFlag(StatusEffectFlags.PreventMovement))
{
return;
}
@ -363,7 +363,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
// todo: seems ffxiv doesnt even differentiate between sneak/invis?
{
hasSneak = target.statusEffects.HasStatusEffectsByFlag((uint)StatusEffectFlags.Stealth);
hasSneak = target.GetMod(Modifier.Stealth) > 0;
hasInvisible = hasSneak;
}

View File

@ -10,21 +10,28 @@ using FFXIVClassic_Map_Server.actors.group;
namespace FFXIVClassic_Map_Server.actors.chara.ai
{
// https://github.com/Windower/POLUtils/blob/master/PlayOnline.FFXI/Enums.cs
[Flags]
public enum ValidTarget : ushort
{
None = 0x00,
Self = 0x01,
Player = 0x02,
PartyMember = 0x04,
Ally = 0x08,
NPC = 0x10,
Enemy = 0x20,
Unknown = 0x40,
Object = 0x60,
CorpseOnly = 0x80,
Corpse = 0x9D // CorpseOnly + NPC + Ally + Partymember + Self
Self = 0x01, //Can be used on self (if this flag isn't set and target is self, return false)
SelfOnly = 0x02, //Must be used on self (if this flag is set and target isn't self, return false)
Party = 0x4, //Can be used on party members
PartyOnly = 0x8, //Must be used on party members
Ally = 0x10, //Can be used on allies
AllyOnly = 0x20, //Must be used on allies
NPC = 0x40, //Can be used on static NPCs
NPCOnly = 0x80, //Must be used on static NPCs
Enemy = 0x100, //Can be used on enemies
EnemyOnly = 0x200, //Must be used on enemies
Object = 0x400, //Can be used on objects
ObjectOnly = 0x800, //Must be used on objects
Corpse = 0x1000, //Can be used on corpses
CorpseOnly = 0x2000, //Must be used on corpses
//These are only used for ValidTarget, not MainTarget
MainTargetParty = 0x4000, //Can be used on main target's party (This will basically always be true.)
MainTargetPartyOnly = 0x8000, //Must be used on main target's party (This is for Protect basically.)
}
/// <summary> Targeting from/to different entity types </summary>
@ -66,12 +73,13 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
class TargetFind
{
private Character owner;
private Character masterTarget; // if target is a pet, this is the owner
private Character mainTarget; //This is the target that the skill is being used on
private Character masterTarget; //If mainTarget is a pet, this is the owner
private TargetFindCharacterType findType;
private ValidTarget validTarget;
private TargetFindAOETarget aoeTarget;
private TargetFindAOEType aoeType;
private Vector3 aoeTargetPosition; //This is the center of circle an cone AOEs and the position where line aoes come out
private Vector3 aoeTargetPosition; //This is the center of circle of cone AOEs and the position where line aoes come out. If we have mainTarget this might not be needed?
private float aoeTargetRotation; //This is the direction the aoe target is facing
private float maxDistance; //Radius for circle and cone AOEs, length for line AOEs
private float minDistance; //Minimum distance to that target must be to be able to be hit
@ -82,14 +90,16 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
private float param;
private List<Character> targets;
public TargetFind(Character owner)
public TargetFind(Character owner, Character mainTarget = null)
{
this.owner = owner;
Reset();
this.owner = owner;
this.mainTarget = mainTarget == null ? owner : mainTarget;
}
public void Reset()
{
this.mainTarget = owner;
this.findType = TargetFindCharacterType.None;
this.validTarget = ValidTarget.Enemy;
this.aoeType = TargetFindAOEType.None;
@ -201,11 +211,6 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
//if (targets.Count > 8)
//targets.RemoveRange(8, targets.Count - 8);
//Curaga starts with lowest health players, so the targets are definitely sorted at least for some abilities
//Other aoe abilities might be sorted by distance?
//Protect is random
targets.Sort(delegate (Character a, Character b) { return a.GetHP().CompareTo(b.GetHP()); });
}
/// <summary>
@ -323,41 +328,57 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
if (target == null || !retarget && targets.Contains(target))
return false;
//This skill can't be used on self and target is self, return false
if ((validTarget & ValidTarget.Self) == 0 && target == owner)
if (target == null)
return false;
//This skill can't be used on NPCs and target is an NPC, return false
if ((validTarget & ValidTarget.NPC) == 0 && target.isStatic)
return false;
//This skill can't be used on corpses and target is dead, return false
//This skill can't be used on a corpse and target is dead
if ((validTarget & ValidTarget.Corpse) == 0 && target.IsDead())
return false;
//This skill must be used on Allies and target is not an ally, return false
if ((validTarget & ValidTarget.Ally) != 0 && target.allegiance != owner.allegiance)
return false;
//This skill can't be used on players and target is a player, return false
//Do we need a player flag? Ally/Enemy flags probably serve the same purpose
//if ((validTarget & ValidTarget.Player) == 0 && target is Player)
//return false;
//This skill must be used on enemies an target is not an enemy
if ((validTarget & ValidTarget.Enemy) != 0 && target.allegiance == owner.allegiance)
return false;
//This skill must be used on a party member and target is not in owner's party, return false
if ((validTarget & ValidTarget.PartyMember) != 0 && target.currentParty != owner.currentParty)
return false;
//This skill must be used on a corpse and target is alive, return false
//This skill must be used on a corpse and target is alive
if ((validTarget & ValidTarget.CorpseOnly) != 0 && target.IsAlive())
return false;
//This skill can't be used on self and target is self
if ((validTarget & ValidTarget.Self) == 0 && target == owner)
return false;
//This skill must be used on self and target isn't self
if ((validTarget & ValidTarget.SelfOnly) != 0 && target != owner)
return false;
//This skill can't be used on an ally and target is an ally
if ((validTarget & ValidTarget.Ally) == 0 && target.allegiance == owner.allegiance)
return false;
//This skill must be used on an ally and target is not an ally
if ((validTarget & ValidTarget.AllyOnly) != 0 && target.allegiance != owner.allegiance)
return false;
//This skill can't be used on an enemu and target is an enemy
if ((validTarget & ValidTarget.Enemy) == 0 && target.allegiance != owner.allegiance)
return false;
//This skill must be used on an enemy and target is an ally
if ((validTarget & ValidTarget.EnemyOnly) != 0 && target.allegiance == owner.allegiance)
return false;
//This skill can't be used on party members and target is a party member
if ((validTarget & ValidTarget.Party) == 0 && target.currentParty == owner.currentParty)
return false;
//This skill must be used on party members and target is not a party member
if ((validTarget & ValidTarget.PartyOnly) != 0 && target.currentParty != owner.currentParty)
return false;
//This skill can't be used on NPCs and target is an npc
if ((validTarget & ValidTarget.NPC) == 0 && target.isStatic)
return false;
//This skill must be used on NPCs and target is not an npc
if ((validTarget & ValidTarget.NPCOnly) != 0 && !target.isStatic)
return false;
// todo: why is player always zoning?
// cant target if zoning
if (target is Player && ((Player)target).playerSession.isUpdatesLocked)
@ -372,6 +393,15 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
if (validTarget == ValidTarget.Self && aoeType == TargetFindAOEType.None && owner != target)
return false;
//This skill can't be used on main target's party members and target is a party member of main target
if ((validTarget & ValidTarget.MainTargetParty) == 0 && target.currentParty == mainTarget.currentParty)
return false;
//This skill must be used on main target's party members and target is not a party member of main target
if ((validTarget & ValidTarget.MainTargetPartyOnly) != 0 && target.currentParty != mainTarget.currentParty)
return false;
// this is fuckin retarded, think of a better way l8r
if (!ignoreAOE)
{

View File

@ -15,10 +15,11 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
{
this.startTime = DateTime.Now;
this.skill = Server.GetWorldManager().GetBattleCommand(skillId);
var returnCode = lua.LuaEngine.CallLuaBattleCommandFunction(owner, skill, "ability", "onAbilityPrepare", owner, target, skill);
var returnCode = skill.CallLuaFunction(owner, "onAbilityPrepare", owner, target, skill);
this.target = target != null ? target : owner;
this.target = (skill.mainTarget & ValidTarget.SelfOnly) != 0 ? owner : target;
errorResult = new CommandResult(owner.actorId, 32553, 0);
if (returnCode == 0)
{
OnStart();
@ -32,7 +33,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
public override void OnStart()
{
var returnCode = lua.LuaEngine.CallLuaBattleCommandFunction(owner, skill, "ability", "onAbilityStart", owner, target, skill);
var returnCode = skill.CallLuaFunction(owner, "onAbilityStart", owner, target, skill);
if (returnCode != 0)
{

View File

@ -100,26 +100,35 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
//List<BattleAction> actions = new List<BattleAction>();
CommandResultContainer actions = new CommandResultContainer();
var i = 0;
for (int hitNum = 0; hitNum < 1 /* owner.GetMod((uint) Modifier.HitCount)*/; hitNum++)
{
CommandResult action = new CommandResult(target.actorId, 0x765D, (uint)HitEffect.Hit, 100, (byte)HitDirection.None, (byte) hitNum);
action.commandType = CommandType.AutoAttack;
action.actionType = ActionType.Physical;
action.actionProperty = (ActionProperty) owner.GetMod(Modifier.AttackType);
// evasion, miss, dodge, etc to be handled in script, calling helpers from scripts/weaponskills.lua
// temporary evade/miss/etc function to test hit effects
action.DoAction(owner, target, null, actions);
}
// todo: this is fuckin stupid, probably only need *one* error packet, not an error for each action
CommandResult[] errors = (CommandResult[])actions.GetList().ToArray().Clone();
CommandResult error = null;// new BattleAction(0, null, 0, 0);
//owner.DoActions(null, actions.GetList(), ref error);
//owner.OnAttack(this, actions[0], ref errorResult);
var anim = (uint)(17 << 24 | 1 << 12);
owner.DoBattleAction(22104, anim, actions.GetList());
//This is all temporary until the skill sheet is finishd and the different auto attacks are added to the database
//Some mobs have multiple unique auto attacks that they switch between as well as ranged auto attacks, so we'll need a way to handle that
//For now, just use a temporary hardcoded BattleCommand that's the same for everyone.
BattleCommand attackCommand = new BattleCommand(22104, "Attack");
attackCommand.range = 5;
attackCommand.rangeHeight = 10;
attackCommand.worldMasterTextId = 0x765D;
attackCommand.mainTarget = (ValidTarget)768;
attackCommand.validTarget = (ValidTarget)17152;
attackCommand.commandType = CommandType.AutoAttack;
attackCommand.numHits = (byte)owner.GetMod(Modifier.HitCount);
attackCommand.basePotency = 100;
ActionProperty property = (owner.GetMod(Modifier.AttackType) != 0) ? (ActionProperty)owner.GetMod(Modifier.AttackType) : ActionProperty.Slashing;
attackCommand.actionProperty = property;
attackCommand.actionType = ActionType.Physical;
uint anim = (17 << 24 | 1 << 12);
if (owner is Player)
anim = (25 << 24 | 1 << 12);
attackCommand.battleAnimation = anim;
if (owner.CanUse(target, attackCommand))
{
attackCommand.targetFind.FindWithinArea(target, attackCommand.validTarget, attackCommand.aoeTarget);
owner.DoBattleCommand(attackCommand, "autoattack");
}
}
public override void TryInterrupt()

View File

@ -12,7 +12,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
{
owner.Disengage();
owner.ChangeState(SetActorStatePacket.MAIN_STATE_DEAD);
owner.statusEffects.RemoveStatusEffectsByFlags((uint)StatusEffectFlags.LoseOnDeath, true);
owner.statusEffects.RemoveStatusEffectsByFlags((uint)StatusEffectFlags.LoseOnDeath);
//var deathStatePacket = SetActorStatePacket.BuildPacket(owner.actorId, SetActorStatePacket.MAIN_STATE_DEAD2, owner.currentSubState);
//owner.zone.BroadcastPacketAroundActor(owner, deathStatePacket);
canInterrupt = false;

View File

@ -18,32 +18,28 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
this.startPos = owner.GetPosAsVector3();
this.startTime = DateTime.Now;
this.spell = Server.GetWorldManager().GetBattleCommand(spellId);
var returnCode = lua.LuaEngine.CallLuaBattleCommandFunction(owner, spell, "magic", "onMagicPrepare", owner, target, spell);
//Modify spell based on status effects. Need to do it here because they can modify cast times
List<StatusEffect> effects = owner.statusEffects.GetStatusEffectsByFlag((uint)(StatusEffectFlags.ActivateOnCastStart));
var returnCode = spell.CallLuaFunction(owner, "onMagicPrepare", owner, target, spell);
//modify skill based on status effects
//Do this here to allow buffs like Resonance to increase range before checking CanCast()
foreach (var effect in effects)
lua.LuaEngine.CallLuaStatusEffectFunction(owner, effect, "onMagicCast", owner, effect, spell);
owner.statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnCastStart, "onMagicCast", owner, spell);
this.target = target != null ? target : owner;
this.target = (spell.mainTarget & ValidTarget.SelfOnly) != 0 ? owner : target;
if (returnCode == 0 && owner.CanCast(this.target, spell))
errorResult = new CommandResult(owner.actorId, 32553, 0);
if (returnCode == 0 && owner.CanUse(this.target, spell, errorResult))
{
OnStart();
}
else
{
errorResult = new CommandResult(owner.actorId, 32553, 0);
interrupt = true;
}
}
public override void OnStart()
{
var returnCode = lua.LuaEngine.CallLuaBattleCommandFunction(owner, spell, "magic", "onMagicStart", owner, target, spell);
var returnCode = spell.CallLuaFunction(owner, "onMagicStart", owner, target, spell);
if (returnCode != 0)
{
@ -62,7 +58,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
Player p = (Player)owner;
if (spell.comboStep == 1 || ((p.playerWork.comboNextCommandId[0] == spell.id || p.playerWork.comboNextCommandId[1] == spell.id)))
{
lua.LuaEngine.CallLuaBattleCommandFunction(owner, spell, "magic", "onCombo", owner, target, spell);
spell.CallLuaFunction(owner, "onCombo", owner, target, spell);
spell.isCombo = true;
}
}
@ -168,7 +164,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
private bool CanCast()
{
return owner.CanCast(target, spell) && spell.IsValidMainTarget(owner, target) && !HasMoved();
return owner.CanUse(target, spell);
}
private bool HasMoved()

View File

@ -14,11 +14,14 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
base(owner, target)
{
this.startTime = DateTime.Now;
//this.target = skill.targetFind.
this.skill = Server.GetWorldManager().GetBattleCommand(skillId);
var returnCode = lua.LuaEngine.CallLuaBattleCommandFunction(owner, skill, "weaponskill", "onSkillPrepare", owner, target, skill);
if (returnCode == 0 && owner.CanWeaponSkill(target, skill))
var returnCode = skill.CallLuaFunction(owner, "onSkillPrepare", owner, target, skill);
this.target = (skill.mainTarget & ValidTarget.SelfOnly) != 0 ? owner : target;
errorResult = new CommandResult(owner.actorId, 32553, 0);
if (returnCode == 0 && owner.CanUse(this.target, skill, errorResult))
{
OnStart();
}
@ -31,7 +34,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
public override void OnStart()
{
var returnCode = lua.LuaEngine.CallLuaBattleCommandFunction(owner, skill, "weaponskill", "onSkillStart", owner, target, skill);
var returnCode = skill.CallLuaFunction(owner, "onSkillStart", owner, target, skill);
if (returnCode != 0)
{
@ -48,8 +51,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
{
//If there is a position bonus
if (skill.positionBonus != BattleCommandPositionBonus.None)
//lua.LuaEngine.CallLuaBattleCommandFunction(owner, skill, "weaponskill", "onPositional", owner, target, skill);
skill.CallLuaFunction(owner, "onPositional", owner, target, skill);
skill.CallLuaFunction(owner, "weaponskill", "onPositional", owner, target, skill);
//Combo stuff
if (owner is Player)
@ -61,7 +63,6 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
//If owner is a player and the skill being used is part of the current combo
if (p.playerWork.comboNextCommandId[0] == skill.id || p.playerWork.comboNextCommandId[1] == skill.id)
{
lua.LuaEngine.CallLuaBattleCommandFunction(owner, skill, "weaponskill", "onCombo", owner, target, skill);
skill.CallLuaFunction(owner, "onCombo", owner, target, skill);
skill.isCombo = true;
}
@ -161,7 +162,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
private bool CanUse()
{
return owner.CanWeaponSkill(target, skill) && skill.IsValidMainTarget(owner, target);
return owner.CanUse(target, skill);
}
public BattleCommand GetWeaponSkill()

View File

@ -12,37 +12,55 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
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.Evade, 30310 },
{ HitType.Parry, 30308 },
{ HitType.Block, 30306 },
{ HitType.Resist, 30310 }, //Resists seem to use the evade text id
{ HitType.Hit, 30301 },
{ 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>()
{
{ 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.Parry, 30448 }, //[Target] parries, 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.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.Evade, HitEffect.Evade },
{ HitType.Parry, HitEffect.Parry },
{ 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.Crit, HitEffect.Crit }
{ 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 }
};
public static Dictionary<KnockbackType, HitEffect> KnockbackEffects = new Dictionary<KnockbackType, HitEffect>()
@ -201,7 +219,8 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
//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)
{
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.amount = (ushort)(action.amount * percentResist);
@ -217,7 +236,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
defender.SubtractMod((uint)Modifier.Stoneskin, mitigation);
}
public static void DamageTarget(Character attacker, Character defender, CommandResult action, CommandResultContainer actionContainer= null)
public static void DamageTarget(Character attacker, Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer= null)
{
if (defender != null)
{
@ -230,9 +249,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
bnpc.lastAttacker = attacker;
}
defender.DelHP((short)action.amount);
attacker.OnDamageDealt(defender, action, actionContainer);
defender.OnDamageTaken(attacker, action, actionContainer);
defender.DelHP((short)action.amount, actionContainer);
attacker.OnDamageDealt(defender, skill, action, actionContainer);
defender.OnDamageTaken(attacker, skill, action, actionContainer);
// todo: other stuff too
if (defender is BattleNpc)
@ -248,13 +267,13 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
}
}
public static void HealTarget(Character caster, Character target, CommandResult action, CommandResultContainer actionContainer = null)
public static void HealTarget(Character caster, Character target, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
{
if (target != null)
{
target.AddHP(action.amount);
target.AddHP(action.amount, actionContainer);
target.statusEffects.CallLuaFunctionByFlag((uint) StatusEffectFlags.ActivateOnHealed, "onHealed", caster, target, action, actionContainer);
target.statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnHealed, "onHealed", caster, target, skill, action, actionContainer);
}
}
@ -279,7 +298,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
public static double GetParryRate(Character attacker, Character defender, BattleCommand skill, CommandResult action)
{
//Can't parry with shield, can't parry rear attacks
if (defender.GetMod((uint)Modifier.HasShield) != 0 || action.param == (byte) HitDirection.Rear)
if (defender.GetMod((uint)Modifier.CanBlock) != 0 || action.param == (byte) HitDirection.Rear)
return 0;
double parryRate = 10.0;
@ -324,7 +343,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
public static double GetBlockRate(Character attacker, Character defender, BattleCommand skill, CommandResult action)
{
//Shields are required to block and can't block from rear.
if (defender.GetMod((uint)Modifier.HasShield) == 0 || action.param == (byte)HitDirection.Rear)
if (defender.GetMod((uint)Modifier.CanBlock) == 0 || action.param == (byte)HitDirection.Rear)
return 0;
short dlvl = (short)(defender.GetLevel() - attacker.GetLevel());
@ -355,11 +374,26 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
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)
{
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);
return true;
}
@ -425,6 +459,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
FinishActionStatus(caster, target, skill, action, actionContainer);
break;
default:
action.effectId = (uint) HitEffect.AnimationEffectType;
actionContainer.AddAction(action);
break;
}
@ -450,7 +485,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.
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
//Needs to be done here because certain buff messages appear before it.
@ -473,23 +508,27 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
actionContainer.AddAction(action);
action.enmity = (ushort) (action.enmity * (skill != null ? skill.enmityModifier : 1));
//Damage the target
DamageTarget(attacker, defender, action, actionContainer);
DamageTarget(attacker, defender, skill, action, actionContainer);
}
public static void FinishActionSpell(Character attacker, Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
{
//I'm assuming that like physical attacks stoneskin is taken into account before mitigation
HandleStoneskin(defender, action);
//Determine the hit type of the action
if (!TryMiss(attacker, defender, skill, 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))
{
HandleStoneskin(defender, action);
if (!TryCrit(attacker, defender, skill, action))
if (!TryResist(attacker, defender, skill, action))
action.hitType = HitType.Hit;
action.hitType = HitType.Hit;
}
//There are no multi-hit spells
action.worldMasterTextId = SingleHitTypeTextIds[action.hitType];
//There are no multi-hit spells, so we don't need to take that into account
action.worldMasterTextId = MagicalHitTypeTextIds[action.hitType];
//Set the hit effect
SetHitEffectSpell(attacker, defender, skill, action);
@ -500,7 +539,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
actionContainer.AddAction(action);
DamageTarget(attacker, defender, action, actionContainer);
DamageTarget(attacker, defender, skill, action, actionContainer);
}
public static void FinishActionHeal(Character attacker, Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
@ -510,7 +549,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
actionContainer.AddAction(action);
HealTarget(attacker, defender, action, actionContainer);
HealTarget(attacker, defender, skill, action, actionContainer);
}
public static void FinishActionStatus(Character attacker, Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
@ -542,10 +581,10 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
hitEffect |= HitEffect.RecoilLv3;
}
hitEffect |= HitTypeEffects[hitType];
hitEffect |= HitTypeEffectsPhysical[hitType];
//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);
skill.comboEffectAdded = true;
@ -555,7 +594,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
if (hitType >= HitType.Parry)
{
//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)
hitEffect |= HitEffect.Protect;
@ -572,20 +611,8 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
var hitEffect = HitEffect.MagicEffectType;
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)
{
@ -594,16 +621,15 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
}
//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.
//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 (action != null)
hitEffect |= HitEffect.Shell;
if (defender.statusEffects.HasStatusEffect(StatusEffectId.Stoneskin))
if (action != null)
hitEffect |= HitEffect.Stoneskin;
hitEffect |= HitEffect.MagicShell;
}
action.effectId = (uint)hitEffect;
}
@ -654,7 +680,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
double rand = Program.Random.NextDouble();
//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);
//Because combos might change duration or tier
@ -670,7 +696,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
{
//If we need an extra action to show the status text
if (isAdditional)
results.AddAction(target.actorId, 30328, skill.statusId | (uint) HitEffect.StatusEffectType);
results.AddAction(target.actorId, effect.GetStatusGainTextId(), skill.statusId | (uint) HitEffect.StatusEffectType);
}
else
action.worldMasterTextId = 32002;//Is this right?
@ -733,7 +759,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
return 0;
int baseLevel = Math.Min(player.GetLevel(), mob.GetLevel());
ushort baseEXP = BASEEXP[baseLevel];
ushort baseEXP = BASEEXP[baseLevel - 1];
double dlvlModifier = 1.0;
@ -864,10 +890,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
totalBonus += GetChainBonus(expChainNumber);
StatusEffect newChain = Server.GetWorldManager().GetStatusEffect((uint)StatusEffectId.EXPChain);
newChain.SetSilent(true);
newChain.SetDuration(timeLimit);
newChain.SetTier((byte)(Math.Min(expChainNumber + 1, 255)));
attacker.statusEffects.AddStatusEffect(newChain, attacker, true, true);
attacker.statusEffects.AddStatusEffect(newChain, attacker);
actionContainer?.AddEXPActions(attacker.AddExp(baseExp, (byte)attacker.GetClass(), (byte)(totalBonus.Min(255))));
}

View File

@ -181,7 +181,7 @@ namespace FFXIVClassic_Map_Server.Actors
return true;
}
public override bool CanCast(Character target, BattleCommand spell)
public override bool CanUse(Character target, BattleCommand spell, CommandResult error = null)
{
// todo:
if (target == null)
@ -202,18 +202,6 @@ namespace FFXIVClassic_Map_Server.Actors
return true;
}
public override bool CanWeaponSkill(Character target, BattleCommand skill)
{
// todo:
return true;
}
public override bool CanUseAbility(Character target, BattleCommand ability)
{
// todo:
return false;
}
public uint GetDespawnTime()
{
return despawnTime;
@ -256,8 +244,6 @@ namespace FFXIVClassic_Map_Server.Actors
this.hateContainer.ClearHate();
zone.BroadcastPacketsAroundActor(this, GetSpawnPackets(null, 0x01));
zone.BroadcastPacketsAroundActor(this, GetInitPackets());
charaWork.parameterSave.hp = charaWork.parameterSave.hpMax;
charaWork.parameterSave.mp = charaWork.parameterSave.mpMax;
RecalculateStats();
OnSpawn();
@ -280,6 +266,7 @@ namespace FFXIVClassic_Map_Server.Actors
// <actor> defeat/defeats <target>
if (actionContainer != null)
actionContainer.AddEXPAction(new CommandResult(actorId, 30108, 0));
if (lastAttacker.currentParty != null && lastAttacker.currentParty is Party)
{
foreach (var memberId in ((Party)lastAttacker.currentParty).members)
@ -303,6 +290,7 @@ namespace FFXIVClassic_Map_Server.Actors
if (positionUpdates != null)
positionUpdates.Clear();
aiContainer.InternalDie(tick, despawnTime);
//this.ResetMoveSpeeds();
// todo: reset cooldowns
@ -446,11 +434,11 @@ namespace FFXIVClassic_Map_Server.Actors
mobModifiers.Add((MobModifier)mobModId, val);
}
public override void OnDamageTaken(Character attacker, CommandResult action, CommandResultContainer actionContainer = null)
public override void OnDamageTaken(Character attacker, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
{
if (GetMobMod((uint)MobModifier.DefendScript) != 0)
lua.LuaEngine.CallLuaBattleFunction(this, "onDamageTaken", this, attacker, action.amount);
base.OnDamageTaken(attacker, action, actionContainer);
base.OnDamageTaken(attacker, skill, action, actionContainer);
}
}
}

View File

@ -139,6 +139,8 @@ namespace FFXIVClassic_Map_Server.Actors
private List<Director> ownedDirectors = new List<Director>();
private Director loginInitDirector = null;
List<ushort> hotbarSlotsToUpdate = new List<ushort>();
public PlayerWork playerWork = new PlayerWork();
public Session playerSession;
@ -759,10 +761,11 @@ namespace FFXIVClassic_Map_Server.Actors
this.positionZ = destinationZ;
this.rotation = destinationRot;
this.statusEffects.RemoveStatusEffectsByFlags((uint)StatusEffectFlags.LoseOnZoning);
//Save Player
Database.SavePlayerPlayTime(this);
Database.SavePlayerPosition(this);
this.statusEffects.RemoveStatusEffectsByFlags((uint)StatusEffectFlags.LoseOnZoning, true);
Database.SavePlayerStatusEffects(this);
}
@ -1018,6 +1021,12 @@ namespace FFXIVClassic_Map_Server.Actors
//Check if bonus point available... set
//Remove buffs that fall off when changing class
CommandResultContainer resultContainer = new CommandResultContainer();
statusEffects.RemoveStatusEffectsByFlags((uint)StatusEffectFlags.LoseOnClassChange, resultContainer);
resultContainer.CombineLists();
DoBattleAction(0, 0x7c000062, resultContainer.GetList());
//Set rested EXP
charaWork.parameterSave.state_mainSkill[0] = classId;
charaWork.parameterSave.state_mainSkillLevel = charaWork.battleSave.skillLevel[classId-1];
@ -1956,7 +1965,14 @@ namespace FFXIVClassic_Map_Server.Actors
}
QueuePackets(propPacketUtil.Done());
}
if ((updateFlags & ActorUpdateFlags.Hotbar) != 0)
{
UpdateHotbar(hotbarSlotsToUpdate);
hotbarSlotsToUpdate.Clear();
updateFlags ^= ActorUpdateFlags.Hotbar;
}
@ -1972,12 +1988,11 @@ namespace FFXIVClassic_Map_Server.Actors
//Update commands and recast timers for the entire hotbar
public void UpdateHotbar()
{
List<ushort> slotsToUpdate = new List<ushort>();
for (ushort i = charaWork.commandBorder; i < charaWork.commandBorder + 30; i++)
{
slotsToUpdate.Add(i);
hotbarSlotsToUpdate.Add(i);
}
UpdateHotbar(slotsToUpdate);
updateFlags |= ActorUpdateFlags.Hotbar;
}
//Updates the hotbar and recast timers for only certain hotbar slots
@ -2049,7 +2064,6 @@ namespace FFXIVClassic_Map_Server.Actors
ushort lowHotbarSlot = (ushort)(hotbarSlot - charaWork.commandBorder);
ushort maxRecastTime = (ushort)(ability != null ? ability.maxRecastTimeSeconds : 5);
uint recastEnd = Utils.UnixTimeStampUTC() + maxRecastTime;
List<ushort> slotsToUpdate = new List<ushort>();
Database.EquipAbility(this, classId, (ushort) (hotbarSlot - charaWork.commandBorder), commandId, recastEnd);
//If the class we're equipping for is the current class (need to find out if state_mainSkill is supposed to change when you're a job)
@ -2061,8 +2075,8 @@ namespace FFXIVClassic_Map_Server.Actors
charaWork.parameterTemp.maxCommandRecastTime[lowHotbarSlot] = maxRecastTime;
charaWork.parameterSave.commandSlot_recastTime[lowHotbarSlot] = recastEnd;
slotsToUpdate.Add(hotbarSlot);
UpdateHotbar(slotsToUpdate);
hotbarSlotsToUpdate.Add(hotbarSlot);
updateFlags |= ActorUpdateFlags.Hotbar;
}
@ -2098,25 +2112,23 @@ namespace FFXIVClassic_Map_Server.Actors
Database.EquipAbility(this, GetCurrentClassOrJob(), (ushort)(lowHotbarSlot2), 0xA0F00000 ^ charaWork.command[hotbarSlot2], charaWork.parameterSave.commandSlot_recastTime[lowHotbarSlot2]);
//Update slots on client
List<ushort> slotsToUpdate = new List<ushort>();
slotsToUpdate.Add(hotbarSlot1);
slotsToUpdate.Add(hotbarSlot2);
UpdateHotbar(slotsToUpdate);
hotbarSlotsToUpdate.Add(hotbarSlot1);
hotbarSlotsToUpdate.Add(hotbarSlot2);
updateFlags |= ActorUpdateFlags.Hotbar;
}
public void UnequipAbility(ushort hotbarSlot, bool printMessage = true)
{
List<ushort> slotsToUpdate = new List<ushort>();
ushort trueHotbarSlot = (ushort)(hotbarSlot + charaWork.commandBorder - 1);
ushort trueHotbarSlot = (ushort)(hotbarSlot + charaWork.commandBorder);
uint commandId = charaWork.command[trueHotbarSlot];
Database.UnequipAbility(this, (ushort)(trueHotbarSlot - charaWork.commandBorder));
Database.UnequipAbility(this, hotbarSlot);
charaWork.command[trueHotbarSlot] = 0;
slotsToUpdate.Add(trueHotbarSlot);
hotbarSlotsToUpdate.Add(trueHotbarSlot);
if(printMessage)
if (printMessage && commandId != 0)
SendGameMessage(Server.GetWorldManager().GetActor(), 30604, 0x20, 0, 0xA0F00000 ^ commandId);
UpdateHotbar(slotsToUpdate);
updateFlags |= ActorUpdateFlags.Hotbar;
}
//Finds the first hotbar slot with a given commandId.
@ -2290,110 +2302,75 @@ namespace FFXIVClassic_Map_Server.Actors
return true;
}
public override bool CanCast(Character target, BattleCommand spell)
//Do we need separate functions? they check the same things
public override bool CanUse(Character target, BattleCommand skill, CommandResult error = null)
{
//Might want to do these with a CommandResult instead to be consistent with the rest of command stuff
if (GetHotbarTimer(spell.id) > Utils.UnixTimeStampUTC())
{
// todo: this needs confirming
// Please wait a moment and try again.
SendGameMessage(Server.GetWorldManager().GetActor(), 32535, 0x20, (uint)spell.id);
return false;
}
if (target == null)
{
// Target does not exist.
SendGameMessage(Server.GetWorldManager().GetActor(), 32511, 0x20, (uint)spell.id);
return false;
}
if (Utils.XZDistance(positionX, positionZ, target.positionX, target.positionZ) > spell.range)
{
// The target is too far away.
SendGameMessage(Server.GetWorldManager().GetActor(), 32539, 0x20, (uint)spell.id);
return false;
}
if (Utils.XZDistance(positionX, positionZ, target.positionX, target.positionZ) < spell.minRange)
{
// The target is too close.
SendGameMessage(Server.GetWorldManager().GetActor(), 32538, 0x20, (uint)spell.id);
return false;
}
if (target.positionY - positionY > (spell.rangeHeight / 2))
{
// The target is too far above you.
SendGameMessage(Server.GetWorldManager().GetActor(), 32540, 0x20, (uint)spell.id);
return false;
}
if (positionY - target.positionY > (spell.rangeHeight / 2))
{
// The target is too far below you.
SendGameMessage(Server.GetWorldManager().GetActor(), 32541, 0x20, (uint)spell.id);
return false;
}
if (!IsValidTarget(target, spell.mainTarget) || !spell.IsValidMainTarget(this, target))
if (!skill.IsValidMainTarget(this, target, error) || !IsValidTarget(target, skill.mainTarget))
{
// error packet is set in IsValidTarget
return false;
}
return true;
}
public override bool CanWeaponSkill(Character target, BattleCommand skill)
{
// todo: see worldmaster ids 32558~32557 for proper ko message and stuff
//Might want to do these with a BattleAction instead to be consistent with the rest of command stuff
if (GetHotbarTimer(skill.id) > Utils.UnixTimeStampUTC())
{
// todo: this needs confirming
// Please wait a moment and try again.
SendGameMessage(Server.GetWorldManager().GetActor(), 32535, 0x20, (uint)skill.id);
error?.SetTextId(32535);
return false;
}
if (target == null)
if (Utils.XZDistance(positionX, positionZ, target.positionX, target.positionZ) > skill.range)
{
// Target does not exist.
SendGameMessage(Server.GetWorldManager().GetActor(), 32511, 0x20, (uint)skill.id);
// The target is too far away.
error?.SetTextId(32539);
return false;
}
if (Utils.XZDistance(positionX, positionZ, target.positionX, target.positionZ) < skill.minRange)
{
// The target is too close.
error?.SetTextId(32538);
return false;
}
//Original game checked height difference before horizontal distance
if (target.positionY - positionY > (skill.rangeHeight / 2))
{
// The target is too far above you.
SendGameMessage(Server.GetWorldManager().GetActor(), 32540, 0x20, (uint)skill.id);
error?.SetTextId(32540);
return false;
}
if (positionY - target.positionY > (skill.rangeHeight / 2))
{
// The target is too far below you.
SendGameMessage(Server.GetWorldManager().GetActor(), 32541, 0x20, (uint)skill.id);
error?.SetTextId(32541);
return false;
}
var targetDist = Utils.XZDistance(positionX, positionZ, target.positionX, target.positionZ);
if (targetDist > skill.range)
if (skill.CalculateMpCost(this) > GetMP())
{
// The target is out of range.
SendGameMessage(Server.GetWorldManager().GetActor(), 32537, 0x20, (uint)skill.id);
// You do not have enough MP.
error?.SetTextId(32545);
return false;
}
if (targetDist < skill.minRange)
if (skill.CalculateTpCost(this) > GetTP())
{
// The target is too close.
SendGameMessage(Server.GetWorldManager().GetActor(), 32538, 0x20, (uint)skill.id);
// You do not have enough TP.
error?.SetTextId(32546);
return false;
}
if (!IsValidTarget(target, skill.validTarget) || !skill.IsValidMainTarget(this, target))
//Proc requirement
if (skill.procRequirement != BattleCommandProcRequirement.None && !charaWork.battleTemp.timingCommandFlag[(int)skill.procRequirement - 1])
{
// error packet is set in IsValidTarget
//Conditions for use are not met
error?.SetTextId(32556);
return false;
}
return true;
}
@ -2598,7 +2575,7 @@ namespace FFXIVClassic_Map_Server.Actors
StatusEffect comboEffect = new StatusEffect(this, Server.GetWorldManager().GetStatusEffect((uint) StatusEffectId.Combo));
comboEffect.SetDuration(13);
comboEffect.SetOverwritable(1);
statusEffects.AddStatusEffect(comboEffect, this, true);
statusEffects.AddStatusEffect(comboEffect, this);
playerWork.comboCostBonusRate = 1;
}
//Otherwise we're ending a combo, remove the status
@ -2634,10 +2611,10 @@ namespace FFXIVClassic_Map_Server.Actors
}
var hasShield = equip.GetItemAtSlot(SLOT_OFFHAND) != null ? 1 : 0;
SetMod((uint)Modifier.HasShield, hasShield);
SetMod((uint)Modifier.CanBlock, hasShield);
SetMod((uint)Modifier.AttackType, damageAttribute);
SetMod((uint)Modifier.AttackDelay, attackDelay);
SetMod((uint)Modifier.Delay, attackDelay);
SetMod((uint)Modifier.HitCount, hitCount);
//These stats all correlate in a 3:2 fashion
@ -2647,13 +2624,13 @@ namespace FFXIVClassic_Map_Server.Actors
AddMod((uint)Modifier.Defense, (long)(GetMod(Modifier.Vitality) * 0.667));
//These stats correlate in a 4:1 fashion. (Unsure if MND is accurate but it would make sense for it to be)
AddMod((uint)Modifier.MagicAttack, (long)((float)GetMod(Modifier.Intelligence) * 0.25));
AddMod((uint)Modifier.AttackMagicPotency, (long)((float)GetMod(Modifier.Intelligence) * 0.25));
AddMod((uint)Modifier.MagicAccuracy, (long)((float)GetMod(Modifier.Mind) * 0.25));
AddMod((uint)Modifier.MagicHeal, (long)((float)GetMod(Modifier.Mind) * 0.25));
AddMod((uint)Modifier.HealingMagicPotency, (long)((float)GetMod(Modifier.Mind) * 0.25));
AddMod((uint)Modifier.MagicEvasion, (long)((float)GetMod(Modifier.Piety) * 0.25));
AddMod((uint)Modifier.MagicEnfeeblingPotency, (long)((float)GetMod(Modifier.Piety) * 0.25));
AddMod((uint)Modifier.EnfeeblingMagicPotency, (long)((float)GetMod(Modifier.Piety) * 0.25));
//VIT correlates to HP in a 1:1 fashion
AddMod((uint)Modifier.Hp, (long)((float)Modifier.Vitality));

View File

@ -2,6 +2,7 @@
using FFXIVClassic_Map_Server.dataobjects;
using FFXIVClassic_Map_Server.packets.send.group;
using FFXIVClassic_Map_Server.packets.send.groups;
using System;
using System.Collections.Generic;
namespace FFXIVClassic_Map_Server.actors.group
@ -126,13 +127,14 @@ namespace FFXIVClassic_Map_Server.actors.group
while (true)
{
if (GetMemberCount() - currentIndex >= 64)
int memberCount = Math.Min(GetMemberCount(), members.Count);
if (memberCount - currentIndex >= 64)
session.QueuePacket(GroupMembersX64Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex));
else if (GetMemberCount() - currentIndex >= 32)
else if (memberCount - currentIndex >= 32)
session.QueuePacket(GroupMembersX32Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex));
else if (GetMemberCount() - currentIndex >= 16)
else if (memberCount - currentIndex >= 16)
session.QueuePacket(GroupMembersX16Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex));
else if (GetMemberCount() - currentIndex > 0)
else if (memberCount - currentIndex > 0)
session.QueuePacket(GroupMembersX08Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex));
else
break;

View File

@ -8,13 +8,26 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
//These flags can be stacked and mixed, but the client will prioritize certain flags over others.
[Flags]
public enum HitEffect : uint
{
//All HitEffects have the last byte 0x8
{
//This is used for physical attacks
HitEffectType = 8 << 24,
//This is used for additioanl effect hits. Only difference from HitEffectType is that it does not play audio.
AdditionalEffectType = 24 << 24,
//Status effects use 32 << 24
StatusEffectType = 32 << 24,
//Magic effects use 48 << 24
//When losing a status effect while using a skill, this prevents the hit effect from playing on the actor playing the animation
StatusLossType = 40 << 24,
//Magic effects use 48 << 24, this is also used for when statuses are lost on attack
MagicEffectType = 48 << 24,
//This places the number on the user regardless of the target this hit effect is for, used for things like bloodbath
SelfHealType = 72 << 24,
//Plays the effect animation with no text or additional effects. Unsure if there are any flags. Used for things like Convert
AnimationEffectType = 96 << 24,
//Each Type has it's own set of flags. These should be split into their own enums,
//but for now just keep them all under HitEffect so we don't have to change anything.
//HitEffectType flags
//Not setting RecoilLv2 or RecoilLv3 results in the weaker RecoilLv1.
//These are the recoil animations that play on the target, ranging from weak to strong.
@ -54,15 +67,10 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
//Another effect plays when both Protect and Shell flags are activated.
//Not sure what this effect is.
//Random guess: if the attack was a hybrid of both physical and magical and the target had both Protect and Shell buffs applied.
Protect = 1 << 6 | HitEffectType,
Shell = 1 << 7 | HitEffectType,
Protect = 1 << 6,
Shell = 1 << 7,
ProtectShellSpecial = Protect | Shell,
// Required for heal text to be blue, not sure if that's all it's used for
Heal = 1 << 8,
MP = 1 << 9, //Causes "MP" text to appear when used with MagicEffectType. | with Heal to make text blue
TP = 1 << 10,//Causes "TP" text to appear when used with MagicEffectType. | with Heal to make text blue
//If only HitEffect1 is set out of the hit effects, the "Evade!" pop-up text triggers along with the evade visual.
//If no hit effects are set, the "Miss!" pop-up is triggered and no hit visual is played.
HitEffect1 = 1 << 9,
@ -103,17 +111,69 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
UnknownShieldEffect = HitEffect5 | HitEffect4,
Stoneskin = HitEffect5 | HitEffect4 | HitEffect1,
//Unknown = 1 << 14, -- Not sure what this flag does; might be another HitEffect.
//A special effect when performing appropriate skill combos in succession.
//Ex: Thunder (SkillCombo1 Effect) -> Thundara (SkillCombo2 Effect) -> Thundaga (SkillCombo3 Effect)
//Special Note: SkillCombo4 was never actually used in 1.0 since combos only chained up to 3 times maximum.
SkillCombo1 = 1 << 15,
SkillCombo2 = 1 << 16,
SkillCombo3 = SkillCombo1 | SkillCombo2,
SkillCombo4 = 1 << 17
SkillCombo4 = 1 << 17,
//Flags beyond here are unknown/untested.
//This is used in the absorb effect for some reason
Unknown = 1 << 19,
//AdditionalEffectType flags
//The AdditionalEffectType is used for the additional effects some weapons have.
//These effect ids do not repeat the effect of the attack and will not show without a preceding HitEffectType or MagicEffectType
//It's unclear what this is for. The ifrit fight capture has a BLM using the garuda weapon
//and this flag is set every time but has no apparent effect.
UnknownAdditionalFlag = 1,
//These play effects on the target
FireEffect = 1 << 10,
IceEffect = 2 << 10,
WindEffect = 3 << 10,
EarthEffect = 4 << 10,
LightningEffect = 5 << 10,
WaterEffect = 6 << 10,
AstralEffect = 7 << 10, //Possibly for blind?
UmbralEffect = 8 << 10, //Posibly for poison?
//Unknown status effect effects
StatusEffect1 = 12 << 10,
StatusEffect2 = 13 << 10,
HPAbsorbEffect = 14 << 10,
MPAbsorbEffect = 15 << 10,
TPAbsorbEffect = 16 << 10,
TripleAbsorbEffect = 17 << 10, //Not sure about this
MoogleEffect = 18 << 10,
//MagicEffectType Flags
//THese are used for magic effects that deal or heal damage as well as damage over time effects
//Crit is the same as HitEffectType
FullResist = 0,
WeakResist = 1 << 0, //Used for level 1, 2, and 3 resists probably
NoResist = 1 << 1,
MagicShell = 1 << 4, //Used when casting on target with shell effects. MagicEffectType doesnt have a flag for protect or stoneskin
MagicShield = 1 << 5, //When used with an command that has an animation, this plays a purple shield effect. DoTs also have this flag set (at least on ifrit) but they have no animations so it doesnt show
// Required for heal text to be blue, not sure if that's all it's used for
Heal = 1 << 8,
MP = 1 << 9, //Causes "MP" text to appear when used with MagicEffectType. | with Heal to make text blue
TP = 1 << 10, //Causes "TP" text to appear when used with MagicEffectType. | with Heal to make text blue
//SelfHealType flags
//This category causes numbers to appear on the user rather regardless of the target associated with the hit effect and do not play an animation
//These determine the text that displays (HP has no text)
SelfHealHP = 0,
SelfHealMP = 1 << 0, //Shows MP text on self. | with SelfHeal to make blue
SelfHealTP = 1 << 1, //Shows TP text on self. | with SelfHeal to make blue
//Causes self healing numbers to be blue
SelfHeal = 1 << 10,
}
//Mixing some of these flags will cause the client to crash.
@ -134,9 +194,12 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
Evade = 1,
Parry = 2,
Block = 3,
Resist = 4,
Hit = 5,
Crit = 6
SingleResist = 4,
DoubleResist = 5,
TripleResist = 6,
FullResist = 7,
Hit = 8,
Crit = 9
}
//Type of action
@ -326,5 +389,16 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
{
return (ushort)hitType;
}
public void SetTextId(ushort id)
{
worldMasterTextId = id;
}
//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;
}
}
}

View File

@ -20,6 +20,8 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
}
//Just to make scripting simpler
//These have to be split into the normal actions and absorb actions because they use different flags
//AddMP/HP/TPAction are for actions where the targetID is the person being targeted by command. Like Sanguine Rite would use AddMPAction
public void AddMPAction(uint targetId, ushort worldMasterTextId, ushort amount)
{
uint effectId = (uint) (HitEffect.MagicEffectType | HitEffect.MP | HitEffect.Heal);
@ -28,13 +30,38 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
public void AddHPAction(uint targetId, ushort worldMasterTextId, ushort amount)
{
uint effectId = (uint)(HitEffect.MagicEffectType | HitEffect.Heal);
uint effectId = (uint) (HitEffect.MagicEffectType | HitEffect.Heal);
AddAction(targetId, worldMasterTextId, effectId, amount);
}
public void AddTPAction(uint targetId, ushort worldMasterTextId, ushort amount)
{
uint effectId = (uint)(HitEffect.MagicEffectType | HitEffect.TP);
uint effectId = (uint) (HitEffect.MagicEffectType | HitEffect.TP | HitEffect.Heal);
AddAction(targetId, worldMasterTextId, effectId, amount);
}
//These are used for skills where the targetId is the person using a command. For example casting with parsimony would use AddMPAbsorbAction
public void AddMPAbsorbAction(uint targetId, ushort worldMasterTextId, ushort amount)
{
uint effectId = (uint) (HitEffect.SelfHealType | HitEffect.SelfHealMP | HitEffect.SelfHeal);
AddAction(targetId, worldMasterTextId, effectId, amount);
}
public void AddHPAbsorbAction(uint targetId, ushort worldMasterTextId, ushort amount)
{
uint effectId = (uint) (HitEffect.SelfHealType | HitEffect.SelfHeal | HitEffect.SelfHeal);
AddAction(targetId, worldMasterTextId, effectId, amount);
}
public void AddTPAbsorbAction(uint targetId, ushort worldMasterTextId, ushort amount)
{
uint effectId = (uint) (HitEffect.SelfHealType | HitEffect.SelfHealTP | HitEffect.SelfHeal);
AddAction(targetId, worldMasterTextId, effectId, amount);
}
public void AddHitAction(uint targetId, ushort worldMasterTextId, ushort amount)
{
uint effectId = (uint) (HitEffect.HitEffectType | HitEffect.Hit);
AddAction(targetId, worldMasterTextId, effectId, amount);
}

View File

@ -1,5 +1,6 @@
using FFXIVClassic.Common;
using FFXIVClassic_World_Server.Packets.Send.Subpackets.Groups;
using System;
using System.Collections.Generic;
namespace FFXIVClassic_World_Server.DataObjects.Group
@ -104,13 +105,14 @@ namespace FFXIVClassic_World_Server.DataObjects.Group
while (true)
{
if (GetMemberCount() - currentIndex >= 64)
int memberCount = Math.Min(GetMemberCount(), members.Count);
if (memberCount - currentIndex >= 64)
session.clientConnection.QueuePacket(GroupMembersX64Packet.buildPacket(session.sessionId, session.currentZoneId, time, members, ref currentIndex));
else if (GetMemberCount() - currentIndex >= 32)
else if (memberCount - currentIndex >= 32)
session.clientConnection.QueuePacket(GroupMembersX32Packet.buildPacket(session.sessionId, session.currentZoneId, time, members, ref currentIndex));
else if (GetMemberCount() - currentIndex >= 16)
else if (memberCount - currentIndex >= 16)
session.clientConnection.QueuePacket(GroupMembersX16Packet.buildPacket(session.sessionId, session.currentZoneId, time, members, ref currentIndex));
else if (GetMemberCount() - currentIndex > 0)
else if (memberCount - currentIndex > 0)
session.clientConnection.QueuePacket(GroupMembersX08Packet.buildPacket(session.sessionId, session.currentZoneId, time, members, ref currentIndex));
else
break;

View File

@ -61,4 +61,47 @@ TargetFindAOEType =
Circle = 1,
Cone = 2,
Box = 3
}
StatusEffectFlags =
{
None = 0,
--Loss flags - Do we need loseonattacking/caststart? Could just be done with activate flags
LoseOnDeath = bit32.lshift(1, 0), -- effects removed on death
LoseOnZoning = bit32.lshift(1, 1), -- effects removed on zoning
LoseOnEsuna = bit32.lshift(1, 2), -- effects which can be removed with esuna (debuffs)
LoseOnDispel = bit32.lshift(1, 3), -- some buffs which player might be able to dispel from mob
LoseOnLogout = bit32.lshift(1, 4), -- effects removed on logging out
LoseOnAttacking = bit32.lshift(1, 5), -- effects removed when owner attacks another entity
LoseOnCastStart = bit32.lshift(1, 6), -- effects removed when owner starts casting
LoseOnAggro = bit32.lshift(1, 7), -- effects removed when owner gains enmity (swiftsong)
LoseOnClassChange = bit32.lshift(1, 8), --Effect falls off whhen changing class
--Activate flags
ActivateOnCastStart = bit32.lshift(1, 9), --Activates when a cast starts.
ActivateOnCommandStart = bit32.lshift(1, 10), --Activates when a command is used, before iterating over targets. Used for things like power surge, excruciate.
ActivateOnCommandFinish = bit32.lshift(1, 11), --Activates when the command is finished, after all targets have been iterated over. Used for things like Excruciate and Resonance falling off.
ActivateOnPreactionTarget = bit32.lshift(1, 12), --Activates after initial rates are calculated for an action against owner
ActivateOnPreactionCaster = bit32.lshift(1, 13), --Activates after initial rates are calculated for an action by owner
ActivateOnDamageTaken = bit32.lshift(1, 14),
ActivateOnHealed = bit32.lshift(1, 15),
--Should these be rolled into DamageTaken?
ActivateOnMiss = bit32.lshift(1, 16), --Activates when owner misses
ActivateOnEvade = bit32.lshift(1, 17), --Activates when owner evades
ActivateOnParry = bit32.lshift(1, 18), --Activates when owner parries
ActivateOnBlock = bit32.lshift(1, 19), --Activates when owner evades
ActivateOnHit = bit32.lshift(1, 20), --Activates when owner hits
ActivateOnCrit = bit32.lshift(1, 21), --Activates when owner crits
--Prevent flags. Sleep/stun/petrify/etc combine these
PreventSpell = bit32.lshift(1, 22), -- effects which prevent using spells, such as silence
PreventWeaponSkill = bit32.lshift(1, 23), -- effects which prevent using weaponskills, such as pacification
PreventAbility = bit32.lshift(1, 24), -- effects which prevent using abilities, such as amnesia
PreventAttack = bit32.lshift(1, 25), -- effects which prevent basic attacks
PreventMovement = bit32.lshift(1, 26), -- effects which prevent movement such as bind, still allows turning in place
PreventTurn = bit32.lshift(1, 27), -- effects which prevent turning, such as stun
PreventUntarget = bit32.lshift(1, 28), -- effects which prevent changing targets, such as fixation
Stance = bit32.lshift(1, 29) -- effects that do not have a timer
}

View File

@ -0,0 +1,5 @@
function onEventStarted(player, command, triggerName, arg1, arg2, arg3, arg4, targetActor, arg5, arg6, arg7, arg8)
player.Cast(command.actorId, targetActor);
player:endEvent();
end

View File

@ -92,6 +92,14 @@ function onEventStarted(player, actor, triggerName, isTeleport)
if (choice == 1) then
player:PlayAnimation(0x4000FFB);
player:SendGameMessage(worldMaster, 34104, 0x20);
--bandaid fix for returning while dead, missing things like weakness and the heal number
if (player:GetHP() == 0) then
player:SetHP(player.GetMaxHP());
player:ChangeState(0);
player:PlayAnimation(0x01000066);
end
if (isInn) then
--Return to Inn
if (player:GetHomePointInn() == 1) then
@ -107,12 +115,6 @@ function onEventStarted(player, actor, triggerName, isTeleport)
if (destination ~= nil) then
randoPos = getRandomPointInBand(destination[2], destination[4], 3, 5);
rotation = getAngleFacing(randoPos.x, randoPos.y, destination[2], destination[4]);
--bandaid fix for returning while dead, missing things like weakness and the heal number
if (player:GetHP() == 0) then
player:SetHP(player.GetMaxHP());
player:ChangeState(0);
player:PlayAnimation(0x01000066);
end
GetWorldManager():DoZoneChange(player, destination[1], nil, 0, 2, randoPos.x, destination[3], randoPos.y, rotation);
end

View File

@ -12,8 +12,12 @@ end;
function onSkillFinish(caster, target, skill, action, actionContainer)
--Only the bard gets the Battle Voice effect
if caster == target then
actionContainer.AddAction(caster.statusEffects.AddStatusForBattleAction(223253, 1, 0, 30));
local effect = GetWorldManager():GetStatusEffect(223253);
effect.SetDuration(30);
caster.statusEffects.AddStatusEffect(effect, caster, actionContainer);
end
action.DoAction(caster, target, skill, actionContainer);
local effect = GetWorldManager():GetStatusEffect(223029);
effect.SetDuration(60);
caster.statusEffects.AddStatusEffect(effect, caster, actionContainer);
end;

View File

@ -14,6 +14,18 @@ function onAbilityStart(caster, target, ability)
end;
function onSkillFinish(caster, target, skill, action, actionContainer)
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);
--223207: Berserk
--223208: Rampage
--Remove Rampage effect. I'm assuming no message is sent like LNC surges
caster.statusEffects.RemoveStatusEffect(223208);
--If caster has berserk already, remove it and send a message.
local buff = caster.statusEffects.GetStatusEffectById(223207)
if buff ~= nil then
caster.statusEffects.RemoveStatusEffect(buff, actionContainer, 30329);
else
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);
end
end;

View File

@ -22,22 +22,18 @@ function onSkillFinish(caster, target, skill, action, actionContainer)
--If we have a buff then Blissful Mind removes that buff and restores MP. Otherwise, it adds the Blissful Mind effect
if buff ~= nil then
local amount = buff.GetExtra();
local remAction = caster.statusEffects.RemoveStatusEffectForBattleAction(buff, 30329);
caster.AddMP(amount);
actionContainer.AddMPAction(caster.actorId, 33007, amount);
actionContainer.AddAction(remAction);
caster.statusEffects.RemoveStatusEffect(buff, actionContainer, 30329);
else
--Blissful mind takes 25% of CURRENT HP and begins storing MP up to that point, at which point the buff changes to indicate its full
local amount = caster.GetHP() * 0.25;
caster.DelHP(amount);
caster.DelHP(amount, actionContainer);
skill.statusMagnitude = amount;
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);
end
end;

View File

@ -14,5 +14,7 @@ function onAbilityStart(caster, target, ability)
end;
function onSkillFinish(caster, target, skill, action, actionContainer)
target.hateContainer.UpdateHate(caster, -840);
--Need a way to get all targets with hate for player
--target.hateContainer.UpdateHate(caster, -840);
action.DoAction(caster, target, skill, actionContainer);
end;

View File

@ -10,6 +10,12 @@ function onAbilityStart(caster, target, ability)
end;
function onSkillFinish(caster, target, skill, action, actionContainer)
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);
local buff = caster.statusEffects.GetStatusEffectById(223227)
if buff ~= nil then
caster.statusEffects.RemoveStatusEffect(buff, actionContainer, 30329);
else
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);
end
end;

View File

@ -0,0 +1,20 @@
require("global");
require("ability");
function onAbilityPrepare(caster, target, ability)
return 0;
end;
function onAbilityStart(caster, target, ability)
return 0;
end;
function onSkillFinish(caster, target, skill, action, actionContainer)
newMP = math.min(caster.GetHP(), caster.GetMaxMP())
newHP = math.min(caster.GetMP(), caster.GetMaxHP())
caster.SetHP(newHP)
caster.SetMP(newMP)
--Set effect id
action.DoAction(caster, target, skill, actionContainer);
end;

View File

@ -17,7 +17,7 @@ function onSkillFinish(caster, target, skill, action, actionContainer)
coverTier = 2;
end
actionContainer.AddAction(caster.statusEffects.AddStatusForBattleAction(223063, coverTier, skill.statusDuration));
caster.statusEffects.AddStatusEffect(223063, coverTier, 0, 15, 0);
--Apply Covered to target
action.DoAction(caster, target, skill, actionContainer);

View File

@ -1,18 +1,23 @@
require("global");
require("ability");
function onAbilityPrepare(caster, target, ability)
return 0;
end;
function onAbilityStart(caster, target, ability)
return 0;
end;
function onSkillFinish(caster, target, skill, action, actionContainer)
--Need a better way to do this
for i = 223212,223217 do
local remAction = caster.statusEffects.RemoveStatusEffectForBattleAction(i, 30329)
local buff = caster.statusEffects.GetStatusEffectById(i);
if remAction ~= nil then
actionContainer.AddAction(remAction);
if buff ~= nil then
caster.statusEffects.RemoveStatusEffect(buff, actionContainer, 30329);
skill.statusTier = 2;
break;
end

View File

@ -0,0 +1,19 @@
require("global");
require("ability");
function onAbilityPrepare(caster, target, ability)
return 0;
end;
function onAbilityStart(caster, target, ability)
--27124: Enhanced Fists of Fire
if caster.HasTrait(27124) then
ability.statusTier = 2;
end
return 0;
end;
function onSkillFinish(caster, target, skill, action, actionContainer)
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);
end;

View File

@ -12,6 +12,6 @@ end;
function onSkillFinish(caster, target, skill, action, actionContainer)
--Take off 1/3 of attack delay. Not sure if this is the exact amount HF reduces by
action.statusMagnitude = 0.33 * caster.GetMod(modifiersGlobal.AttackDelay);
skill.statusMagnitude = 0.33 * caster.GetMod(modifiersGlobal.Delay);
action.DoAction(caster, target, skill, actionContainer);
end;

View File

@ -20,9 +20,9 @@ function onSkillFinish(caster, target, skill, action, actionContainer)
--223213: Power Surge II
--223212: Power Surge III
--No message is sent when PS is removed by Life Surge
caster.statusEffects.RemoveStatusEffect(223212, true);
caster.statusEffects.RemoveStatusEffect(223213, true);
caster.statusEffects.RemoveStatusEffect(223214, true);
caster.statusEffects.RemoveStatusEffect(223212);
caster.statusEffects.RemoveStatusEffect(223213);
caster.statusEffects.RemoveStatusEffect(223214);
--Using this ability moves to the next LS buff
@ -45,7 +45,6 @@ function onSkillFinish(caster, target, skill, action, actionContainer)
end
if not (removeId == 0) then
--caster.statusEffects.RemoveStatusEffect(removeId, true);
caster.statusEffects.ReplaceEffect(caster.statusEffects.GetStatusEffectById(removeId), skill.statusId, skill.statusTier, skill.statusMagnitude, skill.statusDuration);
end
--DoAction handles rates, buffs, dealing damage

View File

@ -14,11 +14,17 @@ function onAbilityStart(caster, target, ability)
end;
function onSkillFinish(caster, target, skill, action, actionContainer)
--Need a better way to do this
actionContainer.AddAction(caster.statusEffects.RemoveStatusEffectForBattleAction(223215));
actionContainer.AddAction(caster.statusEffects.RemoveStatusEffectForBattleAction(223216));
actionContainer.AddAction(caster.statusEffects.RemoveStatusEffectForBattleAction(223217));
caster.statusEffects.RemoveStatusEffect(223215);
caster.statusEffects.RemoveStatusEffect(223216);
caster.statusEffects.RemoveStatusEffect(223217);
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);
--If caster has any of the power surge effects
local buff = caster.statusEffects.GetStatusEffectById(223212) or caster.statusEffects.GetStatusEffectById(223213) or caster.statusEffects.GetStatusEffectById(223214);
if buff ~= nil then
caster.statusEffects.RemoveStatusEffect(buff, actionContainer, 30329);
else
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);
end
end;

View File

@ -12,18 +12,21 @@ end;
function onSkillFinish(caster, target, skill, action, actionContainer)
--QS gives 300 TP by default.
skill.statusMagnitude = 300;
--I'm assuming that with raging strikes, that increases to 500.
--and traited that increases again to 750 (or 450 without RS)
if caster.statusEffects.HasStatusEffect(223221) then
actionContainer.AddAction(caster.statusEffects.RemoveStatusEffectForBattleAction(223221));
skill.statusMagnitude = 500;
end
--27241: Enhanced Quelling Strike: Increases TP gained from QS by 50%
if caster.HasTrait(27241) then
skill.statusMagnitude = skill.statusMagnitude * 1.5;
end
--When raging strikes is active, Quelling Strikes removes it and immediately restores 100 TP for each tier ofr Raging Strikes.
local buff = caster.statusEffects.GetStatusEffectById(223221)
if buff ~= nil then
skill.tpCost = -100 * (buff.GetTier() - 1);
--QS doesn't send a message
caster.statusEffects.RemoveStatusEffect(buff);
end
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);
end;

View File

@ -6,14 +6,23 @@ function onAbilityPrepare(caster, target, ability)
end;
function onAbilityStart(caster, target, ability)
ability.statusMagnitude = 100;
--27243: Enhanced Raging Strike: Increases effect of Raging Strike by 50%
if caster.HasTrait(27241) then
ability.statusTier = 2;
ability.statusMagnitude = ability.statusMagnitude * 1.5;
end
return 0;
end;
function onSkillFinish(caster, target, skill, action, actionContainer)
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);
--If caster has raging strike, remove it and send message, otherwise apply it.
local buff = caster.statusEffects.GetStatusEffectById(223221)
if buff ~= nil then
--30329: Your Raging Strike removes your Raging Strike effect.
caster.statusEffects.RemoveStatusEffect(buff, actionContainer, 30329);
else
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);
end
end;

View File

@ -14,6 +14,18 @@ function onAbilityStart(caster, target, ability)
end;
function onSkillFinish(caster, target, skill, action, actionContainer)
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);
--223207: Berserk
--223208: Rampage
--Remove Berserk effect. I'm assuming no message is sent like LNC surges
caster.statusEffects.RemoveStatusEffect(223207);
--If caster has rampage already, remove it and send a message.
local buff = caster.statusEffects.GetStatusEffectById(223208)
if buff ~= nil then
caster.statusEffects.RemoveStatusEffect(buff, actionContainer, 30329);
else
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);
end
end;

View File

@ -1,11 +1,11 @@
require("global");
require("weaponskill");
require("ability");
function onSkillPrepare(caster, target, skill)
function onAbilityPrepare(caster, target, ability)
return 0;
end;
function onSkillStart(caster, target, skill)
function onAbilityStart(caster, target, ability)
return 0;
end;

View File

@ -12,9 +12,9 @@ end;
function onSkillFinish(caster, target, skill, action, actionContainer)
--Is this before or after status is gained?
--Will probably need to switch to a flag for this because it might include more than just these 3 effects.
actionContainer.AddAction(caster.statusEffects.RemoveStatusEffectForBattleAction(228011));
actionContainer.AddAction(caster.statusEffects.RemoveStatusEffectForBattleAction(228013));
actionContainer.AddAction(caster.statusEffects.RemoveStatusEffectForBattleAction(228021));
caster.statusEffects.RemoveStatusEffect(228011, actionContainer, 30329);
caster.statusEffects.RemoveStatusEffect(228013, actionContainer, 30329);
caster.statusEffects.RemoveStatusEffect(228021, actionContainer, 30329);
action.DoAction(caster, target, skill, actionContainer);
end;

View File

@ -0,0 +1,17 @@
require("global");
function onSkillPrepare(caster, target, skill)
return 0;
end;
function onSkillStart(caster, target, skill)
return 0;
end;
function onSkillFinish(caster, target, skill, action, actionContainer)
--calculate ws damage
action.amount = skill.basePotency;
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);
end;

View File

@ -2,7 +2,7 @@ require("global");
properties = {
permissions = 0,
parameters = "sssss",
parameters = "sss",
description =
[[
Adds experience <qty> to player or <targetname>.
@ -11,18 +11,19 @@ Adds experience <qty> to player or <targetname>.
]],
}
function onTrigger(player, argc, commandId, animationId, textId, effectId, amount)
function onTrigger(player, argc, animType, modelAnim, effectId)
local sender = "[battleaction] ";
if player then
cid = tonumber(commandId) or 0;
aid = tonumber(animationId) or 0;
tid = tonumber(textId) or 0;
print(effectId)
eid = tonumber(effectId) or 0;
amt = tonumber(amount) or 0;
player:DoBattleActionAnimation(cid, aid, tid, eid, amt);
local actor = GetWorldManager():GetActorInWorld(player.currentTarget) or nil;
if player and actor then
aid = tonumber(animType) or 0
mid = tonumber(modelAnim) or 0
eid = tonumber(effectId) or 0
local id = bit32.lshift(aid, 24);
id = bit32.bor(id, bit32.lshift(mid, 12));
id = bit32.bor(id, eid)
print((tonumber(id)))
player:DoBattleAction(30301, id);
else
print(sender.."unable to add experience, ensure player name is valid.");
end;

View File

@ -0,0 +1,42 @@
require("global");
require("modifiers");
properties = {
permissions = 0,
parameters = "s",
description =
[[
equips all your class and job actions
]],
}
classToActions = {
[2] = { Start = 27100, End = 27119},
[3] = { Start = 27140, End = 27159},
[4] = { Start = 27180, End = 27199},
[7] = { Start = 27220, End = 27239},
[8] = { Start = 27260, End = 27279},
[22] = { Start = 27300, End = 27319},
[23] = { Start = 27340, End = 27359}
}
function onTrigger(player, argc)
local messageId = MESSAGE_TYPE_SYSTEM_ERROR;
local sender = "equipactions";
classId = player.GetClass()
if classToActions[classId] then
s = classToActions[classId].Start
e = classToActions[classId].End
print('h')
for i = 0, 30 do
player.UnequipAbility(i, false)
end
for commandid = s, e do
if GetWorldManager():GetBattleCommand(commandid) then
player:EquipAbilityInFirstOpenSlot(player:GetCurrentClassOrJob(), commandid);
end
end
end
end

View File

@ -20,16 +20,22 @@ function onTrigger(player, argc, slot, wId, eId, vId, cId)
vId = tonumber(vId) or 0;
cId = tonumber(cId) or 0;
if player and argc > 0 then
if argc > 2 then
player:GraphicChange(slot, wId, eId, vId, cId);
player:SendMessage(messageID, sender, string.format("Changing appearance on slot %u", slot));
local actor = GetWorldManager():GetActorInWorld(player.currentTarget) or nil;
if player and actor then
if player and argc > 0 then
-- player.appearanceIds[5] = player.achievementPoints;
if argc > 2 then
actor:GraphicChange(slot, wId, eId, vId, cId);
--player.achievementPoints = player.achievementPoints + 1;
actor:SendMessage(messageID, sender, string.format("Changing appearance on slot %u", slot));
actor:SendMessage(messageID, sender, string.format("points %u", player.appearanceIds[5]));
else
actor.appearanceIds[slot] = wId;
end
actor:SendAppearance();
else
player:GraphicChange(slot, wId);
end
player:SendAppearance();
else
player:SendMessage(messageID, sender, "No parameters sent! Usage: "..properties.description);
player:SendMessage(messageID, sender, "No parameters sent! Usage: "..properties.description);
end;
end;
end;

View File

@ -0,0 +1,135 @@
require("global");
require("modifiers");
properties = {
permissions = 0,
parameters = "sss",
description =
[[
yolo
]],
}
local modelIds =
{
["titan"] = 2107401,
["ifrit"] = 2107302,
["ifrithotair"] = 2207310,
["nail"] = 2207307,
["garuda"] = 2209501,
["garudahelper"] = 2209516,
["plume"] = 2209502,
["monolith"] = 2209506,
["mog"] = 2210408,
["nael"] = 2210902,
["meteor"] = 2210903,
["cactuar"] = 2200905,
["morbol"] = 2201002,
["drake"] = 2202209,
["ogre"] = 2202502,
["treant"] = 2202801,
["couerl"] = 2203203,
["wyvern"] = 2203801,
["clouddragon"] = 2208101,
["golem"] = 2208901,
["atomos"] = 2111002,
["chimera"] = 2308701,
["salamander"] = 2201302,
["ahriman"] = 2201704,
["rat"] = 9111275,
["bat"] = 2104113,
["chigoe"] = 2105613,
["hedgemole"] = 2105709,
["gnat"] = 2200604,
["bird"] = 2201208,
["puk"] = 2200112,
["angler"] = 2204507,
["snurble"] = 2204403,
["lemur"] = 2200505,
["doe"] = 2200303,
["hippogryph"] = 2200405,
["trap"] = 2202710,
["goat"] = 2102312,
["dodo"] = 9111263,
["imp"] = 2202607,
["spriggan"] = 2290036,
["cyclops"] = 2210701,
["raptor"] = 2200205,
["wolf"] = 2201429,
["fungus"] = 2205907,
["basilisk"] = 2200708,
["bomb"] = 2201611,
["jellyfish"] = 2105415,
["slug"] = 2104205,
["coblyn"] = 2202103,
["ghost"] = 2204317,
["crab"] = 2107613,
["yarzon"] = 2205520,
["elemental"] = 2105104,
["boar"] = 2201505,
["kobold"] = 2206629,
["sylph"] = 2206702,
["ixal"] = 2206434,
["amaljaa"] = 2206502,
["qiqirn"] = 2206304,
["apkallu"] = 2202902,
["goobbue"] = 2103301,
["garlean"] = 2207005,
["flan"] = 2103404,
["swarm"] = 2105304,
["goblin"] = 2210301,
["buffalo"] = 2200802,
["skeleton"] = 2201902,
["zombie"] = 2201807,
["toad"] = 2203107,
["wisp"] = 2209903,
["juggernaut"] = 6000243,
["mammet"] = 6000246,
["lantern"] = 1200329,
["helper"] = 2310605,
["diremite"] = 2101108,
["gong"] = 1200050,
}
function onTrigger(player, argc, name, width, height, blockCount)
local messageID = MESSAGE_TYPE_SYSTEM_ERROR;
local sender = "spawnnpc";
if player and (modelIds[name] != nil) then
local pos = player:GetPos();
local x = tonumber(pos[0]);
local y = tonumber(pos[1]);
local z = tonumber(pos[2]);
local rot = tonumber(pos[3]);
local zone = pos[4];
local w = tonumber(width) or 0;
local h = tonumber(height) or 0;
local blocks = tonumber(blockCount) or 0;
for b = 0, blocks do
for i = 0, w do
for j = 0, h do
local actor = player.GetZone().SpawnActor(2104001, 'ass', x + (i * 1), y, z + (j * 1), rot, 0, 0, true);
actor.ChangeNpcAppearance(modelIds[name]);
actor.SetMaxHP(5000);
actor.SetHP(5000);
actor.SetMod(modifiersGlobal.CanBlock, 1);
actor.SetMod(modifiersGlobal.AttackRange, 3);
actor.SetMod(modifiersGlobal.MovementSpeed, 5);
actor.SetMobMod(mobModifiersGlobal.Roams, 1);
actor.SetMobMod(mobModifiersGlobal.RoamDelay, 10);
actor.charaWork.parameterSave.state_mainSkillLevel = 52;
actor.moveState = 3;
end;
end;
x = x + 500
end;
return;
elseif player and (modelIds[name] == nil) then
player:SendMessage(messageID, sender, "That name isn't valid");
else
print("I don't even know how you managed this")
end
return;
end;

View File

@ -0,0 +1,24 @@
require("global");
require("utils");
properties = {
permissions = 0,
parameters = "sssss",
description =
[[
Angle stuff!
!anglestuff
]],
}
function onTrigger(player, argc)
local sender = "[battleaction] ";
if player and player.currentTarget then
local actor = GetWorldManager():GetActorInWorld(player.currentTarget) or nil;
actor.Ability(23459, actor.actorId);
else
print(sender.."unable to add experience, ensure player name is valid.");
end;
end;

View File

@ -163,11 +163,12 @@ function onTrigger(player, argc, width, height, blockCount)
actor.ChangeNpcAppearance(2200905);
actor.SetMaxHP(5000);
actor.SetHP(5000);
actor.SetMod(modifiersGlobal.HasShield, 1);
actor.SetMod(modifiersGlobal.CanBlock, 1);
actor.SetMod(modifiersGlobal.AttackRange, 3);
actor.SetMod(modifiersGlobal.Speed, 5);
actor.SetMod(modifiersGlobal.MovementSpeed, 5);
actor.SetMobMod(mobModifiersGlobal.Roams, 1);
actor.SetMobMod(mobModifiersGlobal.RoamDelay, 3);
actor.SetMobMod(mobModifiersGlobal.RoamDelay, 10);
actor.charaWork.parameterSave.state_mainSkillLevel = 52;
actor.moveState = 3;
end
end

View File

@ -12,7 +12,7 @@ end;
function onSkillFinish(caster, target, skill, action, actionContainer)
--calculate damage
action.amount = skill.basePotency;
action.statusMagnitude = 15;
skill.statusMagnitude = 15;
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);

View File

@ -12,22 +12,16 @@ end;
--Increased damage and conversion to single target
function onCombo(caster, target, spell)
spell.aoeType = 0;
spell.potency = spell.potency * 1.5;
spell.basePotency = spell.basePotency * 1.5;
end;
function onSkillFinish(caster, target, skill, action, actionContainer)
--Dispels an effect on each target.
local effects = target.statusEffects.GetStatusEffectsByFlag2(16); --lose on dispel
if effects != nil then
target.statusEffects.RemoveStatusEffect(effects[0]);
end;
--calculate damage
action.amount = skill.basePotency;
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);
--Try to apply status effect
action.TryStatus(caster, target, skill, actionContainer, true);
--Dispels an effect on each target.
target.statusEffects.RemoveStatusEffect(GetRandomEffectByFlag(8), actionContainer, 30336);
end;

View File

@ -1,5 +1,6 @@
require("global");
require("magic");
require("modifiers");
function onMagicPrepare(caster, target, spell)
return 0;
@ -14,7 +15,7 @@ function onSkillFinish(caster, target, skill, action, actionContainer)
--2.5 HP per Healing Magic Potency
--0.5 HP per MND
--this is WITH WHM AF chest, don't know formula without AF. AF seems to increase healing by 7-10%?
action.amount = 2.5 * caster.GetMod(modifiersGlobal.MagicHeal) + 0.5 * (caster.GetMod(modifiersGlobal.Mind));
action.amount = 2.5 * caster.GetMod(modifiersGlobal.HealingMagicPotency) + 0.5 * (caster.GetMod(modifiersGlobal.Mind));
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);

View File

@ -30,7 +30,7 @@ function onSkillFinish(caster, target, skill, action, actionContainer)
hpPerMND = 0.25;
end
action.amount = hpPerHMP * caster.GetMod(modifiersGlobal.MagicHeal) + hpPerMND * (caster.GetMod(modifiersGlobal.Mind));
action.amount = hpPerHMP * caster.GetMod(modifiersGlobal.HealingMagicPotency) + hpPerMND * (caster.GetMod(modifiersGlobal.Mind));
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);

View File

@ -0,0 +1,22 @@
require("global");
require("magic");
require("battleutils");
function onMagicPrepare(caster, target, spell)
if not target.statusEffects.HasStatusEffectsByFlag(StatusEffectFlags.LoseOnEsuna) then
return -1
end
return 0;
end;
function onMagicStart(caster, target, spell)
return 0;
end;
function onSkillFinish(caster, target, skill, action, actionContainer)
removeEffect = target.statusEffects.GetRandomEffectByFlag(StatusEffectFlags.LoseOnEsuna)
target.statusEffects.RemoveStatusEffect(removeEffect, actionContainer, 30331);
end;

View File

@ -11,7 +11,7 @@ end;
function onSkillFinish(caster, target, skill, action, actionContainer)
--calculate damage
action.amount = skill.basePotency;
action.amount = 5000;-- skill.basePotency;
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);

View File

@ -12,6 +12,7 @@ end;
function onSkillFinish(caster, target, skill, action, actionContainer)
--calculate damage
action.amount = skill.basePotency;
skill.statusMagnitude = 20;
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);

View File

@ -24,6 +24,6 @@ function onSkillFinish(caster, target, skill, action, actionContainer)
if caster != target then
caster.AddHP(action.amount / 2)
--33012: You recover [amount] HP.
actionContainer.AddHPAction(caster.actorId, 33012, (action.amount / 2));
actionContainer.AddHPAbsorbAction(caster.actorId, 33012, (action.amount / 2));
end
end;

View File

@ -12,7 +12,7 @@ end;
function onSkillFinish(caster, target, skill, action, actionContainer)
--Actual amount of def/mdef will be calculated in OnGain
skill.statusMagnitude = caster.GetMod(modifiersGlobal.MagicEnhancePotency);
skill.statusMagnitude = caster.GetMod(modifiersGlobal.EnhancementMagicPotency);
--27365: Enhanced Protect: Increases magic defense gained from Protect.
if caster.HasTrait(27365) then

View File

@ -15,4 +15,5 @@ end;
--Not sure how raise works yet.
function onSkillFinish(caster, target, skill, action, actionContainer)
action.DoAction(caster, target, skill, actionContainer)
end;

View File

@ -25,7 +25,7 @@ function onSkillFinish(caster, target, skill, action, actionContainer)
intercept = intercept * 1.25;
end
local regenTick = (slope * caster.GetMod(modifiersGlobal.MagicEnhancePotency)) + intercept) + 1;
local regenTick = (slope * caster.GetMod(modifiersGlobal.EnhancementMagicPotency)) + intercept + 1;
spell.statusMagnitude = regenTick;

View File

@ -12,7 +12,7 @@ end;
function onSkillFinish(caster, target, skill, action, actionContainer)
--calculate damage
action.amount = skill.basePotency;
action.statusMagnitude = 50;
skill.statusMagnitude = 50;
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);

View File

@ -1,5 +1,6 @@
require("global");
require("magic");
require("modifiers")
function onMagicPrepare(caster, target, spell)
return 0;
@ -19,7 +20,7 @@ function onSkillFinish(caster, target, skill, action, actionContainer)
hpPerPoint = 1.96;
end
spell.statusMagnitude = hpPerPoint * caster.GetMod(modifiersGlobal.MagicEnhancePotency);
skill.statusMagnitude = hpPerPoint * caster.GetMod(modifiersGlobal.EnhancementMagicPotency);
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);

View File

@ -0,0 +1,35 @@
require("global");
require("magic");
function onMagicPrepare(caster, target, skill)
return 0;
end;
function onMagicStart(caster, target, skill)
return 0;
end;
function onSkillFinish(caster, target, skill, action, actionContainer)
--223224: Swiftsong
--223254: Ballad Of Magi
--223256: Minuet of Rigor
--If target has one of these effects that was from this caster, remove it
local oldSong;
local paeon = target.statusEffects.GetStatusEffectById(223255);
local ballad = target.statusEffects.GetStatusEffectById(223254);
local minuet = target.statusEffects.GetStatusEffectById(223256);
if paeon and paeon.GetSource() == caster then
oldSong = paeon;
elseif ballad and ballad.GetSource() == caster then
oldSong = ballad;
elseif minuet and minuet.GetSource() == caster then
oldSong = minuet;
end
if oldSong then
target.statusEffects.RemoveStatusEffect(oldSong);
end
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);
end;

View File

@ -0,0 +1,17 @@
require("global");
require("ability");
function onAbilityPrepare(caster, target, ability)
return 0;
end;
function onAbilityStart(caster, target, ability)
return 0;
end;
function onSkillFinish(caster, target, skill, action, actionContainer)
action.amount = skill.basePotency;
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);
end;

View File

@ -0,0 +1,17 @@
require("global");
require("ability");
function onAbilityPrepare(caster, target, ability)
return 0;
end;
function onAbilityStart(caster, target, ability)
return 0;
end;
function onSkillFinish(caster, target, skill, action, actionContainer)
action.amount = skill.basePotency;
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);
end;

View File

@ -12,10 +12,7 @@ end;
--Dispel
--Does dispel have a text id?
function onCombo(caster, target, skill)
local effects = target.statusEffects.GetStatusEffectsByFlag(16); --lose on dispel
if effects != nil then
target.statusEffects.RemoveStatusEffect(effects[0]);
end;
return 0;
end;
function onSkillFinish(caster, target, skill, action, actionContainer)
@ -25,6 +22,7 @@ function onSkillFinish(caster, target, skill, action, actionContainer)
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);
--Try to apply status effect
action.TryStatus(caster, target, skill, actionContainer, true);
if skill.isCombo then
target.statusEffects.RemoveStatusEffect(GetRandomEffectByFlag(8), actionContainer, 30336);
end
end;

View File

@ -1,5 +1,6 @@
require("global");
require("weaponskill");
require("utils");
function onSkillPrepare(caster, target, skill)
return 0;
@ -16,7 +17,7 @@ end;
function onSkillFinish(caster, target, skill, action, actionContainer)
--calculate ws damage
action.amount = skill.basePotency;
action.amount = 5000;--skill.basePotency;
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);

View File

@ -21,6 +21,9 @@ function onSkillFinish(caster, target, skill, action, actionContainer)
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);
--Try to apply status effect
action.TryStatus(caster, target, skill, actionContainer, true);
--Status only seems to apply on the first hit
if(action.ActionLanded() and action.hitNum == 1) then
--Try to apply status effect
action.TryStatus(caster, target, skill, actionContainer, true);
end
end;

View File

@ -1,20 +1,16 @@
require("global");
require("weaponskill");
require("modifiers")
function onSkillPrepare(caster, target, skill)
return 0;
end;
function onSkillStart(caster, target, skill)
return 0;
end;
--Increased crit rate
function onCombo(caster, target, skill)
--Get Berserk statuseffect
local berserk = caster.statusEffects.GetStatusEffectById(223160);
--if it isn't nil, remove the AP and Defense mods and reset extra to 0, increase potency
--if it isn't nil, remove the AP and Defense mods and reset extra to 0, increase accuracy
if berserk != nil then
local apPerHit = 20;
local defPerHit = 20;
@ -23,14 +19,20 @@ function onCombo(caster, target, skill)
apPerHit = 24;
end
attacker.SubtractMod(modifiersGlobal.Attack, apPerHit * berserk.GetExtra());
attacker.Add(modifiersGlobal.Defense, defPerHit * berserk.GetExtra());
caster.SubtractMod(modifiersGlobal.Attack, apPerHit * berserk.GetExtra());
caster.Add(modifiersGlobal.Defense, defPerHit * berserk.GetExtra());
berserk.SetExtra(0);
--This is about 50% crit. Don't know if that's what it gave on retail but seems kind of reasonable
skill.critRateBonus = 300;
skill.accuracyModifier = 50;
end;
return 0;
end;
--Increased crit rate
function onCombo(caster, target, skill)
--This is about 25% crit. Don't know if that's what it gave on retail but seems kind of reasonable
skill.critRateBonus = 200;
end;
function onSkillFinish(caster, target, skill, action, actionContainer)

View File

@ -15,7 +15,7 @@ end;
function onSkillFinish(caster, target, skill, action, actionContainer)
--calculate ws damage
skill.Potency = 100;
skill.basePotency = 100;
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);

View File

@ -1,11 +1,27 @@
require("global");
require("weaponskill");
require("modifiers")
function onSkillPrepare(caster, target, skill)
return 0;
end;
--Resets rampage to increase damage
function onSkillStart(caster, target, skill)
--Get Rampage statuseffect
local rampage = caster.statusEffects.GetStatusEffectById(223208);
--if it isn't nil, remove the AP and Defense mods and reset extra to 0, increase potency
if rampage != nil then
local parryPerDT = 20;
local delayMsPerDT = 100;
caster.SubtractMod(modifiersGlobal.Parry, parryPerDT * rampage.GetExtra());
caster.AddMod(modifiersGlobal.Delay, delayMsPerDT * rampage.GetExtra());
rampage.SetExtra(0);
skill.basePotency = skill.basePotency * 1.5;
end;
return 0;
end;
@ -15,6 +31,10 @@ function onCombo(caster, target, skill)
end;
function onSkillFinish(caster, target, skill, action, actionContainer)
if target.target == caster then
skill.statusId = 223015
end;
--calculate ws damage
action.amount = skill.basePotency;
@ -23,4 +43,6 @@ function onSkillFinish(caster, target, skill, action, actionContainer)
--Try to apply status effect
action.TryStatus(caster, target, skill, actionContainer, true);
skill.statusId = 0;
end;

View File

@ -28,7 +28,7 @@ function onSkillFinish(caster, target, skill, action, actionContainer)
--1.21: Equation used to calculate amount of MP adjusted.
--fug
--This might mean max MP isn't involved and the difference was between patches. need to recheck videos
if action.GetHitType() > HitType.Evade and (action.param == HitDirection.Right or action.param == HitDirection.Left) then
if action.ActionLanded() and (action.param == HitDirection.Right or action.param == HitDirection.Left) then
local mpToReturn = 0;
if skill.isCombo then
@ -39,6 +39,6 @@ function onSkillFinish(caster, target, skill, action, actionContainer)
caster.AddMP(mpToReturn);
--30452: You recover x MP.
actionContainer.AddMPAction(caster.actorId, 30452, mpToReturn);
actionContainer.AddMPAbsorbAction(caster.actorId, 30452, mpToReturn);
end
end;

View File

@ -0,0 +1,17 @@
require("global");
require("ability");
function onAbilityPrepare(caster, target, ability)
return 0;
end;
function onAbilityStart(caster, target, ability)
return 0;
end;
function onSkillFinish(caster, target, skill, action, actionContainer)
action.amount = skill.basePotency;
--DoAction handles rates, buffs, dealing damage
action.DoAction(caster, target, skill, actionContainer);
end;

View File

@ -1,5 +1,6 @@
require("global");
require("weaponskill");
require("modifiers")
function onSkillPrepare(caster, target, skill)
return 0;
@ -23,8 +24,8 @@ function onCombo(caster, target, skill)
apPerHit = 24;
end
attacker.SubtractMod(modifiersGlobal.Attack, apPerHit * berserk.GetExtra());
attacker.Add(modifiersGlobal.Defense, defPerHit * berserk.GetExtra());
caster.SubtractMod(modifiersGlobal.Attack, apPerHit * berserk.GetExtra());
caster.Add(modifiersGlobal.Defense, defPerHit * berserk.GetExtra());
berserk.SetExtra(0);
skill.basePotency = skill.basePotency * 1.5;

View File

@ -3,17 +3,17 @@ require("utils")
--Forces a full block (0 damage taken)
function onPreAction(effect, caster, target, skill, action, actionContainer)
--If action hit from the rear and is a weaponskill ation
--Can aegis boon block rear attacks or non-physical attacks?
action.blockRate = 100.0;
end;
--Heals for the amount of HP blocked, up to a certain point. I don't know what determines the cap but it seems to be 703 at level 50. Unsure if it scales down based on level, dlvl, or if that's an arbitrary cap added.
function onBlock(effect, attacker, defender, action, actionContainer)
function onBlock(effect, attacker, defender, skill, action, actionContainer)
--Amount blocked
local absorbAmount = math.Clamp(action.amountMitigated, 0, 703);
--33008: You recover x HP from Aegis Boon
defender.AddHP(absorbAmount);
actionContainer.AddHPAction(defender.actorId, 33008, absorbAmount);
actionContainer.AddAction(defender.statusEffects.RemoveStatusEffectForBattleAction(effect));
defender.statusEffects.RemoveStatusEffect(effect, actionContainer);
end;

View File

@ -1,10 +1,10 @@
require("modifiers")
--Doesn't do flat damage. 20 on Lv 50 Truffle Hog, 11 on Coincounter, 7 on nael hard, 19 on 52 fachan
function onGain(owner, effect)
function onGain(owner, effect, actionContainer)
owner.AddMod(modifiersGlobal.RegenDown, effect.GetMagnitude());
end;
function onLose(owner, effect)
function onLose(owner, effect, actionContainer)
owner.AddMod(modifiersGlobal.RegenDown, effect.GetMagnitude());
end;

View File

@ -1,10 +1,10 @@
require("modifiers")
function onGain(owner, effect)
function onGain(owner, effect, actionContainer)
--Only one song per bard can be active, need to figure out a good way to do this
owner.AddMod(modifiersGlobal.Refresh, effect.GetMagnitude());
end;
function onLose(owner, effect)
function onLose(owner, effect, actionContainer)
owner.SubtractMod(modifiersGlobal.Refresh, effect.GetMagnitude());
end;

View File

@ -8,6 +8,6 @@ end;
function onCommandFinish(effect, owner, skill, actionContainer)
--27259: Light Shot
if skill.id == 27259 then
actionContainer.AddAction(owner.statusEffects.RemoveStatusEffectForBattleAction(effect));
owner.statusEffects.RemoveStatusEffect(effect, actionContainer, 30331, false);
end
end;

View File

@ -1,3 +1,8 @@
require("modifiers")
--BV doesn't really do anything i think
--BV doesn't really do anything i think
function onGain(owner, effect, actionContainer)
end;
function onLose(owner, effect, actionContainer)
end;

View File

@ -1,13 +1,12 @@
require("modifiers");
function onGain(owner, effect)
owner.statusEffects.RemoveStatusEffect(223208);
function onGain(owner, effect, actionContainer)
end
--Increases attack power and reduces defense with each successful attack
--Does this include weaponskills?
--Is this on every hit or every succesfull skill useage?
function onHit(effect, attacker, defender, action, actionContainer)
function onHit(effect, attacker, defender, skill, action, actionContainer)
--Trait increases effect by 20%. Does this include the reduced defense,
--does this increase the cap or the rate at which you get AP or both?
@ -32,7 +31,7 @@ function onHit(effect, attacker, defender, action, actionContainer)
end
end;
function onDamageTaken(effect, attacker, defender, action, actionContainer)
function onDamageTaken(effect, attacker, defender, skill, action, actionContainer)
local apPerHit = 20;
local defPerHit = 20;
@ -45,7 +44,7 @@ function onDamageTaken(effect, attacker, defender, action, actionContainer)
effect.SetExtra(0);
end
function onLose(owner, effect)
function onLose(owner, effect, actionContainer)
local apPerHit = 20;
local defPerHit = 20;

View File

@ -1,7 +1,7 @@
require("modifiers");
function onGain(target, effect)
function onGain(owner, effect, actionContainer)
end;
function onLose(target, effect)
function onLose(owner, effect, actionContainer)
end;

View File

@ -1,9 +1,9 @@
require("modifiers")
function onGain(owner, effect)
function onGain(owner, effect, actionContainer)
owner.SubtractMod(modifiersGlobal.Accuracy, effect.GetMagnitude());
end;
function onLose(owner, effect)
function onLose(owner, effect, actionContainer)
owner.AddMod(modifiersGlobal.Accuracy, effect.GetMagnitude());
end;

View File

@ -4,12 +4,12 @@ require("battleutils")
--Forces crit of a single WS action from rear.
function onPreAction(effect, caster, target, skill, action, actionContainer)
--If action hit from the rear and is a weaponskill ation
if (action.param == HitDirection.Rear and action.commandType == CommandType.WeaponSkill) then
if (action.param == HitDirection.Rear and skill.GetCommandType() == CommandType.WeaponSkill) then
--Set action's crit rate to 100%
action.critRate = 100.0;
end
--Remove status and add message
actionsList.AddAction(target.statusEffects.RemoveForBattleAction(effect));
target.statusEffects.RemoveStatusEffect(effect, actionContainer);
end;

View File

@ -0,0 +1,17 @@
require("modifiers")
require("battleutils")
--Forces crit of a single WS action from rear.
function onPreAction(effect, caster, target, skill, action, actionContainer)
--If action hit from the rear and is a weaponskill ation
if (action.param == HitDirection.Rear and skill.GetCommandType() == CommandType.WeaponSkill) then
--Set action's crit rate to 100%
action.critRate = 100.0;
end
--Figure out INT bonus for tier 2
--Remove status and add message
target.statusEffects.RemoveStatusEffect(effect, actionContainer);
end;

View File

@ -2,15 +2,13 @@
--Based on a few videos it seems like it heals for 0.5% of max MP every second, traited. This is an early guess but it seems correct
--Untraited is less clear. It could be 0.25%, 0.30%, or 0.40%. Guessing it's 0.30
function onTick(owner, effect)
function onTick(owner, effect, actionContainer)
local percentPerSecond = 0.0030;
if effect.GetTier() == 2 then
percentPerSecond = 0.005;
end
print(effect.GetExtra());
local amount = percentPerSecond * owner.GetMaxMP() + 0.25;
effect.SetExtra(effect.GetExtra() + amount);
if effect.GetExtra() >= effect.GetMagnitude() then

View File

@ -1,3 +1,3 @@
function onLose(target, effect)
target:SetProc(1, false);
function onLose(owner, effect, actionContainer)
owner:SetProc(1, false);
end;

View File

@ -3,7 +3,7 @@ require("battleUtils")
--Takes 10% of hp rounded down when using a weaponskill
--Random guess, but increases damage by 10% (12.5% traited)?
function onPreAction(effect, caster, target, skill, action, actionContainer)
if skill.commandType == CommandType.Weaponskill then
if skill.GetCommandType() == CommandType.Weaponskill then
local hpToRemove = math.floor(caster.GetHP() * 0.10);
local modifier = 1.10;
@ -12,9 +12,9 @@ function onPreAction(effect, caster, target, skill, action, actionContainer)
end
action.amount = action.amount * modifier;
caster.DelHP(hpToRemove);
caster.DelHP(hpToRemove, actionContainer);
--Remove status and add message
actionContainer.AddAction(target.statusEffects.RemoveForBattleAction(effect));
--Remove status and add message
caster.statusEffects.RemoveStatusEffect(effect, actionContainer, 30331, true);
end
end;

View File

@ -1,7 +1,8 @@
require("modifiers");
require("battleutils")
--Absorb HP on next WS or ability
function onHit(effect, attacker, defender, action, actionContainer)
function onHit(effect, attacker, defender, skill, action, actionContainer)
--1.21: Absorb HP amount no longer affected by player VIT rating.
--Bloodbath seems based on both defener and attacker's stats, even after 1.21.
@ -11,14 +12,14 @@ function onHit(effect, attacker, defender, action, actionContainer)
--Possibly magic resist? Slashing resist?
--For now using 1.0 as baseline since that seems to be the average
if action.commandType == CommandType.Weaponskill or action.commandType == CommandType.Ability then
if skill.GetCommandType() == CommandType.Weaponskill or skill.GetCommandType() == CommandType.Ability then
local absorbModifier = 1.0
local absorbAmount = action.amount * absorbModifier;
attacker.AddHP(absorbAmount);
--30332: You absorb hp from target
actionContainer.AddHPAction(defender.actorId, 30332, absorbAmount)
actionContainer.AddHPAbsorbAction(defender.actorId, 30332, absorbAmount)
--Bloodbath is lost after absorbing hp
actionContainer.AddAction(defender.statusEffects.RemoveStatusEffectForBattleAction(effect));
defender.statusEffects.RemoveStatusEffect(effect,actionContainer, 30331, false);
end
end;

View File

@ -8,13 +8,14 @@ require("modifiers")
--Bloodletter is apparently impacted by PIE
--http://forum.square-enix.com/ffxiv/threads/35795-STR-DEX-PIE-ATK-Testing/page2
--Chance to land is also impacted by PIE
function onGain(owner, effect)
--This is because PIE increases Enfeebling Magic Potency which impacts additional effect damage and land rates
function onGain(owner, effect, actionContainer)
owner.AddMod(modifiersGlobal.RegenDown, 15);
end
--Additional damage is 570 at level 50
--https://ffxiv.gamerescape.com/w/index.php?title=Bloodletter&oldid=298020
function onLose(owner, effect)
function onLose(owner, effect, actionContainer)
owner.SubtractMod(modifiersGlobal.RegenDown, 15);
owner.DelHP(570);
owner.DelHP(570, actionContainer);
end

View File

@ -1,10 +1,10 @@
require("modifiers")
--Bloodletter2 is the uncomboed version of Bloodletter. It doesn't deal any additional damage when it falls off but has the same tick damage
function onGain(owner, effect)
function onGain(owner, effect, actionContainer)
owner.AddMod(modifiersGlobal.RegenDown, 15);
end
function onLose(owner, effect)
function onLose(owner, effect, actionContainer)
owner.SubtractMod(modifiersGlobal.RegenDown, 15);
end

View File

@ -1,12 +1,12 @@
require("modifiers")
function onGain(target, effect)
function onGain(owner, effect, actionContainer)
--Multiples Attack Magic Potency by 1.2 and Healing Magic Potency by 0.8
target.SetMod(modifiersGlobal.MagicAttack, target.GetMod(modifiersGlobal.MagicAttack) * 1.2);
target.SetMod(modifiersGlobal.MagicHeal, target.GetMod(modifiersGlobal.MagicHeal) * 0.8);
owner.MultiplyMod(modifiersGlobal.AttackMagicPotency, 1.2);
owner.MultiplyMod(modifiersGlobal.HealingMagicPotency, 0.8);
end;
function onLose(target, effect)
target.SetMod(modifiersGlobal.MagicAttack, target.GetMod(modifiersGlobal.MagicAttack) / 1.2);
target.SetMod(modifiersGlobal.MagicHeal, target.GetMod(modifiersGlobal.MagicHeal) / 0.8);
function onLose(owner, effect, actionContainer)
owner.DivideMod(modifiersGlobal.AttackMagicPotency, 1.2);
owner.DivideMod(modifiersGlobal.HealingMagicPotency, 0.8);
end;

View File

@ -1,10 +1,10 @@
require("modifiers")
function onHit(effect, attacker, defender, action, actionContainer)
function onHit(effect, attacker, defender, skill, action, actionContainer)
local enmity = action.enmity;
action.enmity = 0;
defender.hateContainer.UpdateHate(effect.GetSource(), enmity);
--Does collusion send a message?
actionContainer.AddAction(attacker.statusEffects.RemoveStatusEffectForBattleAction(effect));
defender.statusEffects.RemoveStatusEffect(effect, actionContainer, 30331, false);
end;

View File

@ -1,3 +1,6 @@
function onLose(target, effect)
target:SetCombos();
function onGain(owner, effect, actionContainer)
end;
function onLose(owner, effect, actionContainer)
owner:SetCombos();
end;

View File

@ -1,7 +1,7 @@
require("modifiers")
--Enahnced Cover: Restores 25% of damage taken as MP. Does not send a message
function onDamageTaken(effect, attacker, defender, action, actionContainer)
function onDamageTaken(effect, attacker, defender, skill, action, actionContainer)
if effect.GetTier() == 2 then
defender.AddMP(0.25 * action.amount);
end

View File

@ -5,9 +5,9 @@ require("battleutils")
--There isn't really any information on this, but due to the fact it falls off BEFORE the target is hit,
--I'm assuming it increases a spell's accuracy modifier instead of giving actual magic accuracy
function onCommandStart(effect, owner, skill, actionContainer)
if skill.actionType == ActionType.Magic then
if skill.GetActionType() == ActionType.Magic then
--50 is random guess.
skill.accuracyModifier = skill.accuracyModifier + 50;
actionContainer.AddAction(owner.RemoveStatusEffectForBattleAction(effect));
owner.statusEffects.RemoveStatusEffect(effect, actionContainer, 30331, false);
end
end

View File

@ -5,12 +5,13 @@ require("battleutils")
function onPreAction(effect, caster, target, skill, action, actionContainer)
--Evade single ranged or magic attack
--Traited allows for physical attacks
if target.allegiance != caster.allegiance and (skill.isRanged or action.actionType == ActionType.Magic) then
if target.allegiance != caster.allegiance and (skill.isRanged or skill.GetActionType() == ActionType.Magic) then
--Unsure if decoy forces a miss/resist or if this is the one case where the evade hittype is used
--Set action's hit rate to 0
action.hirRate = 0.0;
action.hitRate = 0.0;
action.resistRate = 750;
--Remove status and add message
actionContainer.AddAction(target.statusEffects.RemoveStatusEffectForBattleAction(effect));
defender.statusEffects.RemoveStatusEffect(effect, actionContainer, 30331, false);
end
end;

View File

@ -5,11 +5,12 @@ require("battleutils")
function onPreAction(effect, caster, target, skill, action, actionContainer)
--Evade single ranged or magic attack
--Traited allows for physical attacks
if target.allegiance != caster.allegiance and (skill.isRanged or action.actionType == ActionType.Magic or action.actionType == ActionType.Physical) then
if target.allegiance != caster.allegiance and (skill.isRanged or skill.GetActionType() == ActionType.Magic or skill.GetActionType() == ActionType.Physical) then
--Set action's hit rate to 0
action.hirRate = 0.0;
action.hitRate = 0.0;
action.resistRate = 400;
--Remove status and add message
actionContainer.AddAction(target.statusEffects.RemoveStatusEffectForBattleAction(effect));
defender.statusEffects.RemoveStatusEffect(effect, actionContainer, 30331, false);
end
end;

View File

@ -1,5 +1,5 @@
function onGain(target, effect)
function onGain(owner, effect, actionContainer)
end;
function onLose(target, effect)
function onLose(owner, effect, actionContainer)
end;

View File

@ -1,9 +1,9 @@
require("modifiers")
function onGain(owner, effect)
function onGain(owner, effect, actionContainer)
owner.SubtractMod(modifiersGlobal.Defense, effect.GetMagnitude());
end
function onLose(owner, effect)
function onLose(owner, effect, actionContainer)
owner.AddMod(modifiersGlobal.Defense, effect.GetMagnitude());
end

Some files were not shown because too many files have changed in this diff Show More