mirror of
https://bitbucket.org/Ioncannon/project-meteor-server.git
synced 2025-04-02 19:42:05 -04:00
Merged in skeletonhorn/ffxiv-classic-server-ai-fork/ai-open (pull request #5)
Combat Changes
This commit is contained in:
commit
8c9f841d63
@ -12,7 +12,7 @@ using FFXIVClassic_Map_Server.Actors;
|
|||||||
using FFXIVClassic_Map_Server.actors.chara.player;
|
using FFXIVClassic_Map_Server.actors.chara.player;
|
||||||
using FFXIVClassic_Map_Server.packets.receive.supportdesk;
|
using FFXIVClassic_Map_Server.packets.receive.supportdesk;
|
||||||
using FFXIVClassic_Map_Server.actors.chara.ai;
|
using FFXIVClassic_Map_Server.actors.chara.ai;
|
||||||
using FFXIVClassic_Map_Server.Actors.Chara;
|
using FFXIVClassic_Map_Server.packets.send.actor.battle;
|
||||||
using FFXIVClassic_Map_Server.actors.chara;
|
using FFXIVClassic_Map_Server.actors.chara;
|
||||||
|
|
||||||
namespace FFXIVClassic_Map_Server
|
namespace FFXIVClassic_Map_Server
|
||||||
@ -964,7 +964,7 @@ namespace FFXIVClassic_Map_Server
|
|||||||
var effect = Server.GetWorldManager().GetStatusEffect(id);
|
var effect = Server.GetWorldManager().GetStatusEffect(id);
|
||||||
if (effect != null)
|
if (effect != null)
|
||||||
{
|
{
|
||||||
effect.SetDurationMs(duration);
|
effect.SetDuration(duration);
|
||||||
effect.SetMagnitude(magnitude);
|
effect.SetMagnitude(magnitude);
|
||||||
effect.SetTickMs(tick);
|
effect.SetTickMs(tick);
|
||||||
effect.SetTier(tier);
|
effect.SetTier(tier);
|
||||||
@ -1276,8 +1276,7 @@ namespace FFXIVClassic_Map_Server
|
|||||||
}
|
}
|
||||||
public static void EquipAbility(Player player, byte classId, ushort hotbarSlot, uint commandId, uint recastTime)
|
public static void EquipAbility(Player player, byte classId, ushort hotbarSlot, uint commandId, uint recastTime)
|
||||||
{
|
{
|
||||||
//2700083201 is where abilities start. 2700083200 is for unequipping abilities. Trying to put this in the hotbar will crash the game, need to put 0 instead
|
if (commandId > 0)
|
||||||
if (commandId > 2700083200)
|
|
||||||
{
|
{
|
||||||
using (MySqlConnection conn = new MySqlConnection(
|
using (MySqlConnection conn = new MySqlConnection(
|
||||||
String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}",
|
String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}",
|
||||||
@ -1321,7 +1320,7 @@ namespace FFXIVClassic_Map_Server
|
|||||||
UnequipAbility(player, hotbarSlot);
|
UnequipAbility(player, hotbarSlot);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Unequipping is done by sending an equip packet with 2700083200 as the ability and the hotbar slot of the action being unequipped
|
//Unequipping is done by sending an equip packet with 0xA0F00000 as the ability and the hotbar slot of the action being unequipped
|
||||||
public static void UnequipAbility(Player player, ushort hotbarSlot)
|
public static void UnequipAbility(Player player, ushort hotbarSlot)
|
||||||
{
|
{
|
||||||
using (MySqlConnection conn = new MySqlConnection(
|
using (MySqlConnection conn = new MySqlConnection(
|
||||||
@ -1338,8 +1337,6 @@ namespace FFXIVClassic_Map_Server
|
|||||||
MySqlCommand cmd;
|
MySqlCommand cmd;
|
||||||
string query = "";
|
string query = "";
|
||||||
|
|
||||||
//Drop
|
|
||||||
List<Tuple<ushort, uint>> hotbarList = new List<Tuple<ushort, uint>>();
|
|
||||||
query = @"
|
query = @"
|
||||||
DELETE FROM characters_hotbar
|
DELETE FROM characters_hotbar
|
||||||
WHERE characterId = @charId AND classId = @classId AND hotbarSlot = @hotbarSlot
|
WHERE characterId = @charId AND classId = @classId AND hotbarSlot = @hotbarSlot
|
||||||
@ -1377,7 +1374,7 @@ namespace FFXIVClassic_Map_Server
|
|||||||
SELECT
|
SELECT
|
||||||
hotbarSlot,
|
hotbarSlot,
|
||||||
commandId,
|
commandId,
|
||||||
recastTime
|
recastTime
|
||||||
FROM characters_hotbar WHERE characterId = @charId AND classId = @classId
|
FROM characters_hotbar WHERE characterId = @charId AND classId = @classId
|
||||||
ORDER BY hotbarSlot";
|
ORDER BY hotbarSlot";
|
||||||
|
|
||||||
@ -1385,7 +1382,7 @@ namespace FFXIVClassic_Map_Server
|
|||||||
cmd.Parameters.AddWithValue("@charId", player.actorId);
|
cmd.Parameters.AddWithValue("@charId", player.actorId);
|
||||||
cmd.Parameters.AddWithValue("@classId", player.GetCurrentClassOrJob());
|
cmd.Parameters.AddWithValue("@classId", player.GetCurrentClassOrJob());
|
||||||
|
|
||||||
player.charaWork.commandBorder = 32;
|
player.charaWork.commandBorder = 32;
|
||||||
|
|
||||||
using (MySqlDataReader reader = cmd.ExecuteReader())
|
using (MySqlDataReader reader = cmd.ExecuteReader())
|
||||||
{
|
{
|
||||||
@ -1393,13 +1390,13 @@ namespace FFXIVClassic_Map_Server
|
|||||||
{
|
{
|
||||||
int hotbarSlot = reader.GetUInt16("hotbarSlot");
|
int hotbarSlot = reader.GetUInt16("hotbarSlot");
|
||||||
uint commandId = reader.GetUInt32("commandId");
|
uint commandId = reader.GetUInt32("commandId");
|
||||||
player.charaWork.command[hotbarSlot + player.charaWork.commandBorder] = commandId | 0xA0F00000;
|
player.charaWork.command[hotbarSlot + player.charaWork.commandBorder] = 0xA0F00000 | commandId;
|
||||||
player.charaWork.commandCategory[hotbarSlot + player.charaWork.commandBorder] = 1;
|
player.charaWork.commandCategory[hotbarSlot + player.charaWork.commandBorder] = 1;
|
||||||
player.charaWork.parameterSave.commandSlot_recastTime[hotbarSlot] = reader.GetUInt32("recastTime");
|
player.charaWork.parameterSave.commandSlot_recastTime[hotbarSlot] = reader.GetUInt32("recastTime");
|
||||||
|
|
||||||
//Recast timer
|
//Recast timer
|
||||||
BattleCommand ability = Server.GetWorldManager().GetBattleCommand((ushort)(commandId));
|
BattleCommand ability = Server.GetWorldManager().GetBattleCommand((ushort)(commandId));
|
||||||
player.charaWork.parameterTemp.maxCommandRecastTime[hotbarSlot] = (ushort) (ability != null ? ability.recastTimeSeconds : 1);
|
player.charaWork.parameterTemp.maxCommandRecastTime[hotbarSlot] = (ushort) (ability != null ? ability.maxRecastTimeSeconds : 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1448,7 +1445,7 @@ namespace FFXIVClassic_Map_Server
|
|||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
if (slot != reader.GetUInt16("hotbarSlot"))
|
if (slot != reader.GetUInt16("hotbarSlot"))
|
||||||
return slot;
|
break;
|
||||||
|
|
||||||
slot++;
|
slot++;
|
||||||
}
|
}
|
||||||
@ -1550,7 +1547,7 @@ namespace FFXIVClassic_Map_Server
|
|||||||
|
|
||||||
|
|
||||||
string query = @"
|
string query = @"
|
||||||
INSERT INTO server_items
|
INSERT INTO server_items
|
||||||
(itemId, quality, itemType, durability)
|
(itemId, quality, itemType, durability)
|
||||||
VALUES
|
VALUES
|
||||||
(@itemId, @quality, @itemType, @durability);
|
(@itemId, @quality, @itemType, @durability);
|
||||||
@ -1804,7 +1801,7 @@ namespace FFXIVClassic_Map_Server
|
|||||||
conn.Open();
|
conn.Open();
|
||||||
|
|
||||||
string query = @"
|
string query = @"
|
||||||
INSERT INTO server_linkshells
|
INSERT INTO server_linkshells
|
||||||
(name, master, crest)
|
(name, master, crest)
|
||||||
VALUES
|
VALUES
|
||||||
(@lsName, @master, @crest)
|
(@lsName, @master, @crest)
|
||||||
@ -2160,7 +2157,7 @@ namespace FFXIVClassic_Map_Server
|
|||||||
SET
|
SET
|
||||||
chocoboAppearance=@chocoboAppearance
|
chocoboAppearance=@chocoboAppearance
|
||||||
WHERE
|
WHERE
|
||||||
characterId = @characterId";
|
characterId = @characterId";
|
||||||
|
|
||||||
cmd = new MySqlCommand(query, conn);
|
cmd = new MySqlCommand(query, conn);
|
||||||
cmd.Parameters.AddWithValue("@characterId", player.actorId);
|
cmd.Parameters.AddWithValue("@characterId", player.actorId);
|
||||||
@ -2189,7 +2186,7 @@ namespace FFXIVClassic_Map_Server
|
|||||||
{
|
{
|
||||||
conn.Open();
|
conn.Open();
|
||||||
|
|
||||||
var query = @"SELECT id, name, flags, overwrite FROM server_statuseffects;";
|
var query = @"SELECT id, name, flags, overwrite, tickMs FROM server_statuseffects;";
|
||||||
|
|
||||||
MySqlCommand cmd = new MySqlCommand(query, conn);
|
MySqlCommand cmd = new MySqlCommand(query, conn);
|
||||||
|
|
||||||
@ -2201,8 +2198,9 @@ namespace FFXIVClassic_Map_Server
|
|||||||
var name = reader.GetString("name");
|
var name = reader.GetString("name");
|
||||||
var flags = reader.GetUInt32("flags");
|
var flags = reader.GetUInt32("flags");
|
||||||
var overwrite = reader.GetByte("overwrite");
|
var overwrite = reader.GetByte("overwrite");
|
||||||
|
var tickMs = reader.GetUInt32("tickMs");
|
||||||
var effect = new StatusEffect(id, name, flags, overwrite);
|
var effect = new StatusEffect(id, name, flags, overwrite, tickMs);
|
||||||
|
lua.LuaEngine.LoadStatusEffectScript(effect);
|
||||||
effects.Add(id, effect);
|
effects.Add(id, effect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2231,7 +2229,7 @@ namespace FFXIVClassic_Map_Server
|
|||||||
string queries = "";
|
string queries = "";
|
||||||
foreach (var effect in player.statusEffects.GetStatusEffects())
|
foreach (var effect in player.statusEffects.GetStatusEffects())
|
||||||
{
|
{
|
||||||
var duration = effect.GetDurationMs() + effect.GetStartTime().Millisecond - Program.Tick.Millisecond;
|
var duration = effect.GetDuration() + effect.GetStartTime().Second - Program.Tick.Second;
|
||||||
|
|
||||||
queries += Environment.NewLine + $"REPLACE INTO characters_statuseffect(characterId, statusId, magnitude, duration, tick, tier, extra) VALUES ({player.actorId}, {effect.GetStatusEffectId()}, {effect.GetMagnitude()}, {duration}, {effect.GetTickMs()}, {effect.GetTier()}, {effect.GetExtra()});";
|
queries += Environment.NewLine + $"REPLACE INTO characters_statuseffect(characterId, statusId, magnitude, duration, tick, tier, extra) VALUES ({player.actorId}, {effect.GetStatusEffectId()}, {effect.GetMagnitude()}, {duration}, {effect.GetTickMs()}, {effect.GetTier()}, {effect.GetExtra()});";
|
||||||
}
|
}
|
||||||
@ -2259,10 +2257,11 @@ namespace FFXIVClassic_Map_Server
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
int count = 0;
|
||||||
conn.Open();
|
conn.Open();
|
||||||
|
|
||||||
var query = ("SELECT `id`, name, classJob, lvl, requirements, validTarget, aoeType, aoeRange, aoeTarget, numHits, positionBonus, procRequirement, `range`, buffDuration, debuffDuration, " +
|
var query = ("SELECT `id`, name, classJob, lvl, requirements, mainTarget, validTarget, aoeType, aoeRange, aoeTarget, basePotency, numHits, positionBonus, procRequirement, `range`, statusId, statusDuration, statusChance, " +
|
||||||
"castType, castTime, recastTime, mpCost, tpCost, animationType, effectAnimation, modelAnimation, animationDuration, battleAnimation, validUser FROM server_battle_commands;");
|
"castType, castTime, recastTime, mpCost, tpCost, animationType, effectAnimation, modelAnimation, animationDuration, battleAnimation, validUser, comboId1, comboId2, comboStep, accuracyMod, worldMasterTextId, commandType, actionType, actionProperty FROM server_battle_commands;");
|
||||||
|
|
||||||
MySqlCommand cmd = new MySqlCommand(query, conn);
|
MySqlCommand cmd = new MySqlCommand(query, conn);
|
||||||
|
|
||||||
@ -2277,17 +2276,21 @@ namespace FFXIVClassic_Map_Server
|
|||||||
battleCommand.job = reader.GetByte("classJob");
|
battleCommand.job = reader.GetByte("classJob");
|
||||||
battleCommand.level = reader.GetByte("lvl");
|
battleCommand.level = reader.GetByte("lvl");
|
||||||
battleCommand.requirements = (BattleCommandRequirements)reader.GetUInt16("requirements");
|
battleCommand.requirements = (BattleCommandRequirements)reader.GetUInt16("requirements");
|
||||||
|
battleCommand.mainTarget = (ValidTarget)reader.GetByte("mainTarget");
|
||||||
battleCommand.validTarget = (ValidTarget)reader.GetByte("validTarget");
|
battleCommand.validTarget = (ValidTarget)reader.GetByte("validTarget");
|
||||||
battleCommand.aoeType = (TargetFindAOEType)reader.GetByte("aoeType");
|
battleCommand.aoeType = (TargetFindAOEType)reader.GetByte("aoeType");
|
||||||
|
battleCommand.basePotency = reader.GetUInt16("basePotency");
|
||||||
battleCommand.numHits = reader.GetByte("numHits");
|
battleCommand.numHits = reader.GetByte("numHits");
|
||||||
battleCommand.positionBonus = (BattleCommandPositionBonus)reader.GetByte("positionBonus");
|
battleCommand.positionBonus = (BattleCommandPositionBonus)reader.GetByte("positionBonus");
|
||||||
battleCommand.procRequirement = (BattleCommandProcRequirement)reader.GetByte("procRequirement");
|
battleCommand.procRequirement = (BattleCommandProcRequirement)reader.GetByte("procRequirement");
|
||||||
battleCommand.range = reader.GetInt32("range");
|
battleCommand.range = reader.GetInt32("range");
|
||||||
battleCommand.debuffDurationSeconds = reader.GetUInt32("debuffDuration");
|
battleCommand.statusId = reader.GetUInt32("statusId");
|
||||||
battleCommand.buffDurationSeconds = reader.GetUInt32("buffDuration");
|
battleCommand.statusDuration = reader.GetUInt32("statusDuration");
|
||||||
|
battleCommand.statusChance = reader.GetFloat("statusChance");
|
||||||
battleCommand.castType = reader.GetByte("castType");
|
battleCommand.castType = reader.GetByte("castType");
|
||||||
battleCommand.castTimeSeconds = reader.GetUInt32("castTime");
|
battleCommand.castTimeMs = reader.GetUInt32("castTime");
|
||||||
battleCommand.recastTimeSeconds = reader.GetUInt32("recastTime");
|
battleCommand.maxRecastTimeSeconds = reader.GetUInt32("recastTime");
|
||||||
|
battleCommand.recastTimeMs = battleCommand.maxRecastTimeSeconds * 1000;
|
||||||
battleCommand.mpCost = reader.GetUInt16("mpCost");
|
battleCommand.mpCost = reader.GetUInt16("mpCost");
|
||||||
battleCommand.tpCost = reader.GetUInt16("tpCost");
|
battleCommand.tpCost = reader.GetUInt16("tpCost");
|
||||||
battleCommand.animationType = reader.GetByte("animationType");
|
battleCommand.animationType = reader.GetByte("animationType");
|
||||||
@ -2299,7 +2302,16 @@ namespace FFXIVClassic_Map_Server
|
|||||||
|
|
||||||
battleCommand.battleAnimation = reader.GetUInt32("battleAnimation");
|
battleCommand.battleAnimation = reader.GetUInt32("battleAnimation");
|
||||||
battleCommand.validUser = (BattleCommandValidUser)reader.GetByte("validUser");
|
battleCommand.validUser = (BattleCommandValidUser)reader.GetByte("validUser");
|
||||||
|
|
||||||
|
battleCommand.comboNextCommandId[0] = reader.GetInt32("comboId1");
|
||||||
|
battleCommand.comboNextCommandId[1] = reader.GetInt32("comboId2");
|
||||||
|
battleCommand.comboStep = reader.GetInt16("comboStep");
|
||||||
|
battleCommand.commandType = (CommandType) reader.GetInt16("commandType");
|
||||||
|
battleCommand.actionProperty = (ActionProperty)reader.GetInt16("actionProperty");
|
||||||
|
battleCommand.actionType = (ActionType)reader.GetInt16("actionType");
|
||||||
|
battleCommand.accuracyModifier = reader.GetFloat("accuracyMod");
|
||||||
|
battleCommand.worldMasterTextId = reader.GetUInt16("worldMasterTextId");
|
||||||
|
lua.LuaEngine.LoadBattleCommandScript(battleCommand, "weaponskill");
|
||||||
battleCommandDict.Add(id, battleCommand);
|
battleCommandDict.Add(id, battleCommand);
|
||||||
|
|
||||||
Tuple<byte, short> tuple = Tuple.Create<byte, short>(battleCommand.job, battleCommand.level);
|
Tuple<byte, short> tuple = Tuple.Create<byte, short>(battleCommand.job, battleCommand.level);
|
||||||
@ -2312,8 +2324,65 @@ namespace FFXIVClassic_Map_Server
|
|||||||
List<uint> list = new List<uint>() { id | 0xA0F00000 };
|
List<uint> list = new List<uint>() { id | 0xA0F00000 };
|
||||||
battleCommandIdByLevel.Add(tuple, list);
|
battleCommandIdByLevel.Add(tuple, list);
|
||||||
}
|
}
|
||||||
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Program.Log.Info(String.Format("Loaded {0} battle commands.", count));
|
||||||
|
}
|
||||||
|
catch (MySqlException e)
|
||||||
|
{
|
||||||
|
Program.Log.Error(e.ToString());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
conn.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LoadGlobalBattleTraitList(Dictionary<ushort, BattleTrait> battleTraitDict, Dictionary<byte, List<ushort>> battleTraitJobDict)
|
||||||
|
{
|
||||||
|
using (MySqlConnection conn = new MySqlConnection(String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}", ConfigConstants.DATABASE_HOST, ConfigConstants.DATABASE_PORT, ConfigConstants.DATABASE_NAME, ConfigConstants.DATABASE_USERNAME, ConfigConstants.DATABASE_PASSWORD)))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
conn.Open();
|
||||||
|
|
||||||
|
var query = ("SELECT `id`, name, classJob, lvl, modifier, bonus FROM server_battle_traits;");
|
||||||
|
|
||||||
|
MySqlCommand cmd = new MySqlCommand(query, conn);
|
||||||
|
|
||||||
|
using (MySqlDataReader reader = cmd.ExecuteReader())
|
||||||
|
{
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
var id = reader.GetUInt16("id");
|
||||||
|
var name = reader.GetString("name");
|
||||||
|
var job = reader.GetByte("classJob");
|
||||||
|
var level = reader.GetByte("lvl");
|
||||||
|
uint modifier = reader.GetUInt32("modifier");
|
||||||
|
var bonus = reader.GetInt32("bonus");
|
||||||
|
|
||||||
|
var trait = new BattleTrait(id, name, job, level, modifier, bonus);
|
||||||
|
|
||||||
|
battleTraitDict.Add(id, trait);
|
||||||
|
|
||||||
|
if(battleTraitJobDict.ContainsKey(job))
|
||||||
|
{
|
||||||
|
battleTraitJobDict[job].Add(id);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
battleTraitJobDict[job] = new List<ushort>();
|
||||||
|
battleTraitJobDict[job].Add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Program.Log.Info(String.Format("Loaded {0} battle traits.", count));
|
||||||
}
|
}
|
||||||
catch (MySqlException e)
|
catch (MySqlException e)
|
||||||
{
|
{
|
||||||
|
@ -107,6 +107,7 @@
|
|||||||
<Compile Include="actors\area\PrivateAreaContent.cs" />
|
<Compile Include="actors\area\PrivateAreaContent.cs" />
|
||||||
<Compile Include="actors\area\SpawnLocation.cs" />
|
<Compile Include="actors\area\SpawnLocation.cs" />
|
||||||
<Compile Include="actors\area\Zone.cs" />
|
<Compile Include="actors\area\Zone.cs" />
|
||||||
|
<Compile Include="actors\chara\ai\BattleTrait.cs" />
|
||||||
<Compile Include="actors\chara\ai\controllers\AllyController.cs" />
|
<Compile Include="actors\chara\ai\controllers\AllyController.cs" />
|
||||||
<Compile Include="actors\chara\ai\helpers\ActionQueue.cs" />
|
<Compile Include="actors\chara\ai\helpers\ActionQueue.cs" />
|
||||||
<Compile Include="actors\chara\ai\AIContainer.cs" />
|
<Compile Include="actors\chara\ai\AIContainer.cs" />
|
||||||
@ -117,6 +118,7 @@
|
|||||||
<Compile Include="actors\chara\ai\HateContainer.cs" />
|
<Compile Include="actors\chara\ai\HateContainer.cs" />
|
||||||
<Compile Include="actors\chara\ai\helpers\PathFind.cs" />
|
<Compile Include="actors\chara\ai\helpers\PathFind.cs" />
|
||||||
<Compile Include="actors\chara\ai\BattleCommand.cs" />
|
<Compile Include="actors\chara\ai\BattleCommand.cs" />
|
||||||
|
<Compile Include="actors\chara\ai\state\AbilityState.cs" />
|
||||||
<Compile Include="actors\chara\ai\state\AttackState.cs" />
|
<Compile Include="actors\chara\ai\state\AttackState.cs" />
|
||||||
<Compile Include="actors\chara\ai\state\DeathState.cs" />
|
<Compile Include="actors\chara\ai\state\DeathState.cs" />
|
||||||
<Compile Include="actors\chara\ai\state\DespawnState.cs" />
|
<Compile Include="actors\chara\ai\state\DespawnState.cs" />
|
||||||
@ -217,6 +219,7 @@
|
|||||||
<Compile Include="packets\send\actor\ActorInstantiatePacket.cs" />
|
<Compile Include="packets\send\actor\ActorInstantiatePacket.cs" />
|
||||||
<Compile Include="packets\send\actor\ActorSpecialGraphicPacket.cs" />
|
<Compile Include="packets\send\actor\ActorSpecialGraphicPacket.cs" />
|
||||||
<Compile Include="packets\send\actor\battle\BattleAction.cs" />
|
<Compile Include="packets\send\actor\battle\BattleAction.cs" />
|
||||||
|
<Compile Include="packets\send\actor\battle\BattleActionContainer.cs" />
|
||||||
<Compile Include="packets\send\actor\battle\BattleActionX00Packet.cs" />
|
<Compile Include="packets\send\actor\battle\BattleActionX00Packet.cs" />
|
||||||
<Compile Include="packets\send\actor\battle\BattleActionX18Packet.cs" />
|
<Compile Include="packets\send\actor\battle\BattleActionX18Packet.cs" />
|
||||||
<Compile Include="packets\send\actor\battle\BattleActionX10Packet.cs" />
|
<Compile Include="packets\send\actor\battle\BattleActionX10Packet.cs" />
|
||||||
|
@ -54,9 +54,10 @@ namespace FFXIVClassic_Map_Server
|
|||||||
mWorldManager.LoadActorClasses();
|
mWorldManager.LoadActorClasses();
|
||||||
mWorldManager.LoadSpawnLocations();
|
mWorldManager.LoadSpawnLocations();
|
||||||
mWorldManager.LoadBattleNpcs();
|
mWorldManager.LoadBattleNpcs();
|
||||||
mWorldManager.SpawnAllActors();
|
|
||||||
mWorldManager.LoadStatusEffects();
|
mWorldManager.LoadStatusEffects();
|
||||||
mWorldManager.LoadBattleCommands();
|
mWorldManager.LoadBattleCommands();
|
||||||
|
mWorldManager.LoadBattleTraits();
|
||||||
|
mWorldManager.SpawnAllActors();
|
||||||
mWorldManager.StartZoneThread();
|
mWorldManager.StartZoneThread();
|
||||||
|
|
||||||
IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse(ConfigConstants.OPTIONS_BINDIP), int.Parse(ConfigConstants.OPTIONS_PORT));
|
IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse(ConfigConstants.OPTIONS_BINDIP), int.Parse(ConfigConstants.OPTIONS_PORT));
|
||||||
|
@ -41,10 +41,12 @@ namespace FFXIVClassic_Map_Server
|
|||||||
private Dictionary<uint, StatusEffect> statusEffectList = new Dictionary<uint, StatusEffect>();
|
private Dictionary<uint, StatusEffect> statusEffectList = new Dictionary<uint, StatusEffect>();
|
||||||
private Dictionary<ushort, BattleCommand> battleCommandList = new Dictionary<ushort, BattleCommand>();
|
private Dictionary<ushort, BattleCommand> battleCommandList = new Dictionary<ushort, BattleCommand>();
|
||||||
private Dictionary<Tuple<byte, short>, List<uint>> battleCommandIdByLevel = new Dictionary<Tuple<byte, short>, List<uint>>();//Holds battle command ids keyed by class id and level (in that order)
|
private Dictionary<Tuple<byte, short>, List<uint>> battleCommandIdByLevel = new Dictionary<Tuple<byte, short>, List<uint>>();//Holds battle command ids keyed by class id and level (in that order)
|
||||||
|
private Dictionary<ushort, BattleTrait> battleTraitList = new Dictionary<ushort, BattleTrait>();
|
||||||
|
private Dictionary<byte, List<ushort>> battleTraitIdsForClass = new Dictionary<byte, List<ushort>>();
|
||||||
private Dictionary<uint, ModifierList> battleNpcGenusMods = new Dictionary<uint, ModifierList>();
|
private Dictionary<uint, ModifierList> battleNpcGenusMods = new Dictionary<uint, ModifierList>();
|
||||||
private Dictionary<uint, ModifierList> battleNpcPoolMods = new Dictionary<uint, ModifierList>();
|
private Dictionary<uint, ModifierList> battleNpcPoolMods = new Dictionary<uint, ModifierList>();
|
||||||
private Dictionary<uint, ModifierList> battleNpcSpawnMods = new Dictionary<uint, ModifierList>();
|
private Dictionary<uint, ModifierList> battleNpcSpawnMods = new Dictionary<uint, ModifierList>();
|
||||||
|
|
||||||
private Server mServer;
|
private Server mServer;
|
||||||
|
|
||||||
private const int MILIS_LOOPTIME = 333;
|
private const int MILIS_LOOPTIME = 333;
|
||||||
@ -1334,7 +1336,7 @@ namespace FFXIVClassic_Map_Server
|
|||||||
{
|
{
|
||||||
Program.Tick = DateTime.Now;
|
Program.Tick = DateTime.Now;
|
||||||
foreach (Zone zone in zoneList.Values)
|
foreach (Zone zone in zoneList.Values)
|
||||||
{
|
{
|
||||||
zone.Update(Program.Tick);
|
zone.Update(Program.Tick);
|
||||||
}
|
}
|
||||||
Program.LastTick = Program.Tick;
|
Program.LastTick = Program.Tick;
|
||||||
@ -1473,6 +1475,11 @@ namespace FFXIVClassic_Map_Server
|
|||||||
Database.LoadGlobalBattleCommandList(battleCommandList, battleCommandIdByLevel);
|
Database.LoadGlobalBattleCommandList(battleCommandList, battleCommandIdByLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void LoadBattleTraits()
|
||||||
|
{
|
||||||
|
Database.LoadGlobalBattleTraitList(battleTraitList, battleTraitIdsForClass);
|
||||||
|
}
|
||||||
|
|
||||||
public BattleCommand GetBattleCommand(uint id)
|
public BattleCommand GetBattleCommand(uint id)
|
||||||
{
|
{
|
||||||
BattleCommand battleCommand;
|
BattleCommand battleCommand;
|
||||||
@ -1484,5 +1491,19 @@ namespace FFXIVClassic_Map_Server
|
|||||||
List<uint> ids;
|
List<uint> ids;
|
||||||
return battleCommandIdByLevel.TryGetValue(Tuple.Create(classId, level), out ids) ? ids : new List<uint>();
|
return battleCommandIdByLevel.TryGetValue(Tuple.Create(classId, level), out ids) ? ids : new List<uint>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BattleTrait GetBattleTrait(ushort id)
|
||||||
|
{
|
||||||
|
BattleTrait battleTrait;
|
||||||
|
battleTraitList.TryGetValue(id, out battleTrait);
|
||||||
|
return battleTrait;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ushort> GetAllBattleTraitIdsForClass(byte classId)
|
||||||
|
{
|
||||||
|
List<ushort> ids;
|
||||||
|
return battleTraitIdsForClass.TryGetValue(classId, out ids) ? ids : new List<ushort>();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,9 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
Appearance = 0x10,
|
Appearance = 0x10,
|
||||||
Speed = 0x20,
|
Speed = 0x20,
|
||||||
Work = 0x40,
|
Work = 0x40,
|
||||||
|
Stats = 0x80,
|
||||||
|
Status = 0x100,
|
||||||
|
StatusTime = 0x200,
|
||||||
|
|
||||||
AllNpc = 0x6F,
|
AllNpc = 0x6F,
|
||||||
AllPlayer = 0x9F
|
AllPlayer = 0x9F
|
||||||
@ -364,15 +367,26 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
return classParams;
|
return classParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//character's newMainState kind of messes with this
|
||||||
public void ChangeState(ushort newState)
|
public void ChangeState(ushort newState)
|
||||||
{
|
{
|
||||||
//if (newState != currentMainState)
|
if (newState != currentMainState)
|
||||||
{
|
{
|
||||||
currentMainState = newState;
|
currentMainState = newState;
|
||||||
|
|
||||||
updateFlags |= (ActorUpdateFlags.State | ActorUpdateFlags.Position);
|
updateFlags |= (ActorUpdateFlags.State | ActorUpdateFlags.Position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ModifySpeed(float mod)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
moveSpeeds[i] *= mod;
|
||||||
|
}
|
||||||
|
updateFlags |= ActorUpdateFlags.Speed;
|
||||||
|
}
|
||||||
|
|
||||||
public void ChangeSpeed(int type, float value)
|
public void ChangeSpeed(int type, float value)
|
||||||
{
|
{
|
||||||
moveSpeeds[type] = value;
|
moveSpeeds[type] = value;
|
||||||
@ -403,20 +417,23 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
if (positionUpdates != null && positionUpdates.Count > 0)
|
if (positionUpdates != null && positionUpdates.Count > 0)
|
||||||
{
|
{
|
||||||
var pos = positionUpdates[0];
|
var pos = positionUpdates[0];
|
||||||
oldPositionX = positionX;
|
if (pos != null)
|
||||||
oldPositionY = positionY;
|
{
|
||||||
oldPositionZ = positionZ;
|
oldPositionX = positionX;
|
||||||
oldRotation = rotation;
|
oldPositionY = positionY;
|
||||||
|
oldPositionZ = positionZ;
|
||||||
|
oldRotation = rotation;
|
||||||
|
|
||||||
positionX = pos.X;
|
positionX = pos.X;
|
||||||
positionY = pos.Y;
|
positionY = pos.Y;
|
||||||
positionZ = pos.Z;
|
positionZ = pos.Z;
|
||||||
|
|
||||||
zone.UpdateActorPosition(this);
|
zone.UpdateActorPosition(this);
|
||||||
|
|
||||||
//Program.Server.GetInstance().mLuaEngine.OnPath(actor, position, positionUpdates)
|
|
||||||
|
|
||||||
|
//Program.Server.GetInstance().mLuaEngine.OnPath(actor, position, positionUpdates)
|
||||||
|
}
|
||||||
positionUpdates.Remove(pos);
|
positionUpdates.Remove(pos);
|
||||||
|
|
||||||
}
|
}
|
||||||
packets.Add(CreatePositionUpdatePacket());
|
packets.Add(CreatePositionUpdatePacket());
|
||||||
}
|
}
|
||||||
|
@ -141,29 +141,30 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
|
|
||||||
public void RemoveActorFromZone(Actor actor)
|
public void RemoveActorFromZone(Actor actor)
|
||||||
{
|
{
|
||||||
lock (mActorList)
|
if (actor != null)
|
||||||
{
|
lock (mActorList)
|
||||||
mActorList.Remove(actor.actorId);
|
{
|
||||||
|
mActorList.Remove(actor.actorId);
|
||||||
|
|
||||||
int gridX = (int)actor.positionX / boundingGridSize;
|
int gridX = (int)actor.positionX / boundingGridSize;
|
||||||
int gridY = (int)actor.positionZ / boundingGridSize;
|
int gridY = (int)actor.positionZ / boundingGridSize;
|
||||||
|
|
||||||
gridX += halfWidth;
|
gridX += halfWidth;
|
||||||
gridY += halfHeight;
|
gridY += halfHeight;
|
||||||
|
|
||||||
//Boundries
|
//Boundries
|
||||||
if (gridX < 0)
|
if (gridX < 0)
|
||||||
gridX = 0;
|
gridX = 0;
|
||||||
if (gridX >= numXBlocks)
|
if (gridX >= numXBlocks)
|
||||||
gridX = numXBlocks - 1;
|
gridX = numXBlocks - 1;
|
||||||
if (gridY < 0)
|
if (gridY < 0)
|
||||||
gridY = 0;
|
gridY = 0;
|
||||||
if (gridY >= numYBlocks)
|
if (gridY >= numYBlocks)
|
||||||
gridY = numYBlocks - 1;
|
gridY = numYBlocks - 1;
|
||||||
|
|
||||||
lock (mActorBlock)
|
lock (mActorBlock)
|
||||||
mActorBlock[gridX, gridY].Remove(actor);
|
mActorBlock[gridX, gridY].Remove(actor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateActorPosition(Actor actor)
|
public void UpdateActorPosition(Actor actor)
|
||||||
@ -487,7 +488,6 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
uint zoneId;
|
uint zoneId;
|
||||||
|
|
||||||
if (this is PrivateArea)
|
if (this is PrivateArea)
|
||||||
zoneId = ((PrivateArea)this).GetParentZone().actorId;
|
zoneId = ((PrivateArea)this).GetParentZone().actorId;
|
||||||
else
|
else
|
||||||
@ -500,8 +500,9 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
npc = new Npc(mActorList.Count + 1, actorClass, uniqueId, this, x, y, z, rot, state, animId, null);
|
npc = new Npc(mActorList.Count + 1, actorClass, uniqueId, this, x, y, z, rot, state, animId, null);
|
||||||
|
|
||||||
npc.LoadEventConditions(actorClass.eventConditions);
|
npc.LoadEventConditions(actorClass.eventConditions);
|
||||||
//npc.SetMaxHP(3000);
|
npc.SetMaxHP(100);
|
||||||
//npc.SetHP(3000);
|
npc.SetHP(100);
|
||||||
|
npc.ResetMoveSpeeds();
|
||||||
|
|
||||||
AddActorToZone(npc);
|
AddActorToZone(npc);
|
||||||
|
|
||||||
@ -668,7 +669,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
lock (mActorList)
|
lock (mActorList)
|
||||||
{
|
{
|
||||||
foreach (Actor a in mActorList.Values.ToList())
|
foreach (Actor a in mActorList.Values.ToList())
|
||||||
a.Update(tick);
|
a.Update(tick);
|
||||||
|
|
||||||
if ((tick - lastUpdateScript).TotalMilliseconds > 1500)
|
if ((tick - lastUpdateScript).TotalMilliseconds > 1500)
|
||||||
{
|
{
|
||||||
|
@ -2,10 +2,11 @@
|
|||||||
{
|
{
|
||||||
class BattleTemp
|
class BattleTemp
|
||||||
{
|
{
|
||||||
|
//Are these right?
|
||||||
public const uint NAMEPLATE_SHOWN = 0;
|
public const uint NAMEPLATE_SHOWN = 0;
|
||||||
public const uint TARGETABLE = 1;
|
public const uint TARGETABLE = 1;
|
||||||
//public const uint NAMEPLATE_SHOWN2 = 2;
|
public const uint NAMEPLATE_SHOWN2 = 2;
|
||||||
public const uint NAMEPLATE_SHOWN2 = 3;
|
//public const uint NAMEPLATE_SHOWN2 = 3;
|
||||||
|
|
||||||
public const uint STAT_STRENGTH = 3;
|
public const uint STAT_STRENGTH = 3;
|
||||||
public const uint STAT_VITALITY = 4;
|
public const uint STAT_VITALITY = 4;
|
||||||
@ -25,13 +26,13 @@
|
|||||||
public const uint STAT_ACCURACY = 15;
|
public const uint STAT_ACCURACY = 15;
|
||||||
public const uint STAT_NORMALDEFENSE = 18;
|
public const uint STAT_NORMALDEFENSE = 18;
|
||||||
public const uint STAT_EVASION = 16;
|
public const uint STAT_EVASION = 16;
|
||||||
public const uint STAT_ATTACK_MAGIC = 24;
|
public const uint STAT_ATTACK_MAGIC = 23;
|
||||||
public const uint STAT_HEAL_MAGIC = 25;
|
public const uint STAT_HEAL_MAGIC = 24;
|
||||||
public const uint STAT_ENCHANCEMENT_MAGIC_POTENCY = 26;
|
public const uint STAT_ENCHANCEMENT_MAGIC_POTENCY = 25;
|
||||||
public const uint STAT_ENFEEBLING_MAGIC_POTENCY = 27;
|
public const uint STAT_ENFEEBLING_MAGIC_POTENCY = 26;
|
||||||
|
|
||||||
public const uint STAT_MAGIC_ACCURACY = 28;
|
public const uint STAT_MAGIC_ACCURACY = 27;
|
||||||
public const uint STAT_MAGIC_EVASION = 29;
|
public const uint STAT_MAGIC_EVASION = 28;
|
||||||
|
|
||||||
public const uint STAT_CRAFT_PROCESSING = 30;
|
public const uint STAT_CRAFT_PROCESSING = 30;
|
||||||
public const uint STAT_CRAFT_MAGIC_PROCESSING = 31;
|
public const uint STAT_CRAFT_MAGIC_PROCESSING = 31;
|
||||||
@ -43,6 +44,6 @@
|
|||||||
|
|
||||||
public float[] castGauge_speed = { 1.0f, 0.25f};
|
public float[] castGauge_speed = { 1.0f, 0.25f};
|
||||||
public bool[] timingCommandFlag = new bool[4];
|
public bool[] timingCommandFlag = new bool[4];
|
||||||
public ushort[] generalParameter = new ushort[35];
|
public short[] generalParameter = new short[35];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
|
|
||||||
public Pet pet;
|
public Pet pet;
|
||||||
|
|
||||||
private Dictionary<Modifier, Int64> modifiers = new Dictionary<Modifier, long>();
|
private Dictionary<Modifier, double> modifiers = new Dictionary<Modifier, double>();
|
||||||
|
|
||||||
protected ushort hpBase, hpMaxBase, mpBase, mpMaxBase, tpBase;
|
protected ushort hpBase, hpMaxBase, mpBase, mpMaxBase, tpBase;
|
||||||
protected BattleTemp baseStats = new BattleTemp();
|
protected BattleTemp baseStats = new BattleTemp();
|
||||||
@ -125,13 +125,18 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
public ushort newMainState;
|
public ushort newMainState;
|
||||||
public float spawnX, spawnY, spawnZ;
|
public float spawnX, spawnY, spawnZ;
|
||||||
|
|
||||||
|
//I needed some values I could reuse for random stuff, delete later
|
||||||
|
public int extraInt;
|
||||||
|
public uint extraUint;
|
||||||
|
public float extraFloat;
|
||||||
|
|
||||||
protected Dictionary<string, UInt64> tempVars = new Dictionary<string, UInt64>();
|
protected Dictionary<string, UInt64> tempVars = new Dictionary<string, UInt64>();
|
||||||
|
|
||||||
public Character(uint actorID) : base(actorID)
|
public Character(uint actorID) : base(actorID)
|
||||||
{
|
{
|
||||||
//Init timer array to "notimer"
|
//Init timer array to "notimer"
|
||||||
for (int i = 0; i < charaWork.statusShownTime.Length; i++)
|
for (int i = 0; i < charaWork.statusShownTime.Length; i++)
|
||||||
charaWork.statusShownTime[i] = 0xFFFFFFFF;
|
charaWork.statusShownTime[i] = 0;
|
||||||
|
|
||||||
this.statusEffects = new StatusEffectContainer(this);
|
this.statusEffects = new StatusEffectContainer(this);
|
||||||
|
|
||||||
@ -186,15 +191,19 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
zone.BroadcastPacketsAroundActor(this, propPacketUtil.Done());
|
zone.BroadcastPacketsAroundActor(this, propPacketUtil.Done());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//This logic isn't correct, order of GetStatusEffects() is not necessarily the same as the actual effects in game. Also sending every time at once isn't needed
|
||||||
public List<SubPacket> GetActorStatusPackets()
|
public List<SubPacket> GetActorStatusPackets()
|
||||||
{
|
{
|
||||||
var propPacketUtil = new ActorPropertyPacketUtil("charaWork/status", this);
|
var propPacketUtil = new ActorPropertyPacketUtil("charaWork/status", this);
|
||||||
var i = 0;
|
var i = 0;
|
||||||
foreach (var effect in statusEffects.GetStatusEffects())
|
foreach (var effect in statusEffects.GetStatusEffects())
|
||||||
{
|
{
|
||||||
propPacketUtil.AddProperty($"charaWork.statusShownTime[{i}]");
|
if (!effect.GetHidden())
|
||||||
propPacketUtil.AddProperty(String.Format("charaWork.statusShownTime[{0}]", i));
|
{
|
||||||
i++;
|
propPacketUtil.AddProperty($"charaWork.statusShownTime[{i}]");
|
||||||
|
propPacketUtil.AddProperty(String.Format("charaWork.statusShownTime[{0}]", i));
|
||||||
|
i++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return propPacketUtil.Done();
|
return propPacketUtil.Done();
|
||||||
}
|
}
|
||||||
@ -230,6 +239,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
{
|
{
|
||||||
int currentIndex = 0;
|
int currentIndex = 0;
|
||||||
//AoE abilities only ever hit 16 people, so we probably won't need this loop anymore
|
//AoE abilities only ever hit 16 people, so we probably won't need this loop anymore
|
||||||
|
//Apparently aoe are limited to 8?
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (actions.Length - currentIndex >= 10)
|
if (actions.Length - currentIndex >= 10)
|
||||||
@ -251,11 +261,11 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
|
|
||||||
public void DoBattleAction(ushort commandId, uint animationId, List<BattleAction> actions)
|
public void DoBattleAction(ushort commandId, uint animationId, List<BattleAction> actions)
|
||||||
{
|
{
|
||||||
int currentIndex = 0;
|
int currentIndex = 0;
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (actions.Count - currentIndex >= 18)
|
if (actions.Count - currentIndex >= 10)
|
||||||
zone.BroadcastPacketAroundActor(this, BattleActionX18Packet.BuildPacket(actorId, animationId, commandId, actions, ref currentIndex));
|
zone.BroadcastPacketAroundActor(this, BattleActionX18Packet.BuildPacket(actorId, animationId, commandId, actions, ref currentIndex));
|
||||||
else if (actions.Count - currentIndex > 1)
|
else if (actions.Count - currentIndex > 1)
|
||||||
zone.BroadcastPacketAroundActor(this, BattleActionX10Packet.BuildPacket(actorId, animationId, commandId, actions, ref currentIndex));
|
zone.BroadcastPacketAroundActor(this, BattleActionX10Packet.BuildPacket(actorId, animationId, commandId, actions, ref currentIndex));
|
||||||
@ -266,7 +276,11 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
break;
|
break;
|
||||||
animationId = 0; //If more than one packet is sent out, only send the animation once to avoid double playing.
|
|
||||||
|
//Sending multiple packets at once causes some issues. Setting any combination of these to zero changes what breaks
|
||||||
|
//animationId = 0; //If more than one packet is sent out, only send the animation once to avoid double playing.
|
||||||
|
//commandId = 0;
|
||||||
|
//sourceActorId = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,20 +296,51 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
PathTo(target.positionX, target.positionY, target.positionZ, stepSize, maxPath, radius);
|
PathTo(target.positionX, target.positionY, target.positionZ, stepSize, maxPath, radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Int64 GetMod(uint modifier)
|
public double GetMod(Modifier modifier)
|
||||||
{
|
{
|
||||||
Int64 res;
|
return GetMod((uint)modifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double GetMod(uint modifier)
|
||||||
|
{
|
||||||
|
double res;
|
||||||
if (modifiers.TryGetValue((Modifier)modifier, out res))
|
if (modifiers.TryGetValue((Modifier)modifier, out res))
|
||||||
return res;
|
return res;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetMod(uint modifier, Int64 val)
|
public void SetMod(uint modifier, double val)
|
||||||
{
|
{
|
||||||
if (modifiers.ContainsKey((Modifier)modifier))
|
if (modifiers.ContainsKey((Modifier)modifier))
|
||||||
modifiers[(Modifier)modifier] = val;
|
modifiers[(Modifier)modifier] = val;
|
||||||
else
|
else
|
||||||
modifiers.Add((Modifier)modifier, val);
|
modifiers.Add((Modifier)modifier, val);
|
||||||
|
|
||||||
|
if (modifier <= 35)
|
||||||
|
updateFlags |= ActorUpdateFlags.Stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddMod(Modifier modifier, double val)
|
||||||
|
{
|
||||||
|
AddMod((uint)modifier, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddMod(uint modifier, double val)
|
||||||
|
{
|
||||||
|
|
||||||
|
double newVal = GetMod(modifier) + val;
|
||||||
|
SetMod(modifier, newVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SubtractMod(Modifier modifier, double val)
|
||||||
|
{
|
||||||
|
AddMod((uint)modifier, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SubtractMod(uint modifier, double val)
|
||||||
|
{
|
||||||
|
double newVal = GetMod(modifier) - val;
|
||||||
|
SetMod(modifier, newVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnPath(Vector3 point)
|
public virtual void OnPath(Vector3 point)
|
||||||
@ -332,6 +377,21 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
//DoBattleAction(21001, 0x7C000062, new BattleAction(this.actorId, 0, 1, 0, 0, 1)); //Attack Mode
|
//DoBattleAction(21001, 0x7C000062, new BattleAction(this.actorId, 0, 1, 0, 0, 1)); //Attack Mode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((updateFlags & ActorUpdateFlags.Status) != 0)
|
||||||
|
{
|
||||||
|
List<SubPacket> statusPackets = statusEffects.GetStatusPackets();
|
||||||
|
packets.AddRange(statusPackets);
|
||||||
|
statusPackets.Clear();
|
||||||
|
updateFlags &= ~ActorUpdateFlags.Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((updateFlags & ActorUpdateFlags.StatusTime) != 0)
|
||||||
|
{
|
||||||
|
packets.AddRange(statusEffects.GetStatusTimerPackets());
|
||||||
|
updateFlags &= ~ActorUpdateFlags.StatusTime;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
if ((updateFlags & ActorUpdateFlags.HpTpMp) != 0)
|
if ((updateFlags & ActorUpdateFlags.HpTpMp) != 0)
|
||||||
{
|
{
|
||||||
var propPacketUtil = new ActorPropertyPacketUtil("charaWork/stateAtQuicklyForAll", this);
|
var propPacketUtil = new ActorPropertyPacketUtil("charaWork/stateAtQuicklyForAll", this);
|
||||||
@ -342,6 +402,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
propPacketUtil.AddProperty("charaWork.parameterTemp.tp");
|
propPacketUtil.AddProperty("charaWork.parameterTemp.tp");
|
||||||
packets.AddRange(propPacketUtil.Done());
|
packets.AddRange(propPacketUtil.Done());
|
||||||
}
|
}
|
||||||
|
|
||||||
base.PostUpdate(tick, packets);
|
base.PostUpdate(tick, packets);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -384,11 +445,12 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
public virtual bool Engage(uint targid = 0, ushort newMainState = 0xFFFF)
|
public virtual bool Engage(uint targid = 0, ushort newMainState = 0xFFFF)
|
||||||
{
|
{
|
||||||
// todo: attack the things
|
// todo: attack the things
|
||||||
if (newMainState != 0xFFFF)
|
/*if (newMainState != 0xFFFF)
|
||||||
{
|
{
|
||||||
this.newMainState = newMainState;
|
currentMainState = newMainState;// this.newMainState = newMainState;
|
||||||
|
updateFlags |= ActorUpdateFlags.State;
|
||||||
}
|
}
|
||||||
else if (aiContainer.CanChangeState())
|
else*/ if (aiContainer.CanChangeState())
|
||||||
{
|
{
|
||||||
if (targid == 0)
|
if (targid == 0)
|
||||||
{
|
{
|
||||||
@ -414,11 +476,12 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
|
|
||||||
public virtual bool Disengage(ushort newMainState = 0xFFFF)
|
public virtual bool Disengage(ushort newMainState = 0xFFFF)
|
||||||
{
|
{
|
||||||
if (newMainState != 0xFFFF)
|
/*if (newMainState != 0xFFFF)
|
||||||
{
|
{
|
||||||
this.newMainState = newMainState;
|
currentMainState = newMainState;// this.newMainState = newMainState;
|
||||||
|
updateFlags |= ActorUpdateFlags.State;
|
||||||
}
|
}
|
||||||
else if (IsEngaged())
|
else*/ if (IsEngaged())
|
||||||
{
|
{
|
||||||
aiContainer.Disengage();
|
aiContainer.Disengage();
|
||||||
return true;
|
return true;
|
||||||
@ -454,7 +517,8 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
RecalculateStats();
|
RecalculateStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void Die(DateTime tick)
|
//AdditionalActions is the list of actions that EXP/Chain messages are added to
|
||||||
|
public virtual void Die(DateTime tick, BattleActionContainer actionContainer = null)
|
||||||
{
|
{
|
||||||
// todo: actual despawn timer
|
// todo: actual despawn timer
|
||||||
aiContainer.InternalDie(tick, 10);
|
aiContainer.InternalDie(tick, 10);
|
||||||
@ -514,7 +578,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
|
|
||||||
public byte GetHPP()
|
public byte GetHPP()
|
||||||
{
|
{
|
||||||
return (byte)(charaWork.parameterSave.hp[0] == 0 ? 0 : (charaWork.parameterSave.hp[0] / charaWork.parameterSave.hpMax[0]) * 100);
|
return (byte)(charaWork.parameterSave.hp[0] == 0 ? 0 : (charaWork.parameterSave.hp[0] / (float) charaWork.parameterSave.hpMax[0]) * 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetHP(uint hp)
|
public void SetHP(uint hp)
|
||||||
@ -549,8 +613,8 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
// todo: the following functions are virtuals since we want to check hidden item bonuses etc on player for certain conditions
|
// 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)
|
||||||
{
|
{
|
||||||
// dont wanna die ded
|
// dont wanna die ded, don't want to send update if hp isn't actually changed
|
||||||
if (IsAlive())
|
if (IsAlive() && hp != 0)
|
||||||
{
|
{
|
||||||
// todo: +/- hp and die
|
// todo: +/- hp and die
|
||||||
// todo: battlenpcs probably have way more hp?
|
// todo: battlenpcs probably have way more hp?
|
||||||
@ -558,9 +622,6 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
addHp = addHp.Clamp((short)GetMod((uint)Modifier.MinimumHpLock), charaWork.parameterSave.hpMax[0]);
|
addHp = addHp.Clamp((short)GetMod((uint)Modifier.MinimumHpLock), charaWork.parameterSave.hpMax[0]);
|
||||||
charaWork.parameterSave.hp[0] = (short)addHp;
|
charaWork.parameterSave.hp[0] = (short)addHp;
|
||||||
|
|
||||||
if (charaWork.parameterSave.hp[0] < 1)
|
|
||||||
Die(Program.Tick);
|
|
||||||
|
|
||||||
updateFlags |= ActorUpdateFlags.HpTpMp;
|
updateFlags |= ActorUpdateFlags.HpTpMp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -577,21 +638,30 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
|
|
||||||
public void AddMP(int mp)
|
public void AddMP(int mp)
|
||||||
{
|
{
|
||||||
charaWork.parameterSave.mp = (short)(charaWork.parameterSave.mp + mp).Clamp(ushort.MinValue, charaWork.parameterSave.mpMax);
|
if (IsAlive() && mp != 0)
|
||||||
|
{
|
||||||
|
charaWork.parameterSave.mp = (short)(charaWork.parameterSave.mp + mp).Clamp(ushort.MinValue, charaWork.parameterSave.mpMax);
|
||||||
|
|
||||||
// todo: check hidden effects and shit
|
// todo: check hidden effects and shit
|
||||||
|
|
||||||
updateFlags |= ActorUpdateFlags.HpTpMp;
|
updateFlags |= ActorUpdateFlags.HpTpMp;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddTP(int tp)
|
public void AddTP(int tp)
|
||||||
{
|
{
|
||||||
charaWork.parameterTemp.tp = (short)((charaWork.parameterTemp.tp + tp).Clamp(0, 3000));
|
if (IsAlive() && tp != 0)
|
||||||
tpBase = (ushort) charaWork.parameterTemp.tp;
|
{
|
||||||
updateFlags |= ActorUpdateFlags.HpTpMp;
|
var addTp = charaWork.parameterTemp.tp + tp;
|
||||||
|
|
||||||
|
addTp = addTp.Clamp((int) GetMod(Modifier.MinimumTpLock), 3000);
|
||||||
|
charaWork.parameterTemp.tp = (short) addTp;
|
||||||
|
tpBase = (ushort)charaWork.parameterTemp.tp;
|
||||||
|
updateFlags |= ActorUpdateFlags.HpTpMp;
|
||||||
|
|
||||||
if (tpBase >= 1000)
|
if (tpBase >= 1000)
|
||||||
lua.LuaEngine.GetInstance().OnSignal("tpOver1000");
|
lua.LuaEngine.GetInstance().OnSignal("tpOver1000");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DelHP(int hp)
|
public void DelHP(int hp)
|
||||||
@ -609,14 +679,9 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
AddTP(-tp);
|
AddTP(-tp);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CalculateBaseStats()
|
virtual public void CalculateBaseStats()
|
||||||
{
|
{
|
||||||
// todo: apply mods and shit here, get race/level/job and shit
|
// todo: apply mods and shit here, get race/level/job and shit
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RecalculateStats()
|
|
||||||
{
|
|
||||||
uint hpMod = (uint) GetMod((uint)Modifier.Hp);
|
uint hpMod = (uint) GetMod((uint)Modifier.Hp);
|
||||||
if (hpMod != 0)
|
if (hpMod != 0)
|
||||||
{
|
{
|
||||||
@ -644,14 +709,22 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
}
|
}
|
||||||
// todo: recalculate stats and crap
|
// todo: recalculate stats and crap
|
||||||
updateFlags |= ActorUpdateFlags.HpTpMp;
|
updateFlags |= ActorUpdateFlags.HpTpMp;
|
||||||
|
|
||||||
|
|
||||||
|
SetMod((uint)Modifier.HitCount, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetStat(uint statId, uint val)
|
public void RecalculateStats()
|
||||||
{
|
{
|
||||||
charaWork.battleTemp.generalParameter[statId] = (ushort)val;
|
//CalculateBaseStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ushort GetStat(uint statId)
|
public void SetStat(uint statId, int val)
|
||||||
|
{
|
||||||
|
charaWork.battleTemp.generalParameter[statId] = (short)val;
|
||||||
|
}
|
||||||
|
|
||||||
|
public short GetStat(uint statId)
|
||||||
{
|
{
|
||||||
return charaWork.battleTemp.generalParameter[statId];
|
return charaWork.battleTemp.generalParameter[statId];
|
||||||
}
|
}
|
||||||
@ -659,62 +732,67 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
public virtual float GetSpeed()
|
public virtual float GetSpeed()
|
||||||
{
|
{
|
||||||
// todo: for battlenpc/player calculate speed
|
// todo: for battlenpc/player calculate speed
|
||||||
return GetMod((uint)Modifier.Speed);
|
return (float) GetMod((uint)Modifier.Speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnAttack(State state, BattleAction action, ref BattleAction error)
|
public virtual void OnAttack(State state, BattleAction action, ref BattleAction error)
|
||||||
{
|
{
|
||||||
// todo: change animation based on equipped weapon
|
|
||||||
action.effectId |= (uint)HitEffect.HitVisual1; // melee
|
|
||||||
|
|
||||||
var target = state.GetTarget();
|
var target = state.GetTarget();
|
||||||
|
// todo: change animation based on equipped weapon
|
||||||
// todo: get hitrate and shit, handle protect effect and whatever
|
// todo: get hitrate and shit, handle protect effect and whatever
|
||||||
if (BattleUtils.TryAttack(this, target, action, ref error))
|
if (BattleUtils.TryAttack(this, target, action, ref error))
|
||||||
{
|
{
|
||||||
action.amount = BattleUtils.CalculateAttackDamage(this, target, action);
|
|
||||||
//var packet = BattleActionX01Packet.BuildPacket(owner.actorId, owner.actorId, target.actorId, (uint)0x19001000, (uint)0x8000604, (ushort)0x765D, (ushort)BattleActionX01PacketCommand.Attack, (ushort)damage, (byte)0x1);
|
//var packet = BattleActionX01Packet.BuildPacket(owner.actorId, owner.actorId, target.actorId, (uint)0x19001000, (uint)0x8000604, (ushort)0x765D, (ushort)BattleActionX01PacketCommand.Attack, (ushort)damage, (byte)0x1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: call onAttack/onDamageTaken
|
// todo: call onAttack/onDamageTaken
|
||||||
BattleUtils.DamageTarget(this, target, action, DamageTakenType.Attack);
|
//BattleUtils.DamageTarget(this, target, DamageTakenType.Attack, action);
|
||||||
AddTP(115);
|
AddTP(200);
|
||||||
target.AddTP(100);
|
target.AddTP(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnCast(State state, BattleAction[] actions, ref BattleAction[] errors)
|
public virtual void OnCast(State state, BattleAction[] actions, BattleCommand spell, ref BattleAction[] errors)
|
||||||
{
|
{
|
||||||
var spell = ((MagicState)state).GetSpell();
|
|
||||||
// damage is handled in script
|
// damage is handled in script
|
||||||
var spellCost = spell.CalculateCost((uint)this.GetLevel());
|
var spellCost = spell.CalculateMpCost(this);
|
||||||
this.DelMP(spellCost); // mpCost can be set in script e.g. if caster has something for free spells
|
this.DelMP(spellCost); // mpCost can be set in script e.g. if caster has something for free spells
|
||||||
|
|
||||||
foreach (BattleAction action in actions)
|
foreach (BattleAction action in actions)
|
||||||
|
{
|
||||||
if (zone.FindActorInArea<Character>(action.targetId) is Character chara)
|
if (zone.FindActorInArea<Character>(action.targetId) is Character chara)
|
||||||
BattleUtils.DamageTarget(this, chara, action, DamageTakenType.Magic);
|
{
|
||||||
|
//BattleUtils.HandleHitType(this, chara, action);
|
||||||
|
//BattleUtils.DoAction(this, chara, action, DamageTakenType.Magic);
|
||||||
|
}
|
||||||
|
}
|
||||||
lua.LuaEngine.GetInstance().OnSignal("spellUsed");
|
lua.LuaEngine.GetInstance().OnSignal("spellUsed");
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnWeaponSkill(State state, BattleAction[] actions, ref BattleAction[] errors)
|
public virtual void OnWeaponSkill(State state, BattleAction[] actions, BattleCommand skill, ref BattleAction[] errors)
|
||||||
{
|
{
|
||||||
var skill = ((WeaponSkillState)state).GetWeaponSkill();
|
|
||||||
// damage is handled in script
|
// damage is handled in script
|
||||||
this.DelTP(skill.tpCost);
|
|
||||||
|
|
||||||
foreach (BattleAction action in actions)
|
foreach (BattleAction action in actions)
|
||||||
|
{
|
||||||
|
//Should we just store the character insteado f having to find it again?
|
||||||
if (zone.FindActorInArea<Character>(action.targetId) is Character chara)
|
if (zone.FindActorInArea<Character>(action.targetId) is Character chara)
|
||||||
BattleUtils.DamageTarget(this, chara, action, DamageTakenType.Weaponskill);
|
{
|
||||||
|
//BattleUtils.DoAction(this, chara, action, DamageTakenType.Weaponskill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lua.LuaEngine.GetInstance().OnSignal("weaponskillUsed");
|
this.DelTP(skill.tpCost);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnAbility(State state, BattleAction[] actions, ref BattleAction[] errors)
|
public virtual void OnAbility(State state, BattleAction[] actions, BattleCommand ability, ref BattleAction[] errors)
|
||||||
{
|
{
|
||||||
if (target is BattleNpc)
|
|
||||||
((BattleNpc)target).lastAttacker = this;
|
|
||||||
|
|
||||||
foreach (var action in actions)
|
foreach (var action in actions)
|
||||||
zone.FindActorInArea<BattleNpc>(action.targetId)?.OnDamageTaken(this, action, DamageTakenType.Ability);
|
{
|
||||||
|
if (zone.FindActorInArea<Character>(action.targetId) is Character chara)
|
||||||
|
{
|
||||||
|
//BattleUtils.DoAction(this, chara, action, DamageTakenType.Ability);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnSpawn()
|
public virtual void OnSpawn()
|
||||||
@ -732,9 +810,55 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnDamageTaken(Character attacker, BattleAction action, DamageTakenType damageTakenType)
|
public virtual void OnDamageDealt(Character defender, BattleAction action, BattleActionContainer actionContainer = null)
|
||||||
{
|
{
|
||||||
|
switch (action.hitType)
|
||||||
|
{
|
||||||
|
case (HitType.Miss):
|
||||||
|
OnMiss(this, action, actionContainer);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
OnHit(defender, action, actionContainer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TP is only gained from autoattacks and abilities
|
||||||
|
if (action.commandType == CommandType.AutoAttack || action.commandType == CommandType.Ability)
|
||||||
|
{
|
||||||
|
//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.1);
|
||||||
|
AddTP((int)(weaponDelay * 100 * storeTPPercent));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void OnDamageTaken(Character attacker, BattleAction action, BattleActionContainer actionContainer = null)
|
||||||
|
{
|
||||||
|
switch (action.hitType)
|
||||||
|
{
|
||||||
|
case (HitType.Miss):
|
||||||
|
OnEvade(attacker, action, actionContainer);
|
||||||
|
break;
|
||||||
|
case (HitType.Parry):
|
||||||
|
OnParry(attacker, action, actionContainer);
|
||||||
|
break;
|
||||||
|
case (HitType.Block):
|
||||||
|
OnBlock(attacker, action, actionContainer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnDamageTaken, "onDamageTaken", attacker, this, action);
|
||||||
|
|
||||||
|
//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)
|
public UInt64 GetTempVar(string name)
|
||||||
@ -810,6 +934,191 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
}
|
}
|
||||||
#endregion lua helpers
|
#endregion lua helpers
|
||||||
#endregion ai stuff
|
#endregion ai stuff
|
||||||
}
|
|
||||||
|
|
||||||
}
|
//Reset procs. Only send packet if any procs were actually reset.
|
||||||
|
//This assumes you can't use weaponskills between getting a proc and using the procced ability
|
||||||
|
public void ResetProcs()
|
||||||
|
{
|
||||||
|
var propPacketUtil = new ActorPropertyPacketUtil("charaWork/timingCommand", this);
|
||||||
|
bool shouldSend = false;
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
if (charaWork.battleTemp.timingCommandFlag[i])
|
||||||
|
{
|
||||||
|
shouldSend = true;
|
||||||
|
charaWork.battleTemp.timingCommandFlag[i] = false;
|
||||||
|
propPacketUtil.AddProperty($"charaWork.battleTemp.timingCommandFlag[{i}]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldSend && this is Player player)
|
||||||
|
player.QueuePackets(propPacketUtil.Done());
|
||||||
|
}
|
||||||
|
|
||||||
|
//Set given proc to true and send packet if this is a player
|
||||||
|
// todo: hidden status effects for timing when the procs fall off
|
||||||
|
public void SetProc(int procId, bool val = true)
|
||||||
|
{
|
||||||
|
charaWork.battleTemp.timingCommandFlag[procId] = val;
|
||||||
|
uint effectId = (uint)StatusEffectId.EvadeProc + (uint)procId;
|
||||||
|
|
||||||
|
//If a proc just occurred, add a hidden effect effect
|
||||||
|
if (val)
|
||||||
|
{
|
||||||
|
StatusEffect procEffect = Server.GetWorldManager().GetStatusEffect(effectId);
|
||||||
|
procEffect.SetDuration(5);
|
||||||
|
statusEffects.AddStatusEffect(procEffect, this, true, true);
|
||||||
|
}
|
||||||
|
//Otherwise we're reseting a proc, remove the status
|
||||||
|
else
|
||||||
|
{
|
||||||
|
statusEffects.RemoveStatusEffect(statusEffects.GetStatusEffectById((uint)effectId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this is Player player)
|
||||||
|
{
|
||||||
|
var propPacketUtil = new ActorPropertyPacketUtil("charaWork/timingCommand", this);
|
||||||
|
propPacketUtil.AddProperty($"charaWork.battleTemp.timingCommandFlag[{procId}]");
|
||||||
|
player.QueuePackets(propPacketUtil.Done());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public HitDirection GetHitDirection(Actor target)
|
||||||
|
{
|
||||||
|
//Get between taget's position and our position
|
||||||
|
double angle = Vector3.GetAngle(target.GetPosAsVector3(), GetPosAsVector3());
|
||||||
|
//Add to the target's rotation, mod by 2pi. This is the angle relative to where the target is looking
|
||||||
|
//Actor's rotation is 0 degrees on their left side, rotate it by 45 degrees so that quadrants line up with sides
|
||||||
|
angle = (angle + target.rotation - (.25 * Math.PI)) % (2 * Math.PI);
|
||||||
|
//Make positive
|
||||||
|
if (angle < 0)
|
||||||
|
angle = angle + (2 * Math.PI);
|
||||||
|
|
||||||
|
//Get the side we're on. 0 is front, 1 is right, 2 is rear, 3 is left
|
||||||
|
var side = (int) (angle / (.5 * Math.PI)) % 4;
|
||||||
|
|
||||||
|
return (HitDirection) (1 << side);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Called when this character evades attacker's action
|
||||||
|
public void OnEvade(Character attacker, BattleAction action, BattleActionContainer actionContainer = null)
|
||||||
|
{
|
||||||
|
SetProc((ushort)HitType.Evade);
|
||||||
|
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnEvade, "onEvade", attacker, this, action, actionContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Called when this character blocks attacker's action
|
||||||
|
public void OnBlock(Character attacker, BattleAction action, BattleActionContainer actionContainer = null)
|
||||||
|
{
|
||||||
|
SetProc((ushort)HitType.Block);
|
||||||
|
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnBlock, "onBlock", attacker, this, action, actionContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Called when this character parries attacker's action
|
||||||
|
public void OnParry(Character attacker, BattleAction action, BattleActionContainer actionContainer = null)
|
||||||
|
{
|
||||||
|
SetProc((ushort)HitType.Parry);
|
||||||
|
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnParry, "onParry", attacker, this, action, actionContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Called when this character misses
|
||||||
|
public void OnMiss(Character defender, BattleAction action, BattleActionContainer actionContainer = null)
|
||||||
|
{
|
||||||
|
SetProc((ushort)HitType.Miss);
|
||||||
|
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnMiss, "onMiss", this, defender, action, actionContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnHit(Character defender, BattleAction action, BattleActionContainer actionContainer = null)
|
||||||
|
{
|
||||||
|
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnHit, "onHit", this, defender, action, actionContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
//The order of messages that appears after using a command is:
|
||||||
|
|
||||||
|
//1. Cast start messages. (ie "You begin casting... ")
|
||||||
|
//2. Messages from buffs that activate before the command actually starts, like Power Surge or Presence of Mind. (This may be wrong and these could be the same as 4.)
|
||||||
|
//3. If the command is a multi-hit command, this is where the "You use [command] on [target]" message goes
|
||||||
|
|
||||||
|
//Then, for each hit:
|
||||||
|
//4. Buffs that activate before a command hits, like Blindside
|
||||||
|
//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
|
||||||
|
|
||||||
|
//For every target defeated:
|
||||||
|
//8. Defeat message
|
||||||
|
//9. EXP message
|
||||||
|
//10. EXP chain message
|
||||||
|
|
||||||
|
|
||||||
|
//folder is probably temporary until move to cached scripts is complete
|
||||||
|
public void DoBattleCommand(BattleCommand command, string folder)
|
||||||
|
{
|
||||||
|
//List<BattleAction> actions = new List<BattleAction>();
|
||||||
|
BattleActionContainer actions = new BattleActionContainer();
|
||||||
|
|
||||||
|
var targets = command.targetFind.GetTargets();
|
||||||
|
bool hitTarget = false;
|
||||||
|
|
||||||
|
if (targets.Count > 0)
|
||||||
|
{
|
||||||
|
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnCommandStart, "onCommandStart", this, command, actions);
|
||||||
|
|
||||||
|
foreach (var chara in targets)
|
||||||
|
{
|
||||||
|
ushort hitCount = 0;
|
||||||
|
for (int hitNum = 1; hitNum <= command.numHits; hitNum++)
|
||||||
|
{
|
||||||
|
var action = new BattleAction(chara.actorId, command, (byte)GetHitDirection(chara), (byte)hitNum);
|
||||||
|
|
||||||
|
//uncached script
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
hitTarget = true;
|
||||||
|
hitCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.numHits > 1)
|
||||||
|
{
|
||||||
|
//You use [command] on [target].
|
||||||
|
actions.AddAction(new BattleAction(chara.actorId, 30442, 0, 0, (byte)hitCount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnCommandFinish, "onCommandFinish", this, command, actions);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
actions.AddAction(new BattleAction(target.actorId, 30202, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Now that we know if we hit the target we can check if the combo continues
|
||||||
|
if (this is Player player && command.commandType == CommandType.WeaponSkill)
|
||||||
|
if (command.isCombo && hitTarget)
|
||||||
|
player.SetCombos(command.comboNextCommandId);
|
||||||
|
else
|
||||||
|
player.SetCombos();
|
||||||
|
|
||||||
|
BattleAction error = new BattleAction(actorId, 0, 0);
|
||||||
|
actions.CombineLists();
|
||||||
|
DoBattleAction(command.id, command.battleAnimation, actions.GetList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Character> GetPartyMembersInRange(uint range)
|
||||||
|
{
|
||||||
|
TargetFind targetFind = new TargetFind(this);
|
||||||
|
targetFind.SetAOEType(ValidTarget.PartyMember, TargetFindAOEType.Circle, TargetFindAOETarget.Self, range);
|
||||||
|
targetFind.FindWithinArea(this, ValidTarget.PartyMember, TargetFindAOETarget.Self);
|
||||||
|
return targetFind.GetTargets();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,53 +6,92 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace FFXIVClassic_Map_Server.actors.chara
|
namespace FFXIVClassic_Map_Server.actors.chara
|
||||||
{
|
{
|
||||||
|
//These will need to be redone at some point. remember to update tables in db.
|
||||||
|
//Consider using text_paramname sheet. that matches up with the stats on armor, but some things will need special handling
|
||||||
|
//Also, 0-35 should probably match with up BattleTemp
|
||||||
enum Modifier : UInt32
|
enum Modifier : UInt32
|
||||||
{
|
{
|
||||||
None = 0,
|
NAMEPLATE_SHOWN = 0,
|
||||||
Hp = 1,
|
TARGETABLE = 1,
|
||||||
HpPercent = 2,
|
NAMEPLATE_SHOWN2 = 2,
|
||||||
Mp = 3,
|
//NAMEPLATE_SHOWN2 = 3,
|
||||||
MpPercent = 4,
|
|
||||||
Tp = 5,
|
|
||||||
TpPercent = 6,
|
|
||||||
Regen = 7,
|
|
||||||
Refresh = 8,
|
|
||||||
Strength = 9,
|
|
||||||
Vitality = 10,
|
|
||||||
Dexterity = 11,
|
|
||||||
Intelligence = 12,
|
|
||||||
Mind = 13,
|
|
||||||
Piety = 14,
|
|
||||||
Attack = 15,
|
|
||||||
Accuracy = 16,
|
|
||||||
Defense = 17,
|
|
||||||
Evasion = 18,
|
|
||||||
MagicAttack = 19,
|
|
||||||
MagicHeal = 20, // is this needed? shouldnt it just be calc'd from mind
|
|
||||||
MagicAccuracy = 21,
|
|
||||||
MagicEvasion = 22,
|
|
||||||
MagicDefense = 23,
|
|
||||||
MagicEnhancePotency = 24,
|
|
||||||
MagicEnfeeblingPotency = 25,
|
|
||||||
ResistFire = 26,
|
|
||||||
ResistIce = 27,
|
|
||||||
ResistWind = 28,
|
|
||||||
ResistLightning = 29,
|
|
||||||
ResistEarth = 30,
|
|
||||||
ResistWater = 31, // <3 u jorge
|
|
||||||
AttackRange = 32,
|
|
||||||
Speed = 33,
|
|
||||||
AttackDelay = 34,
|
|
||||||
|
|
||||||
CraftProcessing = 35,
|
|
||||||
CraftMagicProcessing = 36,
|
|
||||||
CraftProcessControl = 37,
|
|
||||||
|
|
||||||
HarvestPotency = 38,
|
Strength = 3,
|
||||||
HarvestLimit = 39,
|
Vitality = 4,
|
||||||
HarvestRate = 40,
|
Dexterity = 5,
|
||||||
|
Intelligence = 6,
|
||||||
|
Mind = 7,
|
||||||
|
Piety = 8,
|
||||||
|
|
||||||
Raise = 41,
|
ResistFire = 9,
|
||||||
MinimumHpLock = 42, // hp cannot fall below this value
|
ResistIce = 10,
|
||||||
|
ResistWind = 11,
|
||||||
|
ResistLightning = 12,
|
||||||
|
ResistEarth = 13,
|
||||||
|
ResistWater = 14,
|
||||||
|
|
||||||
|
Accuracy = 15,
|
||||||
|
Evasion = 16,
|
||||||
|
Attack = 17,
|
||||||
|
Defense = 18, //Is there a magic defense stat? 19 maybe?
|
||||||
|
MagicAttack = 23,
|
||||||
|
MagicHeal = 24,
|
||||||
|
MagicEnhancePotency = 25,
|
||||||
|
MagicEnfeeblingPotency = 26,
|
||||||
|
|
||||||
|
MagicAccuracy = 27,
|
||||||
|
MagicEvasion = 28,
|
||||||
|
|
||||||
|
CraftProcessing = 30,
|
||||||
|
CraftMagicProcessing = 31,
|
||||||
|
CraftProcessControl = 32,
|
||||||
|
|
||||||
|
HarvestPotency = 33,
|
||||||
|
HarvestLimit = 34,
|
||||||
|
HarvestRate = 35,
|
||||||
|
|
||||||
|
None = 36,
|
||||||
|
Hp = 37,
|
||||||
|
HpPercent = 38,
|
||||||
|
Mp = 39,
|
||||||
|
MpPercent = 40,
|
||||||
|
Tp = 41,
|
||||||
|
TpPercent = 42,
|
||||||
|
Regen = 43,
|
||||||
|
Refresh = 44,
|
||||||
|
|
||||||
|
AttackRange = 45,
|
||||||
|
Speed = 46,
|
||||||
|
AttackDelay = 47,
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
//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,
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,9 +35,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
actionQueue = new ActionQueue(owner);
|
actionQueue = new ActionQueue(owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateLastActionTime()
|
public void UpdateLastActionTime(uint delay = 0)
|
||||||
{
|
{
|
||||||
lastActionTime = DateTime.Now;
|
lastActionTime = DateTime.Now.AddSeconds(delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DateTime GetLastActionTime()
|
public DateTime GetLastActionTime()
|
||||||
@ -62,6 +62,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
controller.Update(tick);
|
controller.Update(tick);
|
||||||
|
|
||||||
State top;
|
State top;
|
||||||
|
|
||||||
while (states.Count > 0 && (top = states.Peek()).Update(tick))
|
while (states.Count > 0 && (top = states.Peek()).Update(tick))
|
||||||
{
|
{
|
||||||
if (top == GetCurrentState())
|
if (top == GetCurrentState())
|
||||||
@ -330,7 +331,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
{
|
{
|
||||||
if (CanChangeState())
|
if (CanChangeState())
|
||||||
{
|
{
|
||||||
|
ChangeState(new AbilityState(owner, target, (ushort)abilityId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,8 +6,9 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using FFXIVClassic_Map_Server.actors.chara.player;
|
using FFXIVClassic_Map_Server.actors.chara.player;
|
||||||
using FFXIVClassic.Common;
|
using FFXIVClassic.Common;
|
||||||
using FFXIVClassic_Map_Server.packets.send.actor;
|
using FFXIVClassic_Map_Server.packets.send.actor.battle;
|
||||||
using FFXIVClassic_Map_Server.actors.chara.ai.utils;
|
using FFXIVClassic_Map_Server.actors.chara.ai.utils;
|
||||||
|
using MoonSharp.Interpreter;
|
||||||
|
|
||||||
namespace FFXIVClassic_Map_Server.actors.chara.ai
|
namespace FFXIVClassic_Map_Server.actors.chara.ai
|
||||||
{
|
{
|
||||||
@ -38,10 +39,10 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
public enum BattleCommandProcRequirement : byte
|
public enum BattleCommandProcRequirement : byte
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
Evade = 0x01,
|
Miss,
|
||||||
Block = 0x02,
|
Evade,
|
||||||
Parry = 0x04,
|
Parry,
|
||||||
Miss = 0x08
|
Block
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum BattleCommandValidUser : byte
|
public enum BattleCommandValidUser : byte
|
||||||
@ -51,6 +52,29 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
Monster
|
Monster
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum BattleCommandCastType : ushort
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Weaponskill = 1,
|
||||||
|
Weaponskill2 = 2,
|
||||||
|
BlackMagic = 3,
|
||||||
|
WhiteMagic = 4,
|
||||||
|
SongMagic = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//What type of command it is
|
||||||
|
[Flags]
|
||||||
|
public enum CommandType : ushort
|
||||||
|
{
|
||||||
|
//Type of action
|
||||||
|
None = 0,
|
||||||
|
AutoAttack = 1,
|
||||||
|
WeaponSkill = 2,
|
||||||
|
Ability =3,
|
||||||
|
Spell = 4
|
||||||
|
}
|
||||||
|
|
||||||
class BattleCommand
|
class BattleCommand
|
||||||
{
|
{
|
||||||
public ushort id;
|
public ushort id;
|
||||||
@ -58,28 +82,50 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
public byte job;
|
public byte job;
|
||||||
public byte level;
|
public byte level;
|
||||||
public BattleCommandRequirements requirements;
|
public BattleCommandRequirements requirements;
|
||||||
public ValidTarget validTarget;
|
public ValidTarget mainTarget; //what the skill has to be used on. ie self for flare, enemy for ring of talons even though both are self-centere aoe
|
||||||
public TargetFindAOEType aoeType;
|
public ValidTarget validTarget; //what type of character the skill can hit
|
||||||
public TargetFindAOETarget aoeTarget;
|
public TargetFindAOEType aoeType; //shape of aoe
|
||||||
public byte numHits;
|
public TargetFindAOETarget aoeTarget; //where the center of the aoe is (target/self)
|
||||||
public BattleCommandPositionBonus positionBonus;
|
public byte numHits; //amount of hits in the skill
|
||||||
public BattleCommandProcRequirement procRequirement;
|
public BattleCommandPositionBonus positionBonus; //bonus for front/flank/rear
|
||||||
public int range;
|
public BattleCommandProcRequirement procRequirement;//if the skill requires a block/parry/evade before using
|
||||||
public uint debuffDurationSeconds;
|
public int range; //max distance to use skill
|
||||||
public uint buffDurationSeconds;
|
|
||||||
public byte castType;
|
public uint statusId; //id of statuseffect that the skill might inflict
|
||||||
public uint castTimeSeconds;
|
public uint statusDuration; //duration of statuseffect in milliseconds
|
||||||
public uint recastTimeSeconds;
|
public float statusChance; //percent chance of status landing, 0-1.0. Usually 1.0 for buffs
|
||||||
|
public byte castType; //casting animation, 2 for blm, 3 for whm, 8 for brd
|
||||||
|
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 mpCost;
|
||||||
public ushort tpCost;
|
public ushort tpCost;
|
||||||
public byte animationType;
|
public byte animationType;
|
||||||
public ushort effectAnimation;
|
public ushort effectAnimation;
|
||||||
public ushort modelAnimation;
|
public ushort modelAnimation;
|
||||||
public ushort animationDurationSeconds;
|
public ushort animationDurationSeconds;
|
||||||
|
|
||||||
public uint battleAnimation;
|
public uint battleAnimation;
|
||||||
public ushort worldMasterTextId;
|
public ushort worldMasterTextId;
|
||||||
public int aoeRange;
|
public int aoeRange; //size of aoe effect. (how will this work for box aoes?)
|
||||||
|
public int[] comboNextCommandId = new int[2]; //next two skills in a combo
|
||||||
|
public short comboStep; //Where in a combo string this skill is
|
||||||
|
public CommandType commandType;
|
||||||
|
public ActionProperty actionProperty;
|
||||||
|
public ActionType actionType;
|
||||||
|
|
||||||
|
|
||||||
|
public byte statusTier; //tier of status to put on target
|
||||||
|
public ulong statusMagnitude = 0; //magnitude of status to put on target
|
||||||
|
public ushort basePotency; //damage variable
|
||||||
|
public float enmityModifier; //multiples by damage done to get final enmity
|
||||||
|
public float accuracyModifier; //modifies accuracy
|
||||||
|
public float bonusCritRate; //extra crit rate
|
||||||
|
public bool isCombo;
|
||||||
|
public bool isRanged;
|
||||||
|
|
||||||
|
public bool actionCrit; //Whether any actions were critical hits, used for Excruciate
|
||||||
|
|
||||||
|
public lua.LuaScript script; //cached script
|
||||||
|
|
||||||
public TargetFind targetFind;
|
public TargetFind targetFind;
|
||||||
public BattleCommandValidUser validUser;
|
public BattleCommandValidUser validUser;
|
||||||
@ -89,6 +135,12 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
this.id = id;
|
this.id = id;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.range = 0;
|
this.range = 0;
|
||||||
|
this.enmityModifier = 1;
|
||||||
|
this.accuracyModifier = 0;
|
||||||
|
this.statusTier = 1;
|
||||||
|
this.statusChance = 50;
|
||||||
|
this.recastTimeMs = (uint) maxRecastTimeSeconds * 1000;
|
||||||
|
this.isCombo = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BattleCommand Clone()
|
public BattleCommand Clone()
|
||||||
@ -96,20 +148,34 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
return (BattleCommand)MemberwiseClone();
|
return (BattleCommand)MemberwiseClone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int CallLuaFunction(Character chara, string functionName, params object[] args)
|
||||||
|
{
|
||||||
|
if (script != null && !script.Globals.Get(functionName).IsNil())
|
||||||
|
{
|
||||||
|
DynValue res = new DynValue();
|
||||||
|
res = script.Call(script.Globals.Get(functionName), args);
|
||||||
|
if (res != null)
|
||||||
|
return (int)res.Number;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsSpell()
|
public bool IsSpell()
|
||||||
{
|
{
|
||||||
return mpCost != 0 || castTimeSeconds != 0;
|
return mpCost != 0 || castTimeMs != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsInstantCast()
|
public bool IsInstantCast()
|
||||||
{
|
{
|
||||||
return castTimeSeconds == 0;
|
return castTimeMs == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsValidTarget(Character user, Character target)
|
//Checks whether the skill can be used on the given target
|
||||||
|
public bool IsValidMainTarget(Character user, Character target)
|
||||||
{
|
{
|
||||||
targetFind = new TargetFind(user);
|
targetFind = new TargetFind(user);
|
||||||
|
|
||||||
if (aoeType == TargetFindAOEType.Box)
|
if (aoeType == TargetFindAOEType.Box)
|
||||||
{
|
{
|
||||||
targetFind.SetAOEBox(validTarget, aoeTarget, range, aoeRange);
|
targetFind.SetAOEBox(validTarget, aoeTarget, range, aoeRange);
|
||||||
@ -119,10 +185,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
targetFind.SetAOEType(validTarget, aoeType, aoeTarget, range, aoeRange);
|
targetFind.SetAOEType(validTarget, aoeType, aoeTarget, range, aoeRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
worldMasterTextId
|
worldMasterTextId
|
||||||
32512 cannot be performed on a KO'd target.
|
32512 cannot be performed on a KO'd target.
|
||||||
32513 can only be performed on a KO'd target.
|
32513 can only be performed on a KO'd target.
|
||||||
32514 cannot be performed on yourself.
|
32514 cannot be performed on yourself.
|
||||||
32515 can only be performed on yourself.
|
32515 can only be performed on yourself.
|
||||||
@ -130,24 +195,36 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
32517 can only be performed on a friendly target.
|
32517 can only be performed on a friendly target.
|
||||||
32518 cannot be performed on an enemy.
|
32518 cannot be performed on an enemy.
|
||||||
32519 can only be performed on an enemy,
|
32519 can only be performed on an enemy,
|
||||||
|
32556 unable to execute [weaponskill]. Conditions for use are not met.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// cant target dead
|
// cant target dead
|
||||||
if ((validTarget & (ValidTarget.Corpse | ValidTarget.CorpseOnly)) == 0 && target.IsDead())
|
if ((mainTarget & (ValidTarget.Corpse | ValidTarget.CorpseOnly)) == 0 && target.IsDead())
|
||||||
{
|
{
|
||||||
// cannot be perfomed on
|
// cannot be perfomed on
|
||||||
if (user is Player)
|
if (user is Player)
|
||||||
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32512, 0x20, (uint)id);
|
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32512, 0x20, (uint)id);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (level > user.charaWork.parameterSave.state_mainSkillLevel)
|
|
||||||
|
//level too high
|
||||||
|
if (level > user.GetLevel())
|
||||||
{
|
{
|
||||||
if (user is Player)
|
if (user is Player)
|
||||||
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32527, 0x20, (uint)id);
|
((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);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tpCost > user.GetTP())
|
//costs too much tp
|
||||||
|
if (CalculateTpCost(user) > user.GetTP())
|
||||||
{
|
{
|
||||||
if (user is Player)
|
if (user is Player)
|
||||||
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32546, 0x20, (uint)id);
|
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32546, 0x20, (uint)id);
|
||||||
@ -155,7 +232,6 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
}
|
}
|
||||||
|
|
||||||
// todo: calculate cost based on modifiers also (probably in BattleUtils)
|
// todo: calculate cost based on modifiers also (probably in BattleUtils)
|
||||||
|
|
||||||
if (BattleUtils.CalculateSpellCost(user, target, this) > user.GetMP())
|
if (BattleUtils.CalculateSpellCost(user, target, this) > user.GetMP())
|
||||||
{
|
{
|
||||||
if (user is Player)
|
if (user is Player)
|
||||||
@ -175,8 +251,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// todo: i dont care to message for each scenario, just the most common ones..
|
// todo: i dont care to message for each scenario, just the most common ones..
|
||||||
if ((validTarget & ValidTarget.CorpseOnly) != 0)
|
if ((mainTarget & ValidTarget.CorpseOnly) != 0)
|
||||||
{
|
{
|
||||||
if (target != null && target.IsAlive())
|
if (target != null && target.IsAlive())
|
||||||
{
|
{
|
||||||
@ -186,7 +263,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((validTarget & ValidTarget.Enemy) != 0)
|
if ((mainTarget & ValidTarget.Enemy) != 0)
|
||||||
{
|
{
|
||||||
if (target == user || target != null &&
|
if (target == user || target != null &&
|
||||||
user.allegiance == target.allegiance)
|
user.allegiance == target.allegiance)
|
||||||
@ -197,21 +274,43 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((validTarget & (ValidTarget.PartyMember | ValidTarget.Player | ValidTarget.Ally)) != 0)
|
if ((mainTarget & ValidTarget.Ally) != 0)
|
||||||
{
|
{
|
||||||
if (target == null || target.allegiance != user.allegiance)
|
if (target == null || target.allegiance != user.allegiance)
|
||||||
{
|
{
|
||||||
if (user is Player)
|
if (user is Player)
|
||||||
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32516, 0x20, (uint)id);
|
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32517, 0x20, (uint)id);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return targetFind.CanTarget(target, true, true, true);
|
|
||||||
|
if ((mainTarget & ValidTarget.PartyMember) != 0)
|
||||||
|
{
|
||||||
|
if (target == null || target.currentParty != user.currentParty)
|
||||||
|
{
|
||||||
|
if (user is Player)
|
||||||
|
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32547, 0x20, (uint)id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((mainTarget & ValidTarget.Player) != 0)
|
||||||
|
{
|
||||||
|
if (!(target is Player))
|
||||||
|
{
|
||||||
|
if (user is Player)
|
||||||
|
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32517, 0x20, (uint)id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;// targetFind.CanTarget(target, true, true, true); //this will be done later
|
||||||
}
|
}
|
||||||
|
|
||||||
public ushort CalculateCost(uint level)
|
public ushort CalculateMpCost(Character user)
|
||||||
{
|
{
|
||||||
// todo: use precalculated costs instead
|
// todo: use precalculated costs instead
|
||||||
|
var level = user.GetLevel();
|
||||||
ushort cost = 0;
|
ushort cost = 0;
|
||||||
if (level <= 10)
|
if (level <= 10)
|
||||||
cost = (ushort)(100 + level * 10);
|
cost = (ushort)(100 + level * 10);
|
||||||
@ -230,15 +329,64 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
else
|
else
|
||||||
cost = (ushort)(8000 + (level - 70) * 500);
|
cost = (ushort)(8000 + (level - 70) * 500);
|
||||||
|
|
||||||
if (mpCost != 0)
|
//scale the mpcost by level
|
||||||
return (ushort)Math.Ceiling((cost * mpCost * 0.001));
|
cost = (ushort)Math.Ceiling((cost * mpCost * 0.001));
|
||||||
|
|
||||||
return mpCost != 0 ? (ushort)Math.Ceiling((cost * mpCost * 0.001)) : (ushort)0;
|
//if user is player, check if spell is a part of combo
|
||||||
|
if (user is Player)
|
||||||
|
{
|
||||||
|
var player = user as Player;
|
||||||
|
if (player.playerWork.comboNextCommandId[0] == id || player.playerWork.comboNextCommandId[1] == id)
|
||||||
|
cost = (ushort)Math.Ceiling(cost * (1 - player.playerWork.comboCostBonusRate));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mpCost != 0 ? cost : (ushort)0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//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)
|
||||||
|
{
|
||||||
|
ushort 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
return tp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Character> GetTargets()
|
public List<Character> GetTargets()
|
||||||
{
|
{
|
||||||
return targetFind?.GetTargets<Character>();
|
return targetFind?.GetTargets<Character>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Handles setting the correct target for self-targeted spells
|
||||||
|
public Character GetMainTarget(Character caster, Character target)
|
||||||
|
{
|
||||||
|
//If skill can only be used on self
|
||||||
|
if (mainTarget == ValidTarget.Self)
|
||||||
|
return caster;
|
||||||
|
//If skill can only be used on party members and the target is not a party member
|
||||||
|
else if (((mainTarget & ValidTarget.PartyMember) != 0) && (target.currentParty != caster.currentParty))
|
||||||
|
return caster;
|
||||||
|
//If skill can only be used on allys and the target is not an ally
|
||||||
|
else if (((mainTarget & ValidTarget.Ally) != 0) && (target.allegiance != caster.allegiance))
|
||||||
|
return caster;
|
||||||
|
//If skill can only be used on players and the target is not a player
|
||||||
|
else if (((mainTarget & ValidTarget.Player) != 0) && (!(target is Player)))
|
||||||
|
return caster;
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ushort GetCommandType()
|
||||||
|
{
|
||||||
|
return (ushort) commandType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
28
FFXIVClassic Map Server/actors/chara/ai/BattleTrait.cs
Normal file
28
FFXIVClassic Map Server/actors/chara/ai/BattleTrait.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FFXIVClassic_Map_Server.actors.chara.ai
|
||||||
|
{
|
||||||
|
class BattleTrait
|
||||||
|
{
|
||||||
|
public ushort id;
|
||||||
|
public string name;
|
||||||
|
public byte job;
|
||||||
|
public byte level;
|
||||||
|
public uint modifier;
|
||||||
|
public int bonus;
|
||||||
|
|
||||||
|
public BattleTrait(ushort id, string name, byte job, byte level, uint modifier, int bonus)
|
||||||
|
{
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.job = job;
|
||||||
|
this.level = level;
|
||||||
|
this.modifier = modifier;
|
||||||
|
this.bonus = bonus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,13 +7,15 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MoonSharp.Interpreter;
|
||||||
|
using FFXIVClassic.Common;
|
||||||
|
|
||||||
namespace FFXIVClassic_Map_Server.actors.chara.ai
|
namespace FFXIVClassic_Map_Server.actors.chara.ai
|
||||||
{
|
{
|
||||||
enum StatusEffectId : uint
|
enum StatusEffectId : uint
|
||||||
{
|
{
|
||||||
RageofHalone = 221021,
|
RageofHalone = 221021,
|
||||||
|
|
||||||
Quick = 223001,
|
Quick = 223001,
|
||||||
Haste = 223002,
|
Haste = 223002,
|
||||||
Slow = 223003,
|
Slow = 223003,
|
||||||
@ -98,7 +100,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
Retaliation = 223082,
|
Retaliation = 223082,
|
||||||
Foresight = 223083,
|
Foresight = 223083,
|
||||||
Defender = 223084,
|
Defender = 223084,
|
||||||
Rampage = 223085,
|
Rampage = 223085, //old effect
|
||||||
Enraged = 223086,
|
Enraged = 223086,
|
||||||
Warmonger = 223087,
|
Warmonger = 223087,
|
||||||
Disorientx1 = 223088,
|
Disorientx1 = 223088,
|
||||||
@ -118,10 +120,10 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
LifeSap = 223102,
|
LifeSap = 223102,
|
||||||
Farshot = 223103,
|
Farshot = 223103,
|
||||||
QuellingStrike = 223104,
|
QuellingStrike = 223104,
|
||||||
RagingStrike = 223105,
|
RagingStrike = 223105, //old effect
|
||||||
HawksEye = 223106,
|
HawksEye = 223106,
|
||||||
SubtleRelease = 223107,
|
SubtleRelease = 223107,
|
||||||
Decoy = 223108,
|
Decoy = 223108, //Untraited
|
||||||
Profundity = 223109,
|
Profundity = 223109,
|
||||||
TranceChant = 223110,
|
TranceChant = 223110,
|
||||||
RoamingSoul = 223111,
|
RoamingSoul = 223111,
|
||||||
@ -139,10 +141,10 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
ConcussiveBlowx2 = 223124,
|
ConcussiveBlowx2 = 223124,
|
||||||
ConcussiveBlowx3 = 223125,
|
ConcussiveBlowx3 = 223125,
|
||||||
SkullSunder = 223126,
|
SkullSunder = 223126,
|
||||||
Bloodletter = 223127,
|
Bloodletter = 223127, //comboed effect
|
||||||
Levinbolt = 223128,
|
Levinbolt = 223128,
|
||||||
Protect = 223129,
|
Protect = 223129, //old Protect
|
||||||
Shell = 223130,
|
Shell = 223130, //old shell
|
||||||
Reraise = 223131,
|
Reraise = 223131,
|
||||||
ShockSpikes = 223132,
|
ShockSpikes = 223132,
|
||||||
Stoneskin = 223133,
|
Stoneskin = 223133,
|
||||||
@ -218,8 +220,8 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
StealthIV = 223204,
|
StealthIV = 223204,
|
||||||
Combo = 223205,
|
Combo = 223205,
|
||||||
GoringBlade = 223206,
|
GoringBlade = 223206,
|
||||||
Berserk2 = 223207,
|
Berserk2 = 223207, //new effect
|
||||||
Rampage2 = 223208,
|
Rampage2 = 223208, //new effect
|
||||||
FistsofFire = 223209,
|
FistsofFire = 223209,
|
||||||
FistsofEarth = 223210,
|
FistsofEarth = 223210,
|
||||||
FistsofWind = 223211,
|
FistsofWind = 223211,
|
||||||
@ -248,10 +250,10 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
Aero = 223235,
|
Aero = 223235,
|
||||||
Outmaneuver2 = 223236,
|
Outmaneuver2 = 223236,
|
||||||
Blindside2 = 223237,
|
Blindside2 = 223237,
|
||||||
Decoy2 = 223238,
|
Decoy2 = 223238, //Traited
|
||||||
Protect2 = 223239,
|
Protect2 = 223239, //new Protect
|
||||||
SanguineRite3 = 223240,
|
SanguineRite3 = 223240,
|
||||||
Bloodletter2 = 223241,
|
Bloodletter2 = 223241, //uncomboed effect
|
||||||
FullyBlissfulMind = 223242,
|
FullyBlissfulMind = 223242,
|
||||||
MagicEvasionDown = 223243,
|
MagicEvasionDown = 223243,
|
||||||
HundredFists = 223244,
|
HundredFists = 223244,
|
||||||
@ -322,24 +324,58 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
|
|
||||||
|
|
||||||
// custom effects here
|
// custom effects here
|
||||||
|
// status for having procs fall off
|
||||||
|
EvadeProc = 253003,
|
||||||
|
BlockProc = 253004,
|
||||||
|
ParryProc = 253005,
|
||||||
|
MissProc = 253006,
|
||||||
|
EXPChain = 253007
|
||||||
}
|
}
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
enum StatusEffectFlags : uint
|
enum StatusEffectFlags : uint
|
||||||
{
|
{
|
||||||
None = 0x00,
|
None = 0,
|
||||||
Silent = 0x01, // dont display effect loss message
|
Silent = 1 << 0, // dont display effect loss message
|
||||||
LoseOnDeath = 0x02, // effects removed on death
|
|
||||||
LoseOnZoning = 0x04, // effects removed on zoning
|
|
||||||
LoseOnEsuna = 0x08, // effects which can be removed with esuna (debuffs)
|
|
||||||
LoseOnDispel = 0x10, // some buffs which player might be able to dispel from mob
|
|
||||||
LoseOnLogout = 0x20, // effects removed on logging out
|
|
||||||
LoseOnAttacking = 0x40, // effects removed when owner attacks another entity
|
|
||||||
LoseOnCasting = 0x80, // effects removed when owner starts casting
|
|
||||||
LoseOnDamageTaken = 0x100, // effects removed when owner takes damage
|
|
||||||
|
|
||||||
PreventAction = 0x200, // effects which prevent actions such as sleep/paralyze/petrify
|
//Loss flags
|
||||||
Stealth = 0x400, // sneak/invis
|
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)
|
||||||
|
|
||||||
|
//Activate flags
|
||||||
|
ActivateOnCastStart = 1 << 9, //Activates when a cast starts.
|
||||||
|
ActivateOnCommandStart = 1 << 10, //Activates when a command is used, before iterating over targets. Used for things like power surge, excruciate.
|
||||||
|
ActivateOnCommandFinish = 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 = 1 << 12, //Activates after initial rates are calculated for an action against owner
|
||||||
|
ActivateOnPreactionCaster = 1 << 13, //Activates after initial rates are calculated for an action by owner
|
||||||
|
ActivateOnDamageTaken = 1 << 14,
|
||||||
|
ActivateOnHealed = 1 << 15,
|
||||||
|
|
||||||
|
//Should these be rolled into DamageTaken?
|
||||||
|
ActivateOnMiss = 1 << 16, //Activates when owner misses
|
||||||
|
ActivateOnEvade = 1 << 17, //Activates when owner evades
|
||||||
|
ActivateOnParry = 1 << 18, //Activates when owner parries
|
||||||
|
ActivateOnBlock = 1 << 19, //Activates when owner evades
|
||||||
|
ActivateOnHit = 1 << 20, //Activates when owner hits
|
||||||
|
ActivateOnCrit = 1 << 21, //Activates when owner crits
|
||||||
|
|
||||||
|
//Prevent flags. Sleep/stun/petrify/etc combine these
|
||||||
|
PreventSpell = 1 << 22, // effects which prevent using spells, such as silence
|
||||||
|
PreventWeaponSkill = 1 << 23, // effects which prevent using weaponskills, such as pacification
|
||||||
|
PreventAbility = 1 << 24, // effects which prevent using abilities, such as amnesia
|
||||||
|
PreventAttack = 1 << 25, // effects which prevent basic attacks
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
enum StatusEffectOverwrite : byte
|
enum StatusEffectOverwrite : byte
|
||||||
@ -353,32 +389,34 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
class StatusEffect
|
class StatusEffect
|
||||||
{
|
{
|
||||||
// todo: probably use get;set;
|
// todo: probably use get;set;
|
||||||
|
|
||||||
private Character owner;
|
private Character owner;
|
||||||
private Character source;
|
private Character source;
|
||||||
private StatusEffectId id;
|
private StatusEffectId id;
|
||||||
private string name; // name of this effect
|
private string name; // name of this effect
|
||||||
private DateTime startTime; // when was this effect added
|
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 DateTime lastTick; // when did this effect last tick
|
||||||
private uint durationMs; // how long should this effect last in ms
|
private uint duration; // how long should this effect last in seconds
|
||||||
private uint tickMs; // how often should this effect proc
|
private uint tickMs; // how often should this effect proc
|
||||||
private UInt64 magnitude; // a value specified by scripter which is guaranteed to be used by all effects
|
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 byte tier; // same effect with higher tier overwrites this
|
||||||
private UInt64 extra; // optional value
|
private double extra; // optional value
|
||||||
private StatusEffectFlags flags; // death/erase/dispel etc
|
private StatusEffectFlags flags; // death/erase/dispel etc
|
||||||
private StatusEffectOverwrite overwrite; // how to handle adding an effect with same id (see StatusEfectOverwrite)
|
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 silent = false; // do i send a message on losing effect
|
||||||
|
private bool hidden = false;
|
||||||
|
public LuaScript script;
|
||||||
|
|
||||||
HitEffect animationEffect;
|
HitEffect animationEffect;
|
||||||
|
|
||||||
public StatusEffect(Character owner, uint id, UInt64 magnitude, uint tickMs, uint durationMs, byte tier = 0)
|
public StatusEffect(Character owner, uint id, double magnitude, uint tickMs, uint duration, byte tier = 0)
|
||||||
{
|
{
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
this.source = owner;
|
this.source = owner;
|
||||||
this.id = (StatusEffectId)id;
|
this.id = (StatusEffectId)id;
|
||||||
this.magnitude = magnitude;
|
this.magnitude = magnitude;
|
||||||
this.tickMs = tickMs;
|
this.tickMs = tickMs;
|
||||||
this.durationMs = durationMs;
|
this.duration = duration;
|
||||||
this.tier = tier;
|
this.tier = tier;
|
||||||
|
|
||||||
this.startTime = DateTime.Now;
|
this.startTime = DateTime.Now;
|
||||||
@ -392,7 +430,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
this.id = effect.id;
|
this.id = effect.id;
|
||||||
this.magnitude = effect.magnitude;
|
this.magnitude = effect.magnitude;
|
||||||
this.tickMs = effect.tickMs;
|
this.tickMs = effect.tickMs;
|
||||||
this.durationMs = effect.durationMs;
|
this.duration = effect.duration;
|
||||||
this.tier = effect.tier;
|
this.tier = effect.tier;
|
||||||
this.startTime = effect.startTime;
|
this.startTime = effect.startTime;
|
||||||
this.lastTick = effect.lastTick;
|
this.lastTick = effect.lastTick;
|
||||||
@ -401,14 +439,16 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
this.flags = effect.flags;
|
this.flags = effect.flags;
|
||||||
this.overwrite = effect.overwrite;
|
this.overwrite = effect.overwrite;
|
||||||
this.extra = effect.extra;
|
this.extra = effect.extra;
|
||||||
|
this.script = effect.script;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StatusEffect(uint id, string name, uint flags, uint overwrite)
|
public StatusEffect(uint id, string name, uint flags, uint overwrite, uint tickMs)
|
||||||
{
|
{
|
||||||
this.id = (StatusEffectId)id;
|
this.id = (StatusEffectId)id;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.flags = (StatusEffectFlags)flags;
|
this.flags = (StatusEffectFlags)flags;
|
||||||
this.overwrite = (StatusEffectOverwrite)overwrite;
|
this.overwrite = (StatusEffectOverwrite)overwrite;
|
||||||
|
this.tickMs = tickMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
// return true when duration has elapsed
|
// return true when duration has elapsed
|
||||||
@ -417,16 +457,32 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
if (tickMs != 0 && (tick - lastTick).TotalMilliseconds >= tickMs)
|
if (tickMs != 0 && (tick - lastTick).TotalMilliseconds >= tickMs)
|
||||||
{
|
{
|
||||||
lastTick = tick;
|
lastTick = tick;
|
||||||
LuaEngine.CallLuaStatusEffectFunction(this.owner, this, "onTick", this.owner, this);
|
if (LuaEngine.CallLuaStatusEffectFunction(this.owner, this, "onTick", this.owner, this) > 0)
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (durationMs != 0 && (tick - startTime).TotalMilliseconds >= durationMs)
|
if (duration >= 0 && tick >= endTime)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int CallLuaFunction(Character chara, string functionName, params object[] args)
|
||||||
|
{
|
||||||
|
|
||||||
|
DynValue res = new DynValue();
|
||||||
|
|
||||||
|
return lua.LuaEngine.CallLuaStatusEffectFunction(chara, this, functionName, args);
|
||||||
|
if (!script.Globals.Get(functionName).IsNil())
|
||||||
|
{
|
||||||
|
res = script.Call(script.Globals.Get(functionName), args);
|
||||||
|
if (res != null)
|
||||||
|
return (int)res.Number;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public Character GetOwner()
|
public Character GetOwner()
|
||||||
{
|
{
|
||||||
return owner;
|
return owner;
|
||||||
@ -452,14 +508,19 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
return startTime;
|
return startTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DateTime GetEndTime()
|
||||||
|
{
|
||||||
|
return endTime;
|
||||||
|
}
|
||||||
|
|
||||||
public string GetName()
|
public string GetName()
|
||||||
{
|
{
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public uint GetDurationMs()
|
public uint GetDuration()
|
||||||
{
|
{
|
||||||
return durationMs;
|
return duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
public uint GetTickMs()
|
public uint GetTickMs()
|
||||||
@ -467,7 +528,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
return tickMs;
|
return tickMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UInt64 GetMagnitude()
|
public double GetMagnitude()
|
||||||
{
|
{
|
||||||
return magnitude;
|
return magnitude;
|
||||||
}
|
}
|
||||||
@ -477,7 +538,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
return tier;
|
return tier;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UInt64 GetExtra()
|
public double GetExtra()
|
||||||
{
|
{
|
||||||
return extra;
|
return extra;
|
||||||
}
|
}
|
||||||
@ -497,12 +558,32 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
return silent;
|
return silent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool GetHidden()
|
||||||
|
{
|
||||||
|
return hidden;
|
||||||
|
}
|
||||||
|
|
||||||
public void SetStartTime(DateTime time)
|
public void SetStartTime(DateTime time)
|
||||||
{
|
{
|
||||||
this.startTime = time;
|
this.startTime = time;
|
||||||
this.lastTick = time;
|
this.lastTick = time;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetEndTime(DateTime time)
|
||||||
|
{
|
||||||
|
endTime = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Refresh the status, updating the end time based on the duration of the status and broadcasts the new time
|
||||||
|
public void RefreshTime()
|
||||||
|
{
|
||||||
|
endTime = DateTime.Now.AddSeconds(GetDuration());
|
||||||
|
int index = Array.IndexOf(owner.charaWork.status, GetStatusId());
|
||||||
|
|
||||||
|
if (index >= 0)
|
||||||
|
owner.statusEffects.SetTimeAtIndex(index, (uint) Utils.UnixTimeStampUTC(endTime));
|
||||||
|
}
|
||||||
|
|
||||||
public void SetOwner(Character owner)
|
public void SetOwner(Character owner)
|
||||||
{
|
{
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
@ -518,14 +599,14 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetMagnitude(UInt64 magnitude)
|
public void SetMagnitude(double magnitude)
|
||||||
{
|
{
|
||||||
this.magnitude = magnitude;
|
this.magnitude = magnitude;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetDurationMs(uint durationMs)
|
public void SetDuration(uint duration)
|
||||||
{
|
{
|
||||||
this.durationMs = durationMs;
|
this.duration = duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetTickMs(uint tickMs)
|
public void SetTickMs(uint tickMs)
|
||||||
@ -538,7 +619,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
this.tier = tier;
|
this.tier = tier;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetExtra(UInt64 val)
|
public void SetExtra(double val)
|
||||||
{
|
{
|
||||||
this.extra = val;
|
this.extra = val;
|
||||||
}
|
}
|
||||||
@ -558,6 +639,11 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
this.silent = silent;
|
this.silent = silent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetHidden(bool hidden)
|
||||||
|
{
|
||||||
|
this.hidden = hidden;
|
||||||
|
}
|
||||||
|
|
||||||
public void SetAnimation(uint hitEffect)
|
public void SetAnimation(uint hitEffect)
|
||||||
{
|
{
|
||||||
animationEffect = (HitEffect)hitEffect;
|
animationEffect = (HitEffect)hitEffect;
|
||||||
|
@ -9,6 +9,7 @@ using FFXIVClassic_Map_Server.lua;
|
|||||||
using FFXIVClassic_Map_Server.actors.area;
|
using FFXIVClassic_Map_Server.actors.area;
|
||||||
using FFXIVClassic_Map_Server.packets.send;
|
using FFXIVClassic_Map_Server.packets.send;
|
||||||
using FFXIVClassic_Map_Server.packets.send.actor;
|
using FFXIVClassic_Map_Server.packets.send.actor;
|
||||||
|
using FFXIVClassic_Map_Server.packets.send.actor.battle;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using FFXIVClassic_Map_Server.utils;
|
using FFXIVClassic_Map_Server.utils;
|
||||||
|
|
||||||
@ -20,22 +21,36 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
private readonly Dictionary<uint, StatusEffect> effects;
|
private readonly Dictionary<uint, StatusEffect> effects;
|
||||||
public static readonly int MAX_EFFECTS = 20;
|
public static readonly int MAX_EFFECTS = 20;
|
||||||
private bool sendUpdate = false;
|
private bool sendUpdate = false;
|
||||||
|
private DateTime lastTick;// Do all effects tick at the same time like regen?
|
||||||
|
private List<SubPacket> statusSubpackets;
|
||||||
|
private ActorPropertyPacketUtil statusTimerPropPacketUtil;
|
||||||
|
|
||||||
public StatusEffectContainer(Character owner)
|
public StatusEffectContainer(Character owner)
|
||||||
{
|
{
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
this.effects = new Dictionary<uint, StatusEffect>();
|
this.effects = new Dictionary<uint, StatusEffect>();
|
||||||
|
statusSubpackets = new List<SubPacket>();
|
||||||
|
statusTimerPropPacketUtil = new ActorPropertyPacketUtil("charawork/Status", owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update(DateTime tick)
|
public void Update(DateTime tick)
|
||||||
{
|
{
|
||||||
|
//Regen/Refresh/Regain effects tick every 3 seconds
|
||||||
|
if ((DateTime.Now - lastTick).Seconds >= 3)
|
||||||
|
{
|
||||||
|
RegenTick(tick);
|
||||||
|
lastTick = DateTime.Now;
|
||||||
|
}
|
||||||
// list of effects to remove
|
// list of effects to remove
|
||||||
|
|
||||||
|
// if (owner is Player) UpdateTimeAtIndex(4, 4294967295);
|
||||||
var removeEffects = new List<StatusEffect>();
|
var removeEffects = new List<StatusEffect>();
|
||||||
foreach (var effect in effects.Values)
|
for (int i = 0; i < effects.Values.Count; i++)
|
||||||
{
|
{
|
||||||
// effect's update function returns true if effect has completed
|
// effect's update function returns true if effect has completed
|
||||||
if (effect.Update(tick))
|
if (effects.Values.ElementAt(i).Update(tick))
|
||||||
removeEffects.Add(effect);
|
removeEffects.Add(effects.Values.ElementAt(i));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove effects from this list
|
// remove effects from this list
|
||||||
@ -51,6 +66,31 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//regen/refresh/regain
|
||||||
|
public void RegenTick(DateTime tick)
|
||||||
|
{
|
||||||
|
ushort dotTick = (ushort) owner.GetMod(Modifier.RegenDown);
|
||||||
|
ushort regenTick = (ushort) owner.GetMod(Modifier.Regen);
|
||||||
|
ushort refreshtick = (ushort) owner.GetMod(Modifier.Refresh);
|
||||||
|
short regainTick = (short) owner.GetMod(Modifier.Regain);
|
||||||
|
|
||||||
|
//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)
|
||||||
|
{
|
||||||
|
BattleAction action = new BattleAction(owner.actorId, 30331, (uint)(HitEffect.HitEffectType | HitEffect.Hit), 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);
|
||||||
|
|
||||||
|
owner.DoBattleAction(0, 0, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
//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);
|
||||||
|
}
|
||||||
|
|
||||||
public bool HasStatusEffect(uint id)
|
public bool HasStatusEffect(uint id)
|
||||||
{
|
{
|
||||||
return effects.ContainsKey(id);
|
return effects.ContainsKey(id);
|
||||||
@ -61,32 +101,77 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
return effects.ContainsKey((uint)id);
|
return effects.ContainsKey((uint)id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AddStatusEffect(uint id, UInt64 magnitude, double tickMs, double durationMs, byte tier = 0)
|
public BattleAction AddStatusForBattleAction(uint id, byte tier = 1)
|
||||||
{
|
{
|
||||||
return AddStatusEffect(new StatusEffect(this.owner, id, magnitude, (uint)(tickMs * 1000), (uint)(durationMs * 1000), tier), owner);
|
BattleAction action = null;
|
||||||
|
|
||||||
|
if (AddStatusEffect(id, tier))
|
||||||
|
action = new BattleAction(owner.actorId, 30328, id | (uint)HitEffect.StatusEffectType);
|
||||||
|
|
||||||
|
return action;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AddStatusEffect(StatusEffect newEffect, Character source, bool silent = false)
|
public bool AddStatusEffect(uint id)
|
||||||
|
{
|
||||||
|
var se = Server.GetWorldManager().GetStatusEffect(id);
|
||||||
|
|
||||||
|
return AddStatusEffect(se, owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AddStatusEffect(uint id, byte tier)
|
||||||
|
{
|
||||||
|
var se = Server.GetWorldManager().GetStatusEffect(id);
|
||||||
|
|
||||||
|
se.SetTier(tier);
|
||||||
|
|
||||||
|
return AddStatusEffect(se, owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AddStatusEffect(uint id, byte tier, UInt64 magnitude)
|
||||||
|
{
|
||||||
|
var se = Server.GetWorldManager().GetStatusEffect(id);
|
||||||
|
|
||||||
|
se.SetMagnitude(magnitude);
|
||||||
|
se.SetTier(tier);
|
||||||
|
|
||||||
|
return AddStatusEffect(se, owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AddStatusEffect(uint id, byte tier, UInt64 magnitude, uint duration, int tickMs = 3000)
|
||||||
|
{
|
||||||
|
var se = Server.GetWorldManager().GetStatusEffect(id);
|
||||||
|
if (se != null)
|
||||||
|
{
|
||||||
|
se.SetDuration(duration);
|
||||||
|
se.SetStartTime(DateTime.Now);
|
||||||
|
se.SetOwner(owner);
|
||||||
|
}
|
||||||
|
return AddStatusEffect(se ?? new StatusEffect(this.owner, id, magnitude, 3000, duration, tier), owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AddStatusEffect(StatusEffect newEffect, Character source, bool silent = false, bool hidden = false)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
worldMasterTextId
|
worldMasterTextId
|
||||||
32001 [@2B([@IF($E4($EB(1),$EB(2)),you,[@IF($E9(7),[@SHEETEN(xtx/displayName,2,$E9(7),1,1)],$EB(2))])])] [@IF($E4($EB(1),$EB(2)),resist,resists)] the effect of [@SHEET(xtx/status,$E8(11),3)].
|
32001 [@2B([@IF($E4($EB(1),$EB(2)),you,[@IF($E9(7),[@SHEETEN(xtx/displayName,2,$E9(7),1,1)],$EB(2))])])] [@IF($E4($EB(1),$EB(2)),resist,resists)] the effect of [@SHEET(xtx/status,$E8(11),3)].
|
||||||
32002 [@SHEET(xtx/status,$E8(11),3)] fails to take effect.
|
32002 [@SHEET(xtx/status,$E8(11),3)] fails to take effect.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var effect = GetStatusEffectById(newEffect.GetStatusEffectId());
|
var effect = GetStatusEffectById(newEffect.GetStatusEffectId());
|
||||||
|
|
||||||
bool canOverwrite = false;
|
bool canOverwrite = false;
|
||||||
if (effect != null)
|
if (effect != null)
|
||||||
{
|
{
|
||||||
var overwritable = effect.GetOverwritable();
|
var overwritable = effect.GetOverwritable();
|
||||||
canOverwrite = (overwritable == (uint)StatusEffectOverwrite.Always) ||
|
canOverwrite = (overwritable == (uint)StatusEffectOverwrite.Always) ||
|
||||||
(overwritable == (uint)StatusEffectOverwrite.GreaterOnly && (effect.GetDurationMs() < newEffect.GetDurationMs() || effect.GetMagnitude() < newEffect.GetMagnitude())) ||
|
(overwritable == (uint)StatusEffectOverwrite.GreaterOnly && (effect.GetDuration() < newEffect.GetDuration() || effect.GetMagnitude() < newEffect.GetMagnitude())) ||
|
||||||
(overwritable == (uint)StatusEffectOverwrite.GreaterOrEqualTo && (effect.GetDurationMs() <= newEffect.GetDurationMs() || effect.GetMagnitude() <= newEffect.GetMagnitude()));
|
(overwritable == (uint)StatusEffectOverwrite.GreaterOrEqualTo && (effect.GetDuration() <= newEffect.GetDuration() || effect.GetMagnitude() <= newEffect.GetMagnitude()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canOverwrite || effect == null)
|
if (canOverwrite || effect == null)
|
||||||
{
|
{
|
||||||
// send packet to client with effect added message
|
// send packet to client with effect added message
|
||||||
if (!silent || !effect.GetSilent() || (effect.GetFlags() & (uint)StatusEffectFlags.Silent) == 0)
|
if (effect != null && (!silent || !effect.GetSilent() || (effect.GetFlags() & (uint)StatusEffectFlags.Silent) == 0))
|
||||||
{
|
{
|
||||||
// todo: send packet to client with effect added message
|
// todo: send packet to client with effect added message
|
||||||
}
|
}
|
||||||
@ -95,18 +180,39 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
if (canOverwrite)
|
if (canOverwrite)
|
||||||
effects.Remove(newEffect.GetStatusEffectId());
|
effects.Remove(newEffect.GetStatusEffectId());
|
||||||
|
|
||||||
|
newEffect.SetStartTime(DateTime.Now);
|
||||||
|
newEffect.SetEndTime(DateTime.Now.AddSeconds(newEffect.GetDuration()));
|
||||||
|
newEffect.SetOwner(owner);
|
||||||
|
|
||||||
if (effects.Count < MAX_EFFECTS)
|
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);
|
||||||
effects.Add(newEffect.GetStatusEffectId(), newEffect);
|
effects.Add(newEffect.GetStatusEffectId(), newEffect);
|
||||||
newEffect.SetSilent(silent);
|
newEffect.SetSilent(silent);
|
||||||
|
newEffect.SetHidden(hidden);
|
||||||
|
|
||||||
|
if (!newEffect.GetHidden())
|
||||||
{
|
{
|
||||||
var index = Array.IndexOf(effects.Values.ToArray(), newEffect);
|
int index = 0;
|
||||||
owner.charaWork.status[index] = newEffect.GetStatusId();
|
|
||||||
owner.charaWork.statusShownTime[index] = Utils.UnixTimeStampUTC() + (newEffect.GetDurationMs() / 1000);
|
//If effect is already in the list of statuses, get that index, otherwise find the first open index
|
||||||
this.owner.zone.BroadcastPacketAroundActor(this.owner, SetActorStatusPacket.BuildPacket(this.owner.actorId, (ushort)index, (ushort)newEffect.GetStatusId()));
|
if (owner.charaWork.status.Contains(newEffect.GetStatusId()))
|
||||||
}
|
index = Array.IndexOf(owner.charaWork.status, newEffect.GetStatusId());
|
||||||
{
|
else
|
||||||
owner.zone.BroadcastPacketsAroundActor(owner, owner.GetActorStatusPackets());
|
index = Array.IndexOf(owner.charaWork.status, (ushort) 0);
|
||||||
|
|
||||||
|
//owner.charaWork.status[index] = newEffect.GetStatusId();
|
||||||
|
SetStatusAtIndex(index, newEffect.GetStatusId());
|
||||||
|
//Stance statuses need their time set to an extremely high number so their icon doesn't flash
|
||||||
|
//Adding getduration with them doesn't work because it overflows
|
||||||
|
uint time = (newEffect.GetFlags() & (uint) StatusEffectFlags.Stance) == 0 ? Utils.UnixTimeStampUTC(newEffect.GetEndTime()) : 0xFFFFFFFF;
|
||||||
|
SetTimeAtIndex(index, time);
|
||||||
|
//owner.charaWork.statusShownTime[index] = time;
|
||||||
|
//owner.zone.BroadcastPacketAroundActor(owner, SetActorStatusPacket.BuildPacket(owner.actorId, (ushort)index, newEffect.GetStatusId()));
|
||||||
|
//owner.zone.BroadcastPacketsAroundActor(owner, owner.GetActorStatusPackets());
|
||||||
}
|
}
|
||||||
owner.RecalculateStats();
|
owner.RecalculateStats();
|
||||||
}
|
}
|
||||||
@ -115,47 +221,80 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveStatusEffect(StatusEffect effect, bool silent = false)
|
public bool RemoveStatusEffect(StatusEffect effect, bool silent = false)
|
||||||
{
|
{
|
||||||
if (effects.ContainsKey(effect.GetStatusEffectId()))
|
bool removedEffect = false;
|
||||||
|
if (effect != null && effects.ContainsKey(effect.GetStatusEffectId()))
|
||||||
{
|
{
|
||||||
// send packet to client with effect remove message
|
// send packet to client with effect remove message
|
||||||
if (!silent && !effect.GetSilent() || (effect.GetFlags() & (uint)StatusEffectFlags.Silent) == 0)
|
if (!silent && !effect.GetSilent() && (effect.GetFlags() & (uint)StatusEffectFlags.Silent) == 0)
|
||||||
{
|
{
|
||||||
// todo: send packet to client with effect added message
|
owner.DoBattleAction(0, 0, new BattleAction(owner.actorId, 30331, effect.GetStatusEffectId()));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//hidden effects not in charawork
|
||||||
|
if(!effect.GetHidden())
|
||||||
{
|
{
|
||||||
var index = Array.IndexOf(effects.Values.ToArray(), effect);
|
var index = Array.IndexOf(owner.charaWork.status, effect.GetStatusId());
|
||||||
|
|
||||||
owner.charaWork.status[index] = 0;
|
owner.charaWork.status[index] = 0;
|
||||||
owner.charaWork.statusShownTime[index] = uint.MaxValue;
|
owner.charaWork.statusShownTime[index] = 0;
|
||||||
this.owner.zone.BroadcastPacketAroundActor(this.owner, SetActorStatusPacket.BuildPacket(owner.actorId, (ushort)index, (ushort)0));
|
this.owner.zone.BroadcastPacketAroundActor(this.owner, SetActorStatusPacket.BuildPacket(owner.actorId, (ushort)index, (ushort)0));
|
||||||
}
|
}
|
||||||
// function onLose(actor, effect)
|
// function onLose(actor, effect)
|
||||||
LuaEngine.CallLuaStatusEffectFunction(this.owner, effect, "onLose", this.owner, effect);
|
|
||||||
effects.Remove(effect.GetStatusEffectId());
|
effects.Remove(effect.GetStatusEffectId());
|
||||||
|
if(effect.script != null)
|
||||||
|
effect.CallLuaFunction(owner, "onLose", owner, effect);
|
||||||
|
else
|
||||||
|
LuaEngine.CallLuaStatusEffectFunction(this.owner, effect, "onLose", this.owner, effect);
|
||||||
owner.RecalculateStats();
|
owner.RecalculateStats();
|
||||||
sendUpdate = true;
|
sendUpdate = true;
|
||||||
|
removedEffect = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return removedEffect;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveStatusEffect(uint effectId, bool silent = false)
|
public bool RemoveStatusEffect(uint effectId, bool silent = false)
|
||||||
{
|
{
|
||||||
|
bool removedEffect = false;
|
||||||
foreach (var effect in effects.Values)
|
foreach (var effect in effects.Values)
|
||||||
{
|
{
|
||||||
if (effect.GetStatusEffectId() == effectId)
|
if (effect.GetStatusEffectId() == effectId)
|
||||||
{
|
{
|
||||||
RemoveStatusEffect(effect, effect.GetSilent() || silent);
|
RemoveStatusEffect(effect, effect.GetSilent() || silent);
|
||||||
|
removedEffect = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return removedEffect;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Remove status effect and return the battleaction message instead of sending it immediately
|
||||||
|
public BattleAction RemoveStatusEffectForBattleAction(uint effectId, ushort worldMasterTextId = 30331)
|
||||||
|
{
|
||||||
|
BattleAction action = null;
|
||||||
|
if (RemoveStatusEffect(effectId, true))
|
||||||
|
action = new BattleAction(owner.actorId, worldMasterTextId, effectId);
|
||||||
|
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Remove status effect and return the battleaction message instead of sending it immediately
|
||||||
|
public BattleAction RemoveStatusEffectForBattleAction(StatusEffect effect, ushort worldMasterTextId = 30331)
|
||||||
|
{
|
||||||
|
BattleAction action = null;
|
||||||
|
if (RemoveStatusEffect(effect, true))
|
||||||
|
action = new BattleAction(owner.actorId, worldMasterTextId, effect.GetStatusEffectId());
|
||||||
|
return action;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StatusEffect CopyEffect(StatusEffect effect)
|
public StatusEffect CopyEffect(StatusEffect effect)
|
||||||
{
|
{
|
||||||
var newEffect = new StatusEffect(this.owner, effect);
|
var newEffect = new StatusEffect(owner, effect);
|
||||||
newEffect.SetOwner(this.owner);
|
newEffect.SetOwner(owner);
|
||||||
// todo: should source be copied too?
|
// todo: should source be copied too?
|
||||||
return AddStatusEffect(newEffect, effect.GetSource()) ? newEffect : null;
|
return AddStatusEffect(newEffect, effect.GetSource()) ? newEffect : null;
|
||||||
}
|
}
|
||||||
@ -163,10 +302,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
public bool RemoveStatusEffectsByFlags(uint flags, bool silent = false)
|
public bool RemoveStatusEffectsByFlags(uint flags, bool silent = false)
|
||||||
{
|
{
|
||||||
// build list of effects to remove
|
// build list of effects to remove
|
||||||
var removeEffects = new List<StatusEffect>();
|
var removeEffects = GetStatusEffectsByFlag(flags);
|
||||||
foreach (var effect in effects.Values)
|
|
||||||
if ((effect.GetFlags() & flags) != 0)
|
|
||||||
removeEffects.Add(effect);
|
|
||||||
|
|
||||||
// remove effects from main list
|
// remove effects from main list
|
||||||
foreach (var effect in removeEffects)
|
foreach (var effect in removeEffects)
|
||||||
@ -190,12 +326,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
{
|
{
|
||||||
var list = new List<StatusEffect>();
|
var list = new List<StatusEffect>();
|
||||||
foreach (var effect in effects.Values)
|
foreach (var effect in effects.Values)
|
||||||
{
|
|
||||||
if ((effect.GetFlags() & flag) != 0)
|
if ((effect.GetFlags() & flag) != 0)
|
||||||
{
|
|
||||||
list.Add(effect);
|
list.Add(effect);
|
||||||
}
|
|
||||||
}
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,5 +360,88 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
Database.SavePlayerStatusEffects((Player)owner);
|
Database.SavePlayerStatusEffects((Player)owner);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void CallLuaFunctionByFlag(uint flag, string function, params object[] args)
|
||||||
|
{
|
||||||
|
var effects = GetStatusEffectsByFlag(flag);
|
||||||
|
|
||||||
|
object[] argsWithEffect = new object[args.Length + 1];
|
||||||
|
|
||||||
|
for (int i = 0; i < args.Length; i++)
|
||||||
|
argsWithEffect[i + 1] = args[i];
|
||||||
|
|
||||||
|
foreach (var effect in effects)
|
||||||
|
{
|
||||||
|
argsWithEffect[0] = effect;
|
||||||
|
effect.CallLuaFunction(owner, function, argsWithEffect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Sets the status id at an index.
|
||||||
|
//Changing a status to another doesn't seem to work. If updating an index that already has an effect, set it to 0 first then to the correct status
|
||||||
|
public void SetStatusAtIndex(int index, ushort statusId)
|
||||||
|
{
|
||||||
|
owner.charaWork.status[index] = statusId;
|
||||||
|
//owner.zone.BroadcastPacketAroundActor(owner, SetActorStatusPacket.BuildPacket(owner.actorId, (ushort)index, statusId));
|
||||||
|
|
||||||
|
statusSubpackets.Add(SetActorStatusPacket.BuildPacket(owner.actorId, (ushort)index, statusId));
|
||||||
|
owner.updateFlags |= ActorUpdateFlags.Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetTimeAtIndex(int index, uint time)
|
||||||
|
{
|
||||||
|
owner.charaWork.statusShownTime[index] = time;
|
||||||
|
statusTimerPropPacketUtil.AddProperty($"charaWork.statusShownTime[{index}]");
|
||||||
|
owner.updateFlags |= ActorUpdateFlags.StatusTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SubPacket> GetStatusPackets()
|
||||||
|
{
|
||||||
|
return statusSubpackets;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SubPacket> GetStatusTimerPackets()
|
||||||
|
{
|
||||||
|
return statusTimerPropPacketUtil.Done();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResetPropPacketUtil()
|
||||||
|
{
|
||||||
|
statusTimerPropPacketUtil = new ActorPropertyPacketUtil("charaWork/status", owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Overwrites effectToBeReplaced with a new status effect
|
||||||
|
//Returns the message of the new effect being added
|
||||||
|
//Doing this instead of simply calling remove then add so that the new effect is in the same slot as the old one
|
||||||
|
//There should be a better way to do this
|
||||||
|
//Currently causes the icons to blink whenb eing rpelaced
|
||||||
|
public BattleAction ReplaceEffect(StatusEffect effectToBeReplaced, uint newEffectId, byte tier, double magnitude, uint duration)
|
||||||
|
{
|
||||||
|
StatusEffect newEffect = Server.GetWorldManager().GetStatusEffect(newEffectId);
|
||||||
|
newEffect.SetTier(tier);
|
||||||
|
newEffect.SetMagnitude(magnitude);
|
||||||
|
newEffect.SetDuration(duration);
|
||||||
|
newEffect.SetOwner(effectToBeReplaced.GetOwner());
|
||||||
|
effectToBeReplaced.CallLuaFunction(owner, "onLose", owner, effectToBeReplaced);
|
||||||
|
newEffect.CallLuaFunction(owner, "onGain", owner, newEffect);
|
||||||
|
effects.Remove(effectToBeReplaced.GetStatusEffectId());
|
||||||
|
|
||||||
|
newEffect.SetStartTime(DateTime.Now);
|
||||||
|
newEffect.SetEndTime(DateTime.Now.AddSeconds(newEffect.GetDuration()));
|
||||||
|
uint time = (newEffect.GetFlags() & (uint)StatusEffectFlags.Stance) == 0 ? Utils.UnixTimeStampUTC(newEffect.GetEndTime()) : 0xFFFFFFFF;
|
||||||
|
int index = Array.IndexOf(owner.charaWork.status, effectToBeReplaced.GetStatusId());
|
||||||
|
|
||||||
|
//owner.charaWork.status[index] = newEffect.GetStatusId();
|
||||||
|
owner.charaWork.statusShownTime[index] = time;
|
||||||
|
effects[newEffectId] = newEffect;
|
||||||
|
|
||||||
|
SetStatusAtIndex(index, 0);
|
||||||
|
|
||||||
|
//charawork/status
|
||||||
|
SetStatusAtIndex(index, (ushort) (newEffectId - 200000));
|
||||||
|
SetTimeAtIndex(index, time);
|
||||||
|
|
||||||
|
return new BattleAction(owner.actorId, 30328, (uint) HitEffect.StatusEffectType | newEffectId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -53,7 +53,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Only move if owner isn't dead and is either too far away from their spawn point or is meant to roam
|
//Only move if owner isn't dead and is either too far away from their spawn point or is meant to roam
|
||||||
else if (!owner.IsDead() && (owner.isMovingToSpawn || owner.GetMobMod((uint)MobModifier.Roams) > 0))
|
if (!owner.IsDead() && (owner.isMovingToSpawn || owner.GetMobMod((uint)MobModifier.Roams) > 0))
|
||||||
{
|
{
|
||||||
DoRoamTick(tick);
|
DoRoamTick(tick);
|
||||||
}
|
}
|
||||||
@ -83,8 +83,8 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
|
|||||||
{
|
{
|
||||||
foreach (var chara in owner.zone.GetActorsAroundActor<Character>(owner, 50))
|
foreach (var chara in owner.zone.GetActorsAroundActor<Character>(owner, 50))
|
||||||
{
|
{
|
||||||
if (owner.allegiance == chara.allegiance)
|
if (chara.allegiance == owner.allegiance)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (owner.aiContainer.pathFind.AtPoint() && owner.detectionType != DetectionType.None)
|
if (owner.aiContainer.pathFind.AtPoint() && owner.detectionType != DetectionType.None)
|
||||||
{
|
{
|
||||||
@ -119,8 +119,6 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
|
|||||||
if (owner.GetState() != SetActorStatePacket.MAIN_STATE_ACTIVE)
|
if (owner.GetState() != SetActorStatePacket.MAIN_STATE_ACTIVE)
|
||||||
owner.ChangeState(SetActorStatePacket.MAIN_STATE_ACTIVE);
|
owner.ChangeState(SetActorStatePacket.MAIN_STATE_ACTIVE);
|
||||||
|
|
||||||
owner.moveState = 2;
|
|
||||||
//owner.SetMod((uint)Modifier.Speed, 5);
|
|
||||||
lastActionTime = DateTime.Now;
|
lastActionTime = DateTime.Now;
|
||||||
battleStartTime = lastActionTime;
|
battleStartTime = lastActionTime;
|
||||||
// todo: adjust cooldowns with modifiers
|
// todo: adjust cooldowns with modifiers
|
||||||
@ -138,7 +136,6 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
|
|||||||
{
|
{
|
||||||
var target = owner.target;
|
var target = owner.target;
|
||||||
base.Disengage();
|
base.Disengage();
|
||||||
owner.statusEffects.RemoveStatusEffectsByFlags((uint)StatusEffectFlags.LoseOnDeath, true);
|
|
||||||
// todo:
|
// todo:
|
||||||
lastActionTime = lastUpdate.AddSeconds(5);
|
lastActionTime = lastUpdate.AddSeconds(5);
|
||||||
owner.isMovingToSpawn = true;
|
owner.isMovingToSpawn = true;
|
||||||
@ -152,11 +149,15 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
|
|||||||
public override void Cast(Character target, uint spellId)
|
public override void Cast(Character target, uint spellId)
|
||||||
{
|
{
|
||||||
// todo:
|
// todo:
|
||||||
|
if(owner.aiContainer.CanChangeState())
|
||||||
|
owner.aiContainer.InternalCast(target, spellId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Ability(Character target, uint abilityId)
|
public override void Ability(Character target, uint abilityId)
|
||||||
{
|
{
|
||||||
// todo:
|
// todo:
|
||||||
|
if (owner.aiContainer.CanChangeState())
|
||||||
|
owner.aiContainer.InternalAbility(target, abilityId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void RangedAttack(Character target)
|
public override void RangedAttack(Character target)
|
||||||
@ -213,13 +214,13 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
|
|||||||
Disengage();
|
Disengage();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
owner.SetMod((uint)Modifier.Speed, 5);
|
||||||
|
if ((tick - lastCombatTickScript).TotalSeconds > 3)
|
||||||
Move();
|
|
||||||
|
|
||||||
if ((tick - lastCombatTickScript).TotalSeconds > 2)
|
|
||||||
{
|
{
|
||||||
lua.LuaEngine.CallLuaBattleFunction(owner, "onCombatTick", owner, owner.target, Utils.UnixTimeStampUTC(tick), contentGroupCharas);
|
Move();
|
||||||
|
//if (owner.aiContainer.CanChangeState())
|
||||||
|
//owner.aiContainer.WeaponSkill(owner.zone.FindActorInArea<Character>(owner.target.actorId), 27155);
|
||||||
|
//lua.LuaEngine.CallLuaBattleFunction(owner, "onCombatTick", owner, owner.target, Utils.UnixTimeStampUTC(tick), contentGroupCharas);
|
||||||
lastCombatTickScript = tick;
|
lastCombatTickScript = tick;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -239,7 +240,6 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
|
|||||||
|
|
||||||
var targetPos = new Vector3(owner.target.positionX, owner.target.positionY, owner.target.positionZ);
|
var targetPos = new Vector3(owner.target.positionX, owner.target.positionY, owner.target.positionZ);
|
||||||
var distance = Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, targetPos.X, targetPos.Y, targetPos.Z);
|
var distance = Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, targetPos.X, targetPos.Y, targetPos.Z);
|
||||||
|
|
||||||
if (distance > owner.GetAttackRange() - 0.2f || owner.aiContainer.CanFollowPath())
|
if (distance > owner.GetAttackRange() - 0.2f || owner.aiContainer.CanFollowPath())
|
||||||
{
|
{
|
||||||
if (CanMoveForward(distance))
|
if (CanMoveForward(distance))
|
||||||
@ -276,12 +276,13 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
|
|||||||
{
|
{
|
||||||
FaceTarget();
|
FaceTarget();
|
||||||
}
|
}
|
||||||
|
lastRoamUpdate = DateTime.Now;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void FaceTarget()
|
protected void FaceTarget()
|
||||||
{
|
{
|
||||||
// todo: check if stunned etc
|
// todo: check if stunned etc
|
||||||
if (owner.statusEffects.HasStatusEffectsByFlag(StatusEffectFlags.PreventAction))
|
if (owner.statusEffects.HasStatusEffectsByFlag(StatusEffectFlags.PreventTurn) )
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -392,14 +393,17 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
|
|||||||
|
|
||||||
public override void ChangeTarget(Character target)
|
public override void ChangeTarget(Character target)
|
||||||
{
|
{
|
||||||
owner.target = target;
|
if (target != owner.target)
|
||||||
owner.currentLockedTarget = target?.actorId ?? Actor.INVALID_ACTORID;
|
{
|
||||||
owner.currentTarget = target?.actorId ?? Actor.INVALID_ACTORID;
|
owner.target = target;
|
||||||
|
owner.currentLockedTarget = target?.actorId ?? Actor.INVALID_ACTORID;
|
||||||
|
owner.currentTarget = target?.actorId ?? Actor.INVALID_ACTORID;
|
||||||
|
|
||||||
foreach (var player in owner.zone.GetActorsAroundActor<Player>(owner, 50))
|
foreach (var player in owner.zone.GetActorsAroundActor<Player>(owner, 50))
|
||||||
player.QueuePacket(owner.GetHateTypePacket(player));
|
player.QueuePacket(owner.GetHateTypePacket(player));
|
||||||
|
|
||||||
base.ChangeTarget(target);
|
base.ChangeTarget(target);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
|
|||||||
|
|
||||||
public override void Update(DateTime tick)
|
public override void Update(DateTime tick)
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
if (owner.newMainState != owner.currentMainState)
|
if (owner.newMainState != owner.currentMainState)
|
||||||
{
|
{
|
||||||
if (owner.newMainState == SetActorStatePacket.MAIN_STATE_ACTIVE)
|
if (owner.newMainState == SetActorStatePacket.MAIN_STATE_ACTIVE)
|
||||||
@ -32,7 +33,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
|
|||||||
owner.Disengage();
|
owner.Disengage();
|
||||||
}
|
}
|
||||||
owner.currentMainState = (ushort)owner.newMainState;
|
owner.currentMainState = (ushort)owner.newMainState;
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void ChangeTarget(Character target)
|
public override void ChangeTarget(Character target)
|
||||||
|
@ -172,66 +172,78 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
// todo: this is stupid
|
// todo: this is stupid
|
||||||
bool withPet = (flags & ValidTarget.Ally) != 0 || masterTarget.allegiance != owner.allegiance;
|
bool withPet = (flags & ValidTarget.Ally) != 0 || masterTarget.allegiance != owner.allegiance;
|
||||||
|
|
||||||
if (masterTarget != null)
|
if (masterTarget != null && CanTarget(masterTarget))
|
||||||
targets.Add(masterTarget);
|
targets.Add(masterTarget);
|
||||||
|
|
||||||
if (aoeType != TargetFindAOEType.None)
|
if (aoeType != TargetFindAOEType.None)
|
||||||
{
|
{
|
||||||
if (IsPlayer(owner))
|
AddAllInRange(target, withPet);
|
||||||
{
|
|
||||||
if (masterTarget is Player)
|
|
||||||
{
|
|
||||||
findType = TargetFindCharacterType.PlayerToPlayer;
|
|
||||||
|
|
||||||
if (masterTarget.currentParty != null)
|
|
||||||
{
|
|
||||||
if ((validTarget & (ValidTarget.Ally | ValidTarget.PartyMember)) != 0)
|
|
||||||
AddAllInAlliance(masterTarget, withPet);
|
|
||||||
else
|
|
||||||
AddAllInParty(masterTarget, withPet);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AddTarget(masterTarget, withPet);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
findType = TargetFindCharacterType.PlayerToBattleNpc;
|
|
||||||
AddAllBattleNpcs(masterTarget, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// todo: this needs checking..
|
|
||||||
if (masterTarget is Player || owner.allegiance == CharacterTargetingAllegiance.Player)
|
|
||||||
findType = TargetFindCharacterType.BattleNpcToPlayer;
|
|
||||||
else
|
|
||||||
findType = TargetFindCharacterType.BattleNpcToBattleNpc;
|
|
||||||
|
|
||||||
// todo: configurable pet aoe buff
|
|
||||||
if (findType == TargetFindCharacterType.BattleNpcToBattleNpc && TryGetMasterTarget(target) != null)
|
|
||||||
withPet = true;
|
|
||||||
|
|
||||||
// todo: does ffxiv have call for help flag?
|
|
||||||
//if ((findFlags & ValidTarget.HitAll) != 0)
|
|
||||||
//{
|
|
||||||
// AddAllInZone(masterTarget, withPet);
|
|
||||||
//}
|
|
||||||
|
|
||||||
AddAllInAlliance(target, withPet);
|
|
||||||
|
|
||||||
if (findType == TargetFindCharacterType.BattleNpcToPlayer)
|
|
||||||
{
|
|
||||||
if (owner.allegiance == CharacterTargetingAllegiance.Player)
|
|
||||||
AddAllInZone(masterTarget, withPet);
|
|
||||||
else
|
|
||||||
AddAllInHateList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if(targets.Count > 16)
|
/*
|
||||||
targets.RemoveRange(16, targets.Count - 16);
|
if (aoeType != TargetFindAOEType.None)
|
||||||
|
{
|
||||||
|
if (IsPlayer(owner))
|
||||||
|
{
|
||||||
|
if (masterTarget is Player)
|
||||||
|
{
|
||||||
|
findType = TargetFindCharacterType.PlayerToPlayer;
|
||||||
|
|
||||||
|
if (masterTarget.currentParty != null)
|
||||||
|
{
|
||||||
|
if ((validTarget & (ValidTarget.Ally | ValidTarget.PartyMember)) != 0)
|
||||||
|
AddAllInAlliance(masterTarget, withPet);
|
||||||
|
else
|
||||||
|
AddAllInParty(masterTarget, withPet);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddTarget(masterTarget, withPet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
findType = TargetFindCharacterType.PlayerToBattleNpc;
|
||||||
|
AddAllBattleNpcs(masterTarget, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// todo: this needs checking..
|
||||||
|
if (masterTarget is Player || owner.allegiance == CharacterTargetingAllegiance.Player)
|
||||||
|
findType = TargetFindCharacterType.BattleNpcToPlayer;
|
||||||
|
else
|
||||||
|
findType = TargetFindCharacterType.BattleNpcToBattleNpc;
|
||||||
|
|
||||||
|
// todo: configurable pet aoe buff
|
||||||
|
if (findType == TargetFindCharacterType.BattleNpcToBattleNpc && TryGetMasterTarget(target) != null)
|
||||||
|
withPet = true;
|
||||||
|
|
||||||
|
// todo: does ffxiv have call for help flag?
|
||||||
|
//if ((findFlags & ValidTarget.HitAll) != 0)
|
||||||
|
//{
|
||||||
|
// AddAllInZone(masterTarget, withPet);
|
||||||
|
//}
|
||||||
|
|
||||||
|
AddAllInAlliance(target, withPet);
|
||||||
|
|
||||||
|
if (findType == TargetFindCharacterType.BattleNpcToPlayer)
|
||||||
|
{
|
||||||
|
if (owner.allegiance == CharacterTargetingAllegiance.Player)
|
||||||
|
AddAllInZone(masterTarget, withPet);
|
||||||
|
else
|
||||||
|
AddAllInHateList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
|
||||||
|
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>
|
/// <summary>
|
||||||
@ -311,6 +323,18 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AddAllInRange(Character target, bool withPet)
|
||||||
|
{
|
||||||
|
int dist = (int)maxDistance;
|
||||||
|
var actors = owner.zone.GetActorsAroundActor<Character>(target, dist);
|
||||||
|
|
||||||
|
foreach (Character actor in actors)
|
||||||
|
{
|
||||||
|
AddTarget(actor, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private void AddAllInHateList()
|
private void AddAllInHateList()
|
||||||
{
|
{
|
||||||
if (!(owner is BattleNpc))
|
if (!(owner is BattleNpc))
|
||||||
@ -332,6 +356,12 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
if (target == null || !retarget && targets.Contains(target))
|
if (target == null || !retarget && targets.Contains(target))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if ((validTarget & ValidTarget.Player) != 0 && target is Player)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if ((validTarget & ValidTarget.PartyMember) != 0 && target.currentParty == owner.currentParty)
|
||||||
|
return true;
|
||||||
|
|
||||||
// cant target dead
|
// cant target dead
|
||||||
if ((validTarget & ValidTarget.Corpse) == 0 && target.IsDead())
|
if ((validTarget & ValidTarget.Corpse) == 0 && target.IsDead())
|
||||||
return false;
|
return false;
|
||||||
@ -342,6 +372,12 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
if ((validTarget & ValidTarget.Enemy) != 0 && target.allegiance == owner.allegiance)
|
if ((validTarget & ValidTarget.Enemy) != 0 && target.allegiance == owner.allegiance)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (((validTarget & ValidTarget.PartyMember) == 0) && ((validTarget & ValidTarget.Self) == 0) && target.currentParty == owner.currentParty)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ((validTarget & ValidTarget.PartyMember) != 0 && target.currentParty != owner.currentParty)
|
||||||
|
return false;
|
||||||
|
|
||||||
if ((validTarget & ValidTarget.NPC) != 0 && target.isStatic)
|
if ((validTarget & ValidTarget.NPC) != 0 && target.isStatic)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -359,6 +395,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||||||
if (validTarget == ValidTarget.Self && aoeType == TargetFindAOEType.None && owner != target)
|
if (validTarget == ValidTarget.Self && aoeType == TargetFindAOEType.None && owner != target)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if ((validTarget & ValidTarget.Self) == 0 && target == owner)
|
||||||
|
return false;
|
||||||
|
|
||||||
// this is fuckin retarded, think of a better way l8r
|
// this is fuckin retarded, think of a better way l8r
|
||||||
if (!ignoreAOE)
|
if (!ignoreAOE)
|
||||||
{
|
{
|
||||||
|
136
FFXIVClassic Map Server/actors/chara/ai/state/AbilityState.cs
Normal file
136
FFXIVClassic Map Server/actors/chara/ai/state/AbilityState.cs
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using FFXIVClassic.Common;
|
||||||
|
using FFXIVClassic_Map_Server.Actors;
|
||||||
|
using FFXIVClassic_Map_Server.packets.send.actor;
|
||||||
|
using FFXIVClassic_Map_Server.packets.send.actor.battle;
|
||||||
|
using FFXIVClassic_Map_Server.packets.send;
|
||||||
|
|
||||||
|
namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
||||||
|
{
|
||||||
|
class AbilityState : State
|
||||||
|
{
|
||||||
|
|
||||||
|
private BattleCommand skill;
|
||||||
|
|
||||||
|
public AbilityState(Character owner, Character target, ushort skillId) :
|
||||||
|
base(owner, target)
|
||||||
|
{
|
||||||
|
this.startTime = DateTime.Now;
|
||||||
|
this.skill = Server.GetWorldManager().GetBattleCommand(skillId);
|
||||||
|
var returnCode = lua.LuaEngine.CallLuaBattleCommandFunction(owner, skill, "ability", "onAbilityPrepare", owner, target, skill);
|
||||||
|
|
||||||
|
this.target = skill.GetMainTarget(owner, target);
|
||||||
|
|
||||||
|
if (returnCode == 0)
|
||||||
|
{
|
||||||
|
OnStart();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
errorResult = null;
|
||||||
|
interrupt = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnStart()
|
||||||
|
{
|
||||||
|
var returnCode = lua.LuaEngine.CallLuaBattleCommandFunction(owner, skill, "ability", "onAbilityStart", owner, target, skill);
|
||||||
|
|
||||||
|
if (returnCode != 0)
|
||||||
|
{
|
||||||
|
interrupt = true;
|
||||||
|
errorResult = new BattleAction(owner.actorId, (ushort)(returnCode == -1 ? 32558 : returnCode), 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//owner.LookAt(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Update(DateTime tick)
|
||||||
|
{
|
||||||
|
if (skill != null)
|
||||||
|
{
|
||||||
|
TryInterrupt();
|
||||||
|
|
||||||
|
if (interrupt)
|
||||||
|
{
|
||||||
|
OnInterrupt();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: check weapon delay/haste etc and use that
|
||||||
|
var actualCastTime = skill.castTimeMs;
|
||||||
|
|
||||||
|
if ((tick - startTime).Milliseconds >= skill.castTimeMs)
|
||||||
|
{
|
||||||
|
OnComplete();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnInterrupt()
|
||||||
|
{
|
||||||
|
// todo: send paralyzed/sleep message etc.
|
||||||
|
if (errorResult != null)
|
||||||
|
{
|
||||||
|
owner.DoBattleAction(skill.id, errorResult.animation, errorResult);
|
||||||
|
errorResult = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnComplete()
|
||||||
|
{
|
||||||
|
bool hitTarget = false;
|
||||||
|
|
||||||
|
skill.targetFind.FindWithinArea(target, skill.validTarget, skill.aoeTarget);
|
||||||
|
isCompleted = true;
|
||||||
|
|
||||||
|
owner.DoBattleCommand(skill, "ability");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void TryInterrupt()
|
||||||
|
{
|
||||||
|
if (interrupt)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (owner.statusEffects.HasStatusEffectsByFlag((uint)StatusEffectFlags.PreventAbility))
|
||||||
|
{
|
||||||
|
// todo: sometimes paralyze can let you attack, get random percentage of actually letting you attack
|
||||||
|
var list = owner.statusEffects.GetStatusEffectsByFlag((uint)StatusEffectFlags.PreventAbility);
|
||||||
|
uint effectId = 0;
|
||||||
|
if (list.Count > 0)
|
||||||
|
{
|
||||||
|
// todo: actually check proc rate/random chance of whatever effect
|
||||||
|
effectId = list[0].GetStatusEffectId();
|
||||||
|
}
|
||||||
|
interrupt = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
interrupt = !CanUse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CanUse()
|
||||||
|
{
|
||||||
|
return skill.IsValidMainTarget(owner, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BattleCommand GetWeaponSkill()
|
||||||
|
{
|
||||||
|
return skill;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Cleanup()
|
||||||
|
{
|
||||||
|
owner.aiContainer.UpdateLastActionTime(skill.animationDurationSeconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -32,9 +32,8 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||||||
|
|
||||||
public override bool Update(DateTime tick)
|
public override bool Update(DateTime tick)
|
||||||
{
|
{
|
||||||
|
|
||||||
if ((target == null || owner.target != target || owner.target?.actorId != owner.currentLockedTarget) && owner.isAutoAttackEnabled)
|
if ((target == null || owner.target != target || owner.target?.actorId != owner.currentLockedTarget) && owner.isAutoAttackEnabled)
|
||||||
owner.aiContainer.ChangeTarget(target = owner.zone.FindActorInArea<Character>(owner.currentLockedTarget));
|
owner.aiContainer.ChangeTarget(target = owner.zone.FindActorInArea<Character>(owner.currentTarget));
|
||||||
|
|
||||||
if (target == null || target.IsDead())
|
if (target == null || target.IsDead())
|
||||||
{
|
{
|
||||||
@ -82,7 +81,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||||||
|
|
||||||
public override void OnComplete()
|
public override void OnComplete()
|
||||||
{
|
{
|
||||||
BattleAction action = new BattleAction(target.actorId, 0x765D, (uint) HitEffect.Hit, 0, (byte) HitDirection.None);
|
//BattleAction action = new BattleAction(target.actorId, 0x765D, (uint) HitEffect.Hit, 0, (byte) HitDirection.None);
|
||||||
errorResult = null;
|
errorResult = null;
|
||||||
|
|
||||||
// todo: implement auto attack damage bonus in Character.OnAttack
|
// todo: implement auto attack damage bonus in Character.OnAttack
|
||||||
@ -99,16 +98,40 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||||||
* The above damage bonus also applies to “Shot” attacks by archers.
|
* The above damage bonus also applies to “Shot” attacks by archers.
|
||||||
*/
|
*/
|
||||||
// handle paralyze/intimidate/sleep/whatever in Character.OnAttack
|
// handle paralyze/intimidate/sleep/whatever in Character.OnAttack
|
||||||
owner.OnAttack(this, action, ref errorResult);
|
|
||||||
owner.DoBattleAction((ushort)BattleActionX01PacketCommand.Attack, action.animation, errorResult == null ? action : errorResult);
|
|
||||||
|
// todo: Change this to use a BattleCommand like the other states
|
||||||
|
|
||||||
|
//List<BattleAction> actions = new List<BattleAction>();
|
||||||
|
BattleActionContainer actions = new BattleActionContainer();
|
||||||
|
target.SetMod((uint) Modifier.MinimumHpLock, 0);
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
for (int hitNum = 0; hitNum < 1 /* owner.GetMod((uint) Modifier.HitCount)*/; hitNum++)
|
||||||
|
{
|
||||||
|
BattleAction action = new BattleAction(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
|
||||||
|
BattleAction[] errors = (BattleAction[])actions.GetList().ToArray().Clone();
|
||||||
|
BattleAction error = null;// new BattleAction(0, null, 0, 0);
|
||||||
|
//owner.DoActions(null, actions.GetList(), ref error);
|
||||||
|
//owner.OnAttack(this, actions[0], ref errorResult);
|
||||||
|
owner.DoBattleAction(22104, 0x19001000, actions.GetList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void TryInterrupt()
|
public override void TryInterrupt()
|
||||||
{
|
{
|
||||||
if (owner.statusEffects.HasStatusEffectsByFlag((uint)StatusEffectFlags.PreventAction))
|
if (owner.statusEffects.HasStatusEffectsByFlag((uint)StatusEffectFlags.PreventAttack))
|
||||||
{
|
{
|
||||||
// todo: sometimes paralyze can let you attack, calculate proc rate
|
// todo: sometimes paralyze can let you attack, calculate proc rate
|
||||||
var list = owner.statusEffects.GetStatusEffectsByFlag((uint)StatusEffectFlags.PreventAction);
|
var list = owner.statusEffects.GetStatusEffectsByFlag((uint)StatusEffectFlags.PreventAttack);
|
||||||
uint statusId = 0;
|
uint statusId = 0;
|
||||||
if (list.Count > 0)
|
if (list.Count > 0)
|
||||||
{
|
{
|
||||||
@ -124,12 +147,12 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||||||
private bool IsAttackReady()
|
private bool IsAttackReady()
|
||||||
{
|
{
|
||||||
// todo: this enforced delay should really be changed if it's not retail..
|
// todo: this enforced delay should really be changed if it's not retail..
|
||||||
return Program.Tick >= attackTime && Program.Tick >= owner.aiContainer.GetLastActionTime().AddSeconds(1);
|
return Program.Tick >= attackTime && Program.Tick >= owner.aiContainer.GetLastActionTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CanAttack()
|
private bool CanAttack()
|
||||||
{
|
{
|
||||||
if (!owner.isAutoAttackEnabled)
|
if (!owner.isAutoAttackEnabled || target.allegiance == owner.allegiance)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,10 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||||||
: base(owner, null)
|
: base(owner, null)
|
||||||
{
|
{
|
||||||
owner.Disengage();
|
owner.Disengage();
|
||||||
//owner.ChangeState(SetActorStatePacket.MAIN_STATE_DEAD);
|
owner.ChangeState(SetActorStatePacket.MAIN_STATE_DEAD);
|
||||||
var deathStatePacket = SetActorStatePacket.BuildPacket(owner.actorId, SetActorStatePacket.MAIN_STATE_DEAD, owner.currentSubState);
|
owner.statusEffects.RemoveStatusEffectsByFlags((uint)StatusEffectFlags.LoseOnDeath, true);
|
||||||
owner.zone.BroadcastPacketAroundActor(owner, deathStatePacket);
|
//var deathStatePacket = SetActorStatePacket.BuildPacket(owner.actorId, SetActorStatePacket.MAIN_STATE_DEAD2, owner.currentSubState);
|
||||||
|
//owner.zone.BroadcastPacketAroundActor(owner, deathStatePacket);
|
||||||
canInterrupt = false;
|
canInterrupt = false;
|
||||||
startTime = tick;
|
startTime = tick;
|
||||||
despawnTime = startTime.AddSeconds(timeToFadeOut);
|
despawnTime = startTime.AddSeconds(timeToFadeOut);
|
||||||
|
@ -27,7 +27,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||||||
if (owner.IsDead())
|
if (owner.IsDead())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (!owner.statusEffects.HasStatusEffectsByFlag(StatusEffectFlags.PreventAction))
|
if (!owner.statusEffects.HasStatusEffectsByFlag(StatusEffectFlags.PreventMovement))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||||||
this.spell = Server.GetWorldManager().GetBattleCommand(spellId);
|
this.spell = Server.GetWorldManager().GetBattleCommand(spellId);
|
||||||
var returnCode = lua.LuaEngine.CallLuaBattleCommandFunction(owner, spell, "magic", "onMagicPrepare", owner, target, spell);
|
var returnCode = lua.LuaEngine.CallLuaBattleCommandFunction(owner, spell, "magic", "onMagicPrepare", owner, target, spell);
|
||||||
|
|
||||||
if (returnCode == 0 && owner.CanCast(target, spell))
|
this.target = spell.GetMainTarget(owner, target);
|
||||||
|
|
||||||
|
if (returnCode == 0 && owner.CanCast(this.target, spell))
|
||||||
{
|
{
|
||||||
OnStart();
|
OnStart();
|
||||||
}
|
}
|
||||||
@ -51,16 +53,36 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||||||
// todo: check within attack range
|
// todo: check within attack range
|
||||||
float[] baseCastDuration = { 1.0f, 0.25f };
|
float[] baseCastDuration = { 1.0f, 0.25f };
|
||||||
|
|
||||||
float spellSpeed = spell.castTimeSeconds;
|
//Check combo stuff here because combos can impact spell cast times
|
||||||
|
|
||||||
// command casting duration
|
float spellSpeed = spell.castTimeMs;
|
||||||
if (owner is Player)
|
|
||||||
|
//There are no positional spells, so just check onCombo, need to check first because certain spells change aoe type/accuracy
|
||||||
|
//If owner is a player and the spell being used is part of the current combo
|
||||||
|
if (spell.comboStep == 1 || ((owner is Player p) && (p.playerWork.comboNextCommandId[0] == spell.id || p.playerWork.comboNextCommandId[1] == spell.id)))
|
||||||
{
|
{
|
||||||
// todo: modify spellSpeed based on modifiers and stuff
|
lua.LuaEngine.CallLuaBattleCommandFunction(owner, spell, "magic", "onCombo", owner, target, spell);
|
||||||
((Player)owner).SendStartCastbar(spell.id, Utils.UnixTimeStampUTC(DateTime.Now.AddSeconds(spellSpeed)));
|
spell.isCombo = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//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));
|
||||||
|
|
||||||
|
//modify skill based on status effects
|
||||||
|
foreach (var effect in effects)
|
||||||
|
lua.LuaEngine.CallLuaStatusEffectFunction(owner, effect, "onMagicCast", owner, effect, spell);
|
||||||
|
|
||||||
|
if (!spell.IsInstantCast())
|
||||||
|
{
|
||||||
|
// command casting duration
|
||||||
|
if (owner is Player)
|
||||||
|
{
|
||||||
|
// todo: modify spellSpeed based on modifiers and stuff
|
||||||
|
((Player)owner).SendStartCastbar(spell.id, Utils.UnixTimeStampUTC(DateTime.Now.AddMilliseconds(spellSpeed)));
|
||||||
|
}
|
||||||
|
owner.SendChant(0xf, 0x0);
|
||||||
|
owner.DoBattleAction(spell.id, (uint) 0x6F000000 | spell.castType, new BattleAction(target.actorId, 30128, 1, 0, 1)); //You begin casting (6F000002: BLM, 6F000003: WHM, 0x6F000008: BRD)
|
||||||
}
|
}
|
||||||
owner.SendChant(0xF, 0x0);
|
|
||||||
owner.DoBattleAction(spell.id, 0x6F000002, new BattleAction(target.actorId, 30128, 1, 0, 1)); //You begin casting (6F000002: BLM, 6F000003: WHM)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,9 +99,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||||||
}
|
}
|
||||||
|
|
||||||
// todo: check weapon delay/haste etc and use that
|
// todo: check weapon delay/haste etc and use that
|
||||||
var actualCastTime = spell.castTimeSeconds;
|
var actualCastTime = spell.castTimeMs;
|
||||||
|
|
||||||
if ((tick - startTime).TotalSeconds >= spell.castTimeSeconds)
|
if ((tick - startTime).TotalMilliseconds >= spell.castTimeMs)
|
||||||
{
|
{
|
||||||
OnComplete();
|
OnComplete();
|
||||||
return true;
|
return true;
|
||||||
@ -102,24 +124,15 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||||||
|
|
||||||
public override void OnComplete()
|
public override void OnComplete()
|
||||||
{
|
{
|
||||||
|
//How do combos/hitdirs work for aoe abilities or does that not matter for aoe?
|
||||||
|
HitDirection hitDir = owner.GetHitDirection(target);
|
||||||
|
bool hitTarget = false;
|
||||||
|
|
||||||
spell.targetFind.FindWithinArea(target, spell.validTarget, spell.aoeTarget);
|
spell.targetFind.FindWithinArea(target, spell.validTarget, spell.aoeTarget);
|
||||||
isCompleted = true;
|
isCompleted = true;
|
||||||
|
|
||||||
var targets = spell.targetFind.GetTargets();
|
var targets = spell.targetFind.GetTargets();
|
||||||
BattleAction[] actions = new BattleAction[targets.Count];
|
|
||||||
var i = 0;
|
|
||||||
foreach (var chara in targets)
|
|
||||||
{
|
|
||||||
var action = new BattleAction(chara.actorId, spell.worldMasterTextId, spell.battleAnimation, 0, (byte)HitDirection.None, 1);
|
|
||||||
action.amount = (ushort)lua.LuaEngine.CallLuaBattleCommandFunction(owner, spell, "magic", "onMagicFinish", owner, chara, spell, action);
|
|
||||||
actions[i++] = action;
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: this is fuckin stupid, probably only need *one* error packet, not an error for each action
|
|
||||||
var errors = (BattleAction[])actions.Clone();
|
|
||||||
owner.OnCast(this, actions, ref errors);
|
|
||||||
owner.DoBattleAction(spell.id, spell.battleAnimation, actions);
|
|
||||||
|
|
||||||
|
owner.DoBattleCommand(spell, "magic");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void TryInterrupt()
|
public override void TryInterrupt()
|
||||||
@ -127,10 +140,10 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||||||
if (interrupt)
|
if (interrupt)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (owner.statusEffects.HasStatusEffectsByFlag((uint)StatusEffectFlags.PreventAction))
|
if (owner.statusEffects.HasStatusEffectsByFlag((uint)StatusEffectFlags.PreventSpell))
|
||||||
{
|
{
|
||||||
// todo: sometimes paralyze can let you attack, get random percentage of actually letting you attack
|
// todo: sometimes paralyze can let you attack, get random percentage of actually letting you attack
|
||||||
var list = owner.statusEffects.GetStatusEffectsByFlag((uint)StatusEffectFlags.PreventAction);
|
var list = owner.statusEffects.GetStatusEffectsByFlag((uint)StatusEffectFlags.PreventSpell);
|
||||||
uint effectId = 0;
|
uint effectId = 0;
|
||||||
if (list.Count > 0)
|
if (list.Count > 0)
|
||||||
{
|
{
|
||||||
@ -154,7 +167,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||||||
|
|
||||||
private bool CanCast()
|
private bool CanCast()
|
||||||
{
|
{
|
||||||
return owner.CanCast(target, spell) && spell.IsValidTarget(owner, target) && !HasMoved();
|
return owner.CanCast(target, spell) && spell.IsValidMainTarget(owner, target) && !HasMoved();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool HasMoved()
|
private bool HasMoved()
|
||||||
@ -170,7 +183,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||||||
{
|
{
|
||||||
((Player)owner).SendEndCastbar();
|
((Player)owner).SendEndCastbar();
|
||||||
}
|
}
|
||||||
owner.aiContainer.UpdateLastActionTime();
|
owner.aiContainer.UpdateLastActionTime(spell.animationDurationSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BattleCommand GetSpell()
|
public BattleCommand GetSpell()
|
||||||
|
@ -15,11 +15,12 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||||||
{
|
{
|
||||||
|
|
||||||
private BattleCommand skill;
|
private BattleCommand skill;
|
||||||
|
private HitDirection hitDirection;
|
||||||
public WeaponSkillState(Character owner, Character target, ushort skillId) :
|
public WeaponSkillState(Character owner, Character target, ushort skillId) :
|
||||||
base(owner, target)
|
base(owner, target)
|
||||||
{
|
{
|
||||||
this.startTime = DateTime.Now;
|
this.startTime = DateTime.Now;
|
||||||
|
//this.target = skill.targetFind.
|
||||||
this.skill = Server.GetWorldManager().GetBattleCommand(skillId);
|
this.skill = Server.GetWorldManager().GetBattleCommand(skillId);
|
||||||
var returnCode = lua.LuaEngine.CallLuaBattleCommandFunction(owner, skill, "weaponskill", "onSkillPrepare", owner, target, skill);
|
var returnCode = lua.LuaEngine.CallLuaBattleCommandFunction(owner, skill, "weaponskill", "onSkillPrepare", owner, target, skill);
|
||||||
|
|
||||||
@ -46,6 +47,36 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
owner.LookAt(target);
|
owner.LookAt(target);
|
||||||
|
hitDirection = owner.GetHitDirection(target);
|
||||||
|
|
||||||
|
//Do positionals and combo effects first because these can influence accuracy and amount of targets/numhits, which influence the rest of the steps
|
||||||
|
//If there is no positon required or if the position bonus should be activated
|
||||||
|
if ((skill.positionBonus & utils.BattleUtils.ConvertHitDirToPosition(hitDirection)) == skill.positionBonus)
|
||||||
|
{
|
||||||
|
//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);
|
||||||
|
|
||||||
|
//Combo stuff
|
||||||
|
if (owner is Player p)
|
||||||
|
{
|
||||||
|
//If skill is part of owner's class/job, it can be used in a combo
|
||||||
|
if (skill.job == p.GetClass() || skill.job == p.GetCurrentClassOrJob())
|
||||||
|
{
|
||||||
|
//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;
|
||||||
|
}
|
||||||
|
//or if this just the start of a combo
|
||||||
|
else if (skill.comboStep == 1)
|
||||||
|
skill.isCombo = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,9 +93,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||||||
}
|
}
|
||||||
|
|
||||||
// todo: check weapon delay/haste etc and use that
|
// todo: check weapon delay/haste etc and use that
|
||||||
var actualCastTime = skill.castTimeSeconds;
|
var actualCastTime = skill.castTimeMs;
|
||||||
|
|
||||||
if ((tick - startTime).TotalSeconds >= skill.castTimeSeconds)
|
if ((tick - startTime).Milliseconds >= skill.castTimeMs)
|
||||||
{
|
{
|
||||||
OnComplete();
|
OnComplete();
|
||||||
return true;
|
return true;
|
||||||
@ -88,26 +119,11 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||||||
{
|
{
|
||||||
skill.targetFind.FindWithinArea(target, skill.validTarget, skill.aoeTarget);
|
skill.targetFind.FindWithinArea(target, skill.validTarget, skill.aoeTarget);
|
||||||
isCompleted = true;
|
isCompleted = true;
|
||||||
var targets = skill.targetFind.GetTargets();
|
|
||||||
|
|
||||||
BattleAction[] actions = new BattleAction[targets.Count];
|
owner.DoBattleCommand(skill, "weaponskill");
|
||||||
|
owner.statusEffects.RemoveStatusEffectsByFlags((uint) StatusEffectFlags.LoseOnAttacking);
|
||||||
var i = 0;
|
|
||||||
foreach (var chara in targets)
|
|
||||||
{
|
|
||||||
var action = new BattleAction(chara.actorId, skill.worldMasterTextId, (uint)HitEffect.Hit, 0, 1, 1);
|
|
||||||
// evasion, miss, dodge, etc to be handled in script, calling helpers from scripts/weaponskills.lua
|
|
||||||
action.amount = (ushort)lua.LuaEngine.CallLuaBattleCommandFunction(owner, skill, "weaponskill", "onSkillFinish", owner, target, skill, action);
|
|
||||||
actions[i++] = action;
|
|
||||||
chara.Engage(chara.actorId, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: this is fuckin stupid, probably only need *one* error packet, not an error for each action
|
|
||||||
var errors = (BattleAction[])actions.Clone();
|
|
||||||
owner.OnWeaponSkill(this, actions, ref errors);
|
|
||||||
owner.DoBattleAction(skill.id, skill.battleAnimation, actions);
|
|
||||||
|
|
||||||
|
|
||||||
|
lua.LuaEngine.GetInstance().OnSignal("weaponskillUsed");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void TryInterrupt()
|
public override void TryInterrupt()
|
||||||
@ -115,10 +131,10 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||||||
if (interrupt)
|
if (interrupt)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (owner.statusEffects.HasStatusEffectsByFlag((uint)StatusEffectFlags.PreventAction))
|
if (owner.statusEffects.HasStatusEffectsByFlag((uint)StatusEffectFlags.PreventWeaponSkill))
|
||||||
{
|
{
|
||||||
// todo: sometimes paralyze can let you attack, get random percentage of actually letting you attack
|
// todo: sometimes paralyze can let you attack, get random percentage of actually letting you attack
|
||||||
var list = owner.statusEffects.GetStatusEffectsByFlag((uint)StatusEffectFlags.PreventAction);
|
var list = owner.statusEffects.GetStatusEffectsByFlag((uint)StatusEffectFlags.PreventWeaponSkill);
|
||||||
uint effectId = 0;
|
uint effectId = 0;
|
||||||
if (list.Count > 0)
|
if (list.Count > 0)
|
||||||
{
|
{
|
||||||
@ -134,7 +150,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||||||
|
|
||||||
private bool CanUse()
|
private bool CanUse()
|
||||||
{
|
{
|
||||||
return owner.CanWeaponSkill(target, skill) && skill.IsValidTarget(owner, target);
|
return owner.CanWeaponSkill(target, skill) && skill.IsValidMainTarget(owner, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BattleCommand GetWeaponSkill()
|
public BattleCommand GetWeaponSkill()
|
||||||
@ -144,7 +160,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||||||
|
|
||||||
public override void Cleanup()
|
public override void Cleanup()
|
||||||
{
|
{
|
||||||
owner.aiContainer.UpdateLastActionTime();
|
owner.aiContainer.UpdateLastActionTime(skill.animationDurationSeconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,79 +7,192 @@ using System.Threading.Tasks;
|
|||||||
using FFXIVClassic_Map_Server.Actors;
|
using FFXIVClassic_Map_Server.Actors;
|
||||||
using FFXIVClassic_Map_Server.packets.send.actor;
|
using FFXIVClassic_Map_Server.packets.send.actor;
|
||||||
using FFXIVClassic_Map_Server.packets.send.actor.battle;
|
using FFXIVClassic_Map_Server.packets.send.actor.battle;
|
||||||
|
using FFXIVClassic_Map_Server.actors.chara.player;
|
||||||
|
using FFXIVClassic_Map_Server.actors.chara.npc;
|
||||||
|
using FFXIVClassic_Map_Server.dataobjects;
|
||||||
using FFXIVClassic.Common;
|
using FFXIVClassic.Common;
|
||||||
|
|
||||||
namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
|
namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
|
||||||
{
|
{
|
||||||
static class BattleUtils
|
static class BattleUtils
|
||||||
{
|
{
|
||||||
|
|
||||||
|
public static Dictionary<HitType, ushort> SingleHitTypeTextIds = 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> 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>()
|
||||||
|
{
|
||||||
|
{ 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 }
|
||||||
|
};
|
||||||
|
|
||||||
|
//Most of these numbers I'm fairly certain are correct. The repeated numbers at levels 23 and 48 I'm less sure about but they do match some weird spots in the EXP graph
|
||||||
|
|
||||||
|
public static ushort[] BASEEXP = {150, 150, 150, 150, 150, 150, 150, 150, 150, 150, //Level <= 10
|
||||||
|
150, 150, 150, 150, 150, 150, 150, 150, 160, 170, //Level <= 20
|
||||||
|
180, 190, 190, 200, 210, 220, 230, 240, 250, 260, //Level <= 30
|
||||||
|
270, 280, 290, 300, 310, 320, 330, 340, 350, 360, //Level <= 40
|
||||||
|
370, 380, 380, 390, 400, 410, 420, 430, 430, 440}; //Level <= 50
|
||||||
|
|
||||||
public static bool TryAttack(Character attacker, Character defender, BattleAction action, ref BattleAction error)
|
public static bool TryAttack(Character attacker, Character defender, BattleAction action, ref BattleAction error)
|
||||||
{
|
{
|
||||||
// todo: get hit rate, hit count, set hit effect
|
// todo: get hit rate, hit count, set hit effect
|
||||||
action.effectId |= (uint)(HitEffect.RecoilLv2 | HitEffect.Hit | HitEffect.HitVisual1);
|
//action.effectId |= (uint)(HitEffect.RecoilLv2 | HitEffect.Hit | HitEffect.HitVisual1);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ushort CalculateAttackDamage(Character attacker, Character defender, BattleAction action)
|
private static double CalculateDlvlModifier(short dlvl)
|
||||||
{
|
{
|
||||||
ushort damage = (ushort)(Program.Random.Next(10) * 10);
|
//this is just a really, really simplified version of the graph from http://kanican.livejournal.com/55915.html
|
||||||
|
//actual formula is definitely more complicated
|
||||||
|
//I'm going to assum these formulas are linear, and they're clamped so the modifier never goes below 0.
|
||||||
|
double modifier = 0;
|
||||||
|
|
||||||
// todo: handle all other crap before protect/stoneskin
|
|
||||||
|
|
||||||
// todo: handle crit etc
|
if (dlvl >= 0)
|
||||||
if (defender.statusEffects.HasStatusEffect(StatusEffectId.Protect) || defender.statusEffects.HasStatusEffect(StatusEffectId.Protect2))
|
modifier = (.35 * dlvl) + .225;
|
||||||
{
|
else
|
||||||
if (action != null)
|
modifier = (.01 * dlvl) + .25;
|
||||||
action.effectId |= (uint)HitEffect.Protect;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (defender.statusEffects.HasStatusEffect(StatusEffectId.Stoneskin))
|
return modifier.Clamp(0, 1);
|
||||||
{
|
|
||||||
if (action != null)
|
|
||||||
action.effectId |= (uint)HitEffect.Stoneskin;
|
|
||||||
}
|
|
||||||
return damage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ushort GetCriticalHitDamage(Character attacker, Character defender, BattleAction action)
|
//Damage calculations
|
||||||
|
//Calculate damage of action
|
||||||
|
//We could probably just do this when determining the action's hit type
|
||||||
|
public static void CalculatePhysicalDamageTaken(Character attacker, Character defender, BattleCommand skill, BattleAction action)
|
||||||
{
|
{
|
||||||
ushort damage = action.amount;
|
short dlvl = (short)(defender.GetLevel() - attacker.GetLevel());
|
||||||
|
|
||||||
// todo:
|
// todo: physical resistances
|
||||||
//
|
|
||||||
// action.effectId |= (uint)HitEffect.Critical;
|
//dlvl, Defense, and Vitality all effect how much damage is taken after hittype takes effect
|
||||||
//
|
//player attacks cannot do more than 9999 damage.
|
||||||
return damage;
|
//VIT is turned into Defense at a 3:2 ratio in calculatestats, so don't need to do that here
|
||||||
|
double damageTakenPercent = 1 - (defender.GetMod(Modifier.DamageTakenDown) / 100.0);
|
||||||
|
action.amount = (ushort)(action.amount - CalculateDlvlModifier(dlvl) * (defender.GetMod((uint)Modifier.Defense))).Clamp(0, 9999);
|
||||||
|
action.amount = (ushort)(action.amount * damageTakenPercent).Clamp(0, 9999);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ushort CalculateSpellDamage(Character attacker, Character defender, BattleAction action)
|
|
||||||
|
public static void CalculateSpellDamageTaken(Character attacker, Character defender, BattleCommand skill, BattleAction action)
|
||||||
{
|
{
|
||||||
ushort damage = 0;
|
short dlvl = (short)(defender.GetLevel() - attacker.GetLevel());
|
||||||
|
|
||||||
// todo: handle all other crap before shell/stoneskin
|
// todo: elemental resistances
|
||||||
|
//Patch 1.19:
|
||||||
|
//Magic Defense has been abolished and no longer appears in equipment attributes.
|
||||||
|
//The effect of elemental attributes has been changed to that of reducing damage from element-based attacks.
|
||||||
|
|
||||||
if (defender.statusEffects.HasStatusEffect(StatusEffectId.Shell))
|
//http://kanican.livejournal.com/55370.html:
|
||||||
{
|
//elemental resistance stats are not actually related to resists (except for status effects), instead they impact damage taken
|
||||||
// todo: shell probably only shows on magic..
|
|
||||||
if (defender.statusEffects.HasStatusEffect(StatusEffectId.Shell))
|
|
||||||
{
|
|
||||||
if (action != null)
|
|
||||||
action.effectId |= (uint)HitEffect.Shell;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (defender.statusEffects.HasStatusEffect(StatusEffectId.Stoneskin))
|
|
||||||
{
|
//dlvl, Defense, and Vitality all effect how much damage is taken after hittype takes effect
|
||||||
if (action != null)
|
//player attacks cannot do more than 9999 damage.
|
||||||
action.effectId |= (uint)HitEffect.Stoneskin;
|
double damageTakenPercent = 1 - (defender.GetMod(Modifier.DamageTakenDown) / 100.0);
|
||||||
}
|
action.amount = (ushort)(action.amount - CalculateDlvlModifier(dlvl) * (defender.GetMod((uint)Modifier.Defense) + 0.67 * defender.GetMod((uint)Modifier.Vitality))).Clamp(0, 9999);
|
||||||
return damage;
|
action.amount = (ushort)(action.amount * damageTakenPercent).Clamp(0, 9999);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DamageTarget(Character attacker, Character defender, BattleAction action, DamageTakenType type)
|
|
||||||
|
public static void CalculateBlockDamage(Character attacker, Character defender, BattleCommand skill, BattleAction action)
|
||||||
|
{
|
||||||
|
double percentBlocked;
|
||||||
|
|
||||||
|
//Aegis boon forces a full block
|
||||||
|
if (defender.statusEffects.HasStatusEffect(StatusEffectId.AegisBoon))
|
||||||
|
percentBlocked = 1.0;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Is this a case where VIT gives Block?
|
||||||
|
percentBlocked = defender.GetMod((uint)Modifier.Block) * 0.002;//Every point of Block adds .2% to how much is blocked
|
||||||
|
percentBlocked += defender.GetMod((uint)Modifier.Vitality) * 0.001;//Every point of vitality adds .1% to how much is blocked
|
||||||
|
}
|
||||||
|
|
||||||
|
action.amountMitigated = (ushort)(action.amount * percentBlocked);
|
||||||
|
action.amount = (ushort)(action.amount * (1.0 - percentBlocked));
|
||||||
|
}
|
||||||
|
|
||||||
|
//don't know exact crit bonus formula
|
||||||
|
public static void CalculateCritDamage(Character attacker, Character defender, BattleCommand skill, BattleAction action)
|
||||||
|
{
|
||||||
|
short dlvl = (short)(defender.GetLevel() - attacker.GetLevel());
|
||||||
|
double bonus = (.04 * (dlvl * dlvl)) - 2 * dlvl;
|
||||||
|
bonus += 1.20;
|
||||||
|
double potencyModifier = (-.075 * dlvl) + 1.73;
|
||||||
|
|
||||||
|
// + potency bonus
|
||||||
|
//bonus += attacker.GetMod((uint) Modifier.CriticalPotency) * potencyModifier;
|
||||||
|
// - Crit resilience
|
||||||
|
//bonus -= attacker.GetMod((uint)Modifier.CriticalResilience) * potencyModifier;
|
||||||
|
|
||||||
|
//need to add something for bonus potency as a part of skill (ie thundara, which breaks the cap)
|
||||||
|
action.amount = (ushort)(action.amount * bonus.Clamp(1.15, 1.75));//min bonus of 115, max bonus of 175
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CalculateParryDamage(Character attacker, Character defender, BattleCommand skill, BattleAction action)
|
||||||
|
{
|
||||||
|
double percentParry = 0.75;
|
||||||
|
|
||||||
|
action.amountMitigated = (ushort)(action.amount * (1 - percentParry));
|
||||||
|
action.amount = (ushort)(action.amount * percentParry);
|
||||||
|
}
|
||||||
|
|
||||||
|
//There are 3 or 4 tiers of resist that are flat 25% decreases in damage.
|
||||||
|
//It's possible we could just calculate the damage at the same time as we determine the hit type (the same goes for the rest of the hit types)
|
||||||
|
//Or we could have HitTypes for DoubleResist, TripleResist, and FullResist that get used here.
|
||||||
|
public static void CalculateResistDamage(Character attacker, Character defender, BattleCommand skill, BattleAction action)
|
||||||
|
{
|
||||||
|
double percentResist = 0.5;
|
||||||
|
|
||||||
|
action.amountMitigated = (ushort)(action.amount * (1 - percentResist));
|
||||||
|
action.amount = (ushort)(action.amount * percentResist);
|
||||||
|
}
|
||||||
|
|
||||||
|
//It's weird that stoneskin is handled in C# and all other buffs are in scripts right now
|
||||||
|
//But it's because stoneskin acts like both a preaction and postaction buff in that it falls off after damage is dealt but impacts how much damage is dealt
|
||||||
|
public static void HandleStoneskin(Character defender, BattleAction action)
|
||||||
|
{
|
||||||
|
var mitigation = Math.Min(action.amount, defender.GetMod(Modifier.Stoneskin));
|
||||||
|
|
||||||
|
action.amount = (ushort) (action.amount - mitigation).Clamp(0, 9999);
|
||||||
|
defender.SubtractMod((uint)Modifier.Stoneskin, mitigation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DamageTarget(Character attacker, Character defender, BattleAction action, BattleActionContainer actionContainer= null)
|
||||||
{
|
{
|
||||||
if (defender != null)
|
if (defender != null)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
defender.DelHP((short)action.amount);
|
||||||
|
attacker.OnDamageDealt(defender, action, actionContainer);
|
||||||
|
defender.OnDamageTaken(attacker, action, actionContainer);
|
||||||
|
|
||||||
// todo: other stuff too
|
// todo: other stuff too
|
||||||
if (defender is BattleNpc)
|
if (defender is BattleNpc)
|
||||||
{
|
{
|
||||||
@ -88,28 +201,391 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
|
|||||||
{
|
{
|
||||||
bnpc.hateContainer.AddBaseHate(attacker);
|
bnpc.hateContainer.AddBaseHate(attacker);
|
||||||
}
|
}
|
||||||
bnpc.hateContainer.UpdateHate(attacker, action.amount);
|
bnpc.hateContainer.UpdateHate(attacker, action.enmity);
|
||||||
bnpc.lastAttacker = attacker;
|
bnpc.lastAttacker = attacker;
|
||||||
}
|
}
|
||||||
defender.DelHP((short)action.amount);
|
|
||||||
defender.OnDamageTaken(attacker, action, type);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int CalculateSpellDamage(Character attacker, Character defender, BattleCommand spell)
|
public static void HealTarget(Character caster, Character target, BattleAction action, BattleActionContainer actionContainer = null)
|
||||||
{
|
{
|
||||||
// todo: spell formulas and stuff (stoneskin, mods, stats, etc)
|
if (target != null)
|
||||||
return 69;
|
{
|
||||||
|
target.AddHP(action.amount);
|
||||||
|
|
||||||
|
target.statusEffects.CallLuaFunctionByFlag((uint) StatusEffectFlags.ActivateOnHealed, "onHealed", caster, target, action, actionContainer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region Rate Functions
|
||||||
|
|
||||||
|
//How is accuracy actually calculated?
|
||||||
|
public static double GetHitRate(Character attacker, Character defender, BattleCommand skill, BattleAction action)
|
||||||
|
{
|
||||||
|
double hitRate = 80.0;
|
||||||
|
|
||||||
|
//Add raw hit rate buffs, subtract raw evade buffs, take into account skill's accuracy modifier.
|
||||||
|
double hitBuff = attacker.GetMod(Modifier.RawHitRate);
|
||||||
|
double evadeBuff = defender.GetMod(Modifier.RawEvadeRate);
|
||||||
|
float modifier = skill != null ? skill.accuracyModifier : 0;
|
||||||
|
hitRate += (hitBuff + modifier).Clamp(0, 100.0);
|
||||||
|
hitRate -= evadeBuff;
|
||||||
|
return hitRate.Clamp(0, 100.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Whats the parry formula?
|
||||||
|
public static double GetParryRate(Character attacker, Character defender, BattleCommand skill, BattleAction action)
|
||||||
|
{
|
||||||
|
//Can't parry with shield, can't parry rear attacks
|
||||||
|
if (defender.GetMod((uint)Modifier.HasShield) != 0 || action.param == (byte) HitDirection.Rear)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
double parryRate = 10.0;
|
||||||
|
|
||||||
|
parryRate += defender.GetMod(Modifier.Parry) * 0.1;//.1% rate for every point of Parry
|
||||||
|
|
||||||
|
return parryRate + (defender.GetMod(Modifier.RawParryRate));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double GetCritRate(Character attacker, Character defender, BattleCommand skill, BattleAction action)
|
||||||
|
{
|
||||||
|
if (action.actionType == ActionType.Status)
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
//using 10.0 for now since gear isn't working
|
||||||
|
double critRate = 10.0;// 0.16 * attacker.GetMod((uint)Modifier.CritRating);//Crit rating adds .16% per point
|
||||||
|
|
||||||
|
//Add additional crit rate from skill
|
||||||
|
//Should this be a raw percent or a flat crit raitng? the wording on skills/buffs isn't clear.
|
||||||
|
critRate += 0.16 * (skill != null ? skill.bonusCritRate : 0);
|
||||||
|
|
||||||
|
return critRate + attacker.GetMod(Modifier.RawCritRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
//http://kanican.livejournal.com/55370.html
|
||||||
|
// todo: figure that out
|
||||||
|
public static double GetResistRate(Character attacker, Character defender, BattleCommand skill, BattleAction action)
|
||||||
|
{
|
||||||
|
// todo: add elemental stuff
|
||||||
|
//Can only resist spells?
|
||||||
|
if (action.commandType != CommandType.Spell && action.actionProperty <= ActionProperty.Projectile)
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
return 15.0 + defender.GetMod(Modifier.RawResistRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Block Rate follows 4 simple rules:
|
||||||
|
//(1) Every point in DEX gives +0.1% rate
|
||||||
|
//(2) Every point in "Block Rate" gives +0.2% rate
|
||||||
|
//(3) True block proc rate is capped at 75%. No clue on a possible floor.
|
||||||
|
//(4) The baseline rate is based on dLVL only(mob stats play no role). The baseline rate is summarized in this raw data sheet: https://imgbox.com/aasLyaJz
|
||||||
|
public static double GetBlockRate(Character attacker, Character defender, BattleCommand skill, BattleAction action)
|
||||||
|
{
|
||||||
|
//Shields are required to block and can't block from rear.
|
||||||
|
if (defender.GetMod((uint)Modifier.HasShield) == 0 || action.param == (byte)HitDirection.Rear)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
short dlvl = (short)(defender.GetLevel() - attacker.GetLevel());
|
||||||
|
double blockRate = (2.5 * dlvl) + 5; // Base block rate
|
||||||
|
|
||||||
|
//Is this one of those thing where DEX gives block rate and this would be taking DEX into account twice?
|
||||||
|
blockRate += defender.GetMod((uint)Modifier.Dexterity) * 0.1;// .1% for every dex
|
||||||
|
blockRate += defender.GetMod((uint)Modifier.BlockRate) * 0.2;// .2% for every block rate
|
||||||
|
|
||||||
|
return Math.Min(blockRate, 25.0) + defender.GetMod((uint)Modifier.RawBlockRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public static bool TryCrit(Character attacker, Character defender, BattleCommand skill, BattleAction action)
|
||||||
|
{
|
||||||
|
if ((Program.Random.NextDouble() * 100) <= action.critRate)
|
||||||
|
{
|
||||||
|
action.hitType = HitType.Crit;
|
||||||
|
CalculateCritDamage(attacker, defender, skill, action);
|
||||||
|
|
||||||
|
if(skill != null)
|
||||||
|
skill.actionCrit = true;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryResist(Character attacker, Character defender, BattleCommand skill, BattleAction action)
|
||||||
|
{
|
||||||
|
if ((Program.Random.NextDouble() * 100) <= action.resistRate)
|
||||||
|
{
|
||||||
|
action.hitType = HitType.Resist;
|
||||||
|
CalculateResistDamage(attacker, defender, skill, action);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryBlock(Character attacker, Character defender, BattleCommand skill, BattleAction action)
|
||||||
|
{
|
||||||
|
if ((Program.Random.NextDouble() * 100) <= action.blockRate)
|
||||||
|
{
|
||||||
|
action.hitType = HitType.Block;
|
||||||
|
CalculateBlockDamage(attacker, defender, skill, action);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryParry(Character attacker, Character defender, BattleCommand skill, BattleAction action)
|
||||||
|
{
|
||||||
|
if ((Program.Random.NextDouble() * 100) <= action.parryRate)
|
||||||
|
{
|
||||||
|
action.hitType = HitType.Parry;
|
||||||
|
CalculateParryDamage(attacker, defender, skill, action);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TryMiss instead of tryHit because hits are the default and don't change damage
|
||||||
|
public static bool TryMiss(Character attacker, Character defender, BattleCommand skill, BattleAction action)
|
||||||
|
{
|
||||||
|
if ((Program.Random.NextDouble() * 100) >= GetHitRate(attacker, defender, skill, action))
|
||||||
|
{
|
||||||
|
action.hitType = (ushort)HitType.Miss;
|
||||||
|
//On misses, the entire amount is considered mitigated
|
||||||
|
action.amountMitigated = action.amount;
|
||||||
|
action.amount = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Hit Effecthelpers. Different types of hit effects hits use some flags for different things, so they're split into physical, magical, heal, and status
|
||||||
|
*/
|
||||||
|
public static void DoAction(Character caster, Character target, BattleCommand skill, BattleAction action, BattleActionContainer actionContainer = null)
|
||||||
|
{
|
||||||
|
switch (action.actionType)
|
||||||
|
{
|
||||||
|
case (ActionType.Physical):
|
||||||
|
FinishActionPhysical(caster, target, skill, action, actionContainer);
|
||||||
|
break;
|
||||||
|
case (ActionType.Magic):
|
||||||
|
FinishActionSpell(caster, target, skill, action, actionContainer);
|
||||||
|
break;
|
||||||
|
case (ActionType.Heal):
|
||||||
|
FinishActionHeal(caster, target, skill, action, actionContainer);
|
||||||
|
break;
|
||||||
|
case (ActionType.Status):
|
||||||
|
FinishActionStatus(caster, target, skill, action, actionContainer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Determine the hit type, set the hit effect, modify damage based on stoneskin and hit type, hit target
|
||||||
|
public static void FinishActionPhysical(Character attacker, Character defender, BattleCommand skill, BattleAction action, BattleActionContainer actionContainer = null)
|
||||||
|
{
|
||||||
|
//Figure out the hit type and change damage depending on hit type
|
||||||
|
if (!TryMiss(attacker, defender, skill, action))
|
||||||
|
{
|
||||||
|
//Handle Stoneskin here because it seems like stoneskin mitigates damage done before taking into consideration crit/block/parry damage reductions.
|
||||||
|
//This is based on the fact that a 0 damage attack due to stoneskin will heal for 0 with Aegis Boon, meaning Aegis Boon didn't mitigate any damage
|
||||||
|
HandleStoneskin(defender, action);
|
||||||
|
|
||||||
|
//Crits can't be blocked (is this true for Aegis Boon and Divine Veil?) or parried so they are checked first.
|
||||||
|
if (!TryCrit(attacker, defender, skill, action))
|
||||||
|
//Block and parry order don't really matter because if you can block you can't parry and vice versa
|
||||||
|
if (!TryBlock(attacker, defender, skill, action))
|
||||||
|
if(!TryParry(attacker, defender, skill, action))
|
||||||
|
//Finally if it's none of these, the attack was a hit
|
||||||
|
action.hitType = HitType.Hit;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Actions have different text ids depending on whether they're a part of a multi-hit ws or not.
|
||||||
|
Dictionary<HitType, ushort> textIds = SingleHitTypeTextIds;
|
||||||
|
|
||||||
|
//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.
|
||||||
|
if (skill != null && skill.numHits > 1)
|
||||||
|
{
|
||||||
|
if (action.hitNum == 1)
|
||||||
|
actionContainer?.AddAction(new BattleAction(attacker.actorId, 30441, 0));
|
||||||
|
|
||||||
|
textIds = MultiHitTypeTextIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Set the correct textId
|
||||||
|
action.worldMasterTextId = textIds[action.hitType];
|
||||||
|
|
||||||
|
//Set the hit effect
|
||||||
|
SetHitEffectPhysical(attacker, defender, skill, action, actionContainer);
|
||||||
|
|
||||||
|
//Modify damage based on defender's stats
|
||||||
|
CalculatePhysicalDamageTaken(attacker, defender, skill, action);
|
||||||
|
|
||||||
|
actionContainer.AddAction(action);
|
||||||
|
action.enmity = (ushort) (action.enmity * (skill != null ? skill.enmityModifier : 1));
|
||||||
|
//Damage the target
|
||||||
|
DamageTarget(attacker, defender, action, actionContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void FinishActionSpell(Character attacker, Character defender, BattleCommand skill, BattleAction action, BattleActionContainer actionContainer = null)
|
||||||
|
{
|
||||||
|
//Determine the hit type of the action
|
||||||
|
if (!TryMiss(attacker, defender, skill, action))
|
||||||
|
{
|
||||||
|
HandleStoneskin(defender, action);
|
||||||
|
if (!TryCrit(attacker, defender, skill, action))
|
||||||
|
if (!TryResist(attacker, defender, skill, action))
|
||||||
|
action.hitType = HitType.Hit;
|
||||||
|
}
|
||||||
|
|
||||||
|
//There are no multi-hit spells
|
||||||
|
action.worldMasterTextId = SingleHitTypeTextIds[action.hitType];
|
||||||
|
|
||||||
|
//Set the hit effect
|
||||||
|
SetHitEffectSpell(attacker, defender, skill, action);
|
||||||
|
|
||||||
|
HandleStoneskin(defender, action);
|
||||||
|
|
||||||
|
CalculateSpellDamageTaken(attacker, defender, skill, action);
|
||||||
|
|
||||||
|
actionContainer.AddAction(action);
|
||||||
|
|
||||||
|
DamageTarget(attacker, defender, action, actionContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void FinishActionHeal(Character attacker, Character defender, BattleCommand skill, BattleAction action, BattleActionContainer actionContainer = null)
|
||||||
|
{
|
||||||
|
//Set the hit effect
|
||||||
|
SetHitEffectHeal(attacker, defender, skill, action);
|
||||||
|
|
||||||
|
actionContainer.AddAction(action);
|
||||||
|
|
||||||
|
HealTarget(attacker, defender, action, actionContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void FinishActionStatus(Character attacker, Character defender, BattleCommand skill, BattleAction action, BattleActionContainer actionContainer = null)
|
||||||
|
{
|
||||||
|
//Set the hit effect
|
||||||
|
SetHitEffectStatus(attacker, defender, skill, action);
|
||||||
|
|
||||||
|
TryStatus(attacker, defender, skill, action, actionContainer, false);
|
||||||
|
|
||||||
|
actionContainer.AddAction(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetHitEffectPhysical(Character attacker, Character defender, BattleCommand skill, BattleAction action, BattleActionContainer actionContainer)
|
||||||
|
{
|
||||||
|
var hitEffect = HitEffect.HitEffectType;
|
||||||
|
HitType hitType = action.hitType;
|
||||||
|
|
||||||
|
//Don't know what recoil is actually based on, just guessing
|
||||||
|
//Crit is 2 and 3 together
|
||||||
|
if (hitType == HitType.Crit)
|
||||||
|
hitEffect |= HitEffect.CriticalHit;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//It's not clear what recoil level is based on for physical attacks
|
||||||
|
double percentDealt = (100.0 * (action.amount / defender.GetMaxHP()));
|
||||||
|
if (percentDealt > 5.0)
|
||||||
|
hitEffect |= HitEffect.RecoilLv2;
|
||||||
|
else if (percentDealt > 10)
|
||||||
|
hitEffect |= HitEffect.RecoilLv3;
|
||||||
|
}
|
||||||
|
|
||||||
|
hitEffect |= HitTypeEffects[hitType];
|
||||||
|
|
||||||
|
//For combos that land, add the combo effect
|
||||||
|
if (skill != null && skill.isCombo && hitType > HitType.Evade && hitType != HitType.Evade)
|
||||||
|
hitEffect |= (HitEffect)(skill.comboStep << 15);
|
||||||
|
|
||||||
|
//if attack hit the target, take into account protective status effects
|
||||||
|
if (hitType >= HitType.Parry)
|
||||||
|
{
|
||||||
|
//Protect / Shell only show on physical/ magical attacks respectively.
|
||||||
|
if (defender.statusEffects.HasStatusEffect(StatusEffectId.Protect))
|
||||||
|
if (action != null)
|
||||||
|
hitEffect |= HitEffect.Protect;
|
||||||
|
|
||||||
|
if (defender.statusEffects.HasStatusEffect(StatusEffectId.Stoneskin))
|
||||||
|
if (action != null)
|
||||||
|
hitEffect |= HitEffect.Stoneskin;
|
||||||
|
}
|
||||||
|
|
||||||
|
action.effectId = (uint)hitEffect;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetHitEffectSpell(Character attacker, Character defender, BattleCommand skill, BattleAction action, BattleActionContainer actionContainer = null)
|
||||||
|
{
|
||||||
|
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];
|
||||||
|
|
||||||
|
if (skill != null && skill.isCombo)
|
||||||
|
hitEffect |= (HitEffect)(skill.comboStep << 15);
|
||||||
|
|
||||||
|
//if attack hit the target, take into account protective status effects
|
||||||
|
if (hitType >= HitType.Block)
|
||||||
|
{
|
||||||
|
//Protect / Shell only show on physical/ magical attacks respectively.
|
||||||
|
if (defender.statusEffects.HasStatusEffect(StatusEffectId.Shell))
|
||||||
|
if (action != null)
|
||||||
|
hitEffect |= HitEffect.Shell;
|
||||||
|
|
||||||
|
if (defender.statusEffects.HasStatusEffect(StatusEffectId.Stoneskin))
|
||||||
|
if (action != null)
|
||||||
|
hitEffect |= HitEffect.Stoneskin;
|
||||||
|
}
|
||||||
|
action.effectId = (uint)hitEffect;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void SetHitEffectHeal(Character caster, Character receiver, BattleCommand skill, BattleAction action)
|
||||||
|
{
|
||||||
|
var hitEffect = HitEffect.MagicEffectType | HitEffect.Heal;
|
||||||
|
//Heals use recoil levels in some way as well. Possibly for very low health clutch heals or based on percentage of current health healed (not max health).
|
||||||
|
// todo: figure recoil levels out for heals
|
||||||
|
hitEffect |= HitEffect.RecoilLv3;
|
||||||
|
//do heals crit?
|
||||||
|
|
||||||
|
action.effectId = (uint)hitEffect;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetHitEffectStatus(Character caster, Character receiver, BattleCommand skill, BattleAction action)
|
||||||
|
{
|
||||||
|
var hitEffect = (uint)HitEffect.StatusEffectType | skill.statusId;
|
||||||
|
action.effectId = hitEffect;
|
||||||
|
|
||||||
|
action.hitType = HitType.Hit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static uint CalculateSpellCost(Character caster, Character target, BattleCommand spell)
|
public static uint CalculateSpellCost(Character caster, Character target, BattleCommand spell)
|
||||||
{
|
{
|
||||||
var scaledCost = spell.CalculateCost((uint)caster.charaWork.parameterSave.state_mainSkillLevel);
|
var scaledCost = spell.CalculateMpCost(caster);
|
||||||
|
|
||||||
// todo: calculate cost for mob/player
|
// todo: calculate cost for mob/player
|
||||||
if (caster is BattleNpc)
|
if (caster is BattleNpc)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -117,5 +593,234 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
|
|||||||
}
|
}
|
||||||
return scaledCost;
|
return scaledCost;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//IsAdditional is needed because additional actions may be required for some actions' effects
|
||||||
|
//For instance, Goring Blade's bleed effect requires another action so the first action can still show damage numbers
|
||||||
|
//Sentinel doesn't require an additional action because it doesn't need to show those numbers
|
||||||
|
//this is stupid
|
||||||
|
public static void TryStatus(Character caster, Character target, BattleCommand skill, BattleAction action, BattleActionContainer battleActions, bool isAdditional = true)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
StatusEffect effect = Server.GetWorldManager().GetStatusEffect(skill.statusId);
|
||||||
|
//Because combos might change duration or tier
|
||||||
|
if (effect != null)
|
||||||
|
{
|
||||||
|
effect.SetDuration(skill.statusDuration);
|
||||||
|
effect.SetTier(skill.statusTier);
|
||||||
|
effect.SetMagnitude(skill.statusMagnitude);
|
||||||
|
effect.SetOwner(target);
|
||||||
|
if (target.statusEffects.AddStatusEffect(effect, caster))
|
||||||
|
{
|
||||||
|
//If we need an extra action to show the status text
|
||||||
|
if (isAdditional)
|
||||||
|
battleActions.AddAction(target.actorId, 30328, skill.statusId | (uint) HitEffect.StatusEffectType);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
action.worldMasterTextId = 32002;//Is this right?
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//until all effects are scripted and added to db just doing this
|
||||||
|
if (target.statusEffects.AddStatusEffect(skill.statusId, skill.statusTier, skill.statusMagnitude, skill.statusDuration, 3000))
|
||||||
|
{
|
||||||
|
//If we need an extra action to show the status text
|
||||||
|
if (isAdditional)
|
||||||
|
battleActions.AddAction(target.actorId, 30328, skill.statusId | (uint) HitEffect.StatusEffectType);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
action.worldMasterTextId = 32002;//Is this right?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Convert a HitDirection to a BattleCommandPositionBonus. Basically just combining left/right into flank
|
||||||
|
public static BattleCommandPositionBonus ConvertHitDirToPosition(HitDirection hitDir)
|
||||||
|
{
|
||||||
|
BattleCommandPositionBonus position = BattleCommandPositionBonus.None;
|
||||||
|
|
||||||
|
switch (hitDir)
|
||||||
|
{
|
||||||
|
case (HitDirection.Front):
|
||||||
|
position = BattleCommandPositionBonus.Front;
|
||||||
|
break;
|
||||||
|
case (HitDirection.Right):
|
||||||
|
case (HitDirection.Left):
|
||||||
|
position = BattleCommandPositionBonus.Flank;
|
||||||
|
break;
|
||||||
|
case (HitDirection.Rear):
|
||||||
|
position = BattleCommandPositionBonus.Rear;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region experience helpers
|
||||||
|
//See 1.19 patch notes for exp info.
|
||||||
|
public static ushort GetBaseEXP(Player player, BattleNpc mob)
|
||||||
|
{
|
||||||
|
//The way EXP seems to work for most enemies is that it gets the lower character's level, gets the base exp for that level, then uses dlvl to modify that exp
|
||||||
|
//Less than -19 dlvl gives 0 exp and no message is sent.
|
||||||
|
//This equation doesn't seem to work for certain bosses or NMs.
|
||||||
|
//Some enemies might give less EXP? Unsure on this. It seems like there might have been a change in base exp amounts after 1.19
|
||||||
|
|
||||||
|
//Example:
|
||||||
|
//Level 50 in a party kills a level 45 enemy
|
||||||
|
//Base exp is 400, as that's the base EXP for level 45
|
||||||
|
//That's multiplied by the dlvl modifier for -5, which is 0.5625, which gives 225
|
||||||
|
//That's then multiplied by the party modifier, which seems to be 0.667 regardless of party size, which gives 150
|
||||||
|
//150 is then modified by bonus experience from food, rested exp, links, and chains
|
||||||
|
|
||||||
|
int dlvl = mob.GetLevel() - player.GetLevel();
|
||||||
|
if (dlvl <= -20)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
int baseLevel = Math.Min(player.GetLevel(), mob.GetLevel());
|
||||||
|
ushort baseEXP = BASEEXP[baseLevel];
|
||||||
|
|
||||||
|
double dlvlModifier = 1.0;
|
||||||
|
|
||||||
|
//There's 2 functions depending on if the dlvl is positive or negative.
|
||||||
|
if (dlvl >= 0)
|
||||||
|
//I'm not sure if this caps out at some point. This is correct up to at least +9 dlvl though.
|
||||||
|
dlvlModifier += 0.2 * dlvl;
|
||||||
|
else
|
||||||
|
//0.1x + 0.0025x^2
|
||||||
|
dlvlModifier += 0.1 * dlvl + 0.0025 * (dlvl * dlvl);
|
||||||
|
|
||||||
|
//The party modifier isn't clear yet. It seems like it might just be 0.667 for any number of members in a group, but the 1.19 notes say it's variable
|
||||||
|
//There also seem to be some cases where it simply doesn't apply but it isn't obvious if that's correct or when it applies if it is correct
|
||||||
|
double partyModifier = player.currentParty.GetMemberCount() == 1 ? 1.0 : 0.667;
|
||||||
|
|
||||||
|
baseEXP = (ushort) (baseEXP * dlvlModifier * partyModifier);
|
||||||
|
|
||||||
|
return baseEXP;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Gets the EXP bonus when enemies link
|
||||||
|
public static byte GetLinkBonus(ushort linkCount)
|
||||||
|
{
|
||||||
|
byte bonus = 0;
|
||||||
|
|
||||||
|
switch (linkCount)
|
||||||
|
{
|
||||||
|
case (0):
|
||||||
|
break;
|
||||||
|
case (1):
|
||||||
|
bonus = 25;
|
||||||
|
break;
|
||||||
|
case (2):
|
||||||
|
bonus = 50;
|
||||||
|
break;
|
||||||
|
case (3):
|
||||||
|
bonus = 75;
|
||||||
|
break;
|
||||||
|
case (4):
|
||||||
|
default:
|
||||||
|
bonus = 100;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bonus;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Gets EXP chain bonus for Attacker fighting Defender
|
||||||
|
//Official text on EXP Chains: An EXP Chain occurs when players consecutively defeat enemies of equal or higher level than themselves within a specific amount of time.
|
||||||
|
//Assuming this means that there is no bonus for enemies below player's level and EXP chains are specific to the person, not party
|
||||||
|
public static byte GetChainBonus(ushort tier)
|
||||||
|
{
|
||||||
|
byte bonus = 0;
|
||||||
|
|
||||||
|
switch (tier)
|
||||||
|
{
|
||||||
|
case (0):
|
||||||
|
break;
|
||||||
|
case (1):
|
||||||
|
bonus = 20;
|
||||||
|
break;
|
||||||
|
case (2):
|
||||||
|
bonus = 25;
|
||||||
|
break;
|
||||||
|
case (3):
|
||||||
|
bonus = 30;
|
||||||
|
break;
|
||||||
|
case (4):
|
||||||
|
bonus = 40;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
bonus = 50;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return bonus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte GetChainTimeLimit(ushort tier)
|
||||||
|
{
|
||||||
|
byte timeLimit = 0;
|
||||||
|
|
||||||
|
switch (tier)
|
||||||
|
{
|
||||||
|
case (0):
|
||||||
|
timeLimit = 100;
|
||||||
|
break;
|
||||||
|
case (1):
|
||||||
|
timeLimit = 80;
|
||||||
|
break;
|
||||||
|
case (2):
|
||||||
|
timeLimit = 60;
|
||||||
|
break;
|
||||||
|
case (3):
|
||||||
|
timeLimit = 20;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
timeLimit = 10;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return timeLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Calculates bonus EXP for Links and Chains
|
||||||
|
public static void AddBattleBonusEXP(Player attacker, BattleNpc defender, BattleActionContainer actionContainer)
|
||||||
|
{
|
||||||
|
ushort baseExp = GetBaseEXP(attacker, defender);
|
||||||
|
|
||||||
|
//Only bother calculating the rest if there's actually exp to be gained.
|
||||||
|
//0 exp sends no message
|
||||||
|
if (baseExp > 0)
|
||||||
|
{
|
||||||
|
int totalBonus = 0;//GetMod(Modifier.bonusEXP)
|
||||||
|
|
||||||
|
var linkCount = defender.GetMobMod(MobModifier.LinkCount);
|
||||||
|
totalBonus += GetLinkBonus((byte)Math.Min(linkCount, 255));
|
||||||
|
|
||||||
|
StatusEffect effect = attacker.statusEffects.GetStatusEffectById((uint)StatusEffectId.EXPChain);
|
||||||
|
ushort expChainNumber = 0;
|
||||||
|
uint timeLimit = 100;
|
||||||
|
if (effect != null)
|
||||||
|
{
|
||||||
|
expChainNumber = effect.GetTier();
|
||||||
|
timeLimit = (uint)(GetChainTimeLimit(expChainNumber));
|
||||||
|
actionContainer?.AddEXPAction(new BattleAction(attacker.actorId, 33919, 0, expChainNumber, (byte)timeLimit));
|
||||||
|
}
|
||||||
|
|
||||||
|
totalBonus += GetChainBonus(expChainNumber);
|
||||||
|
|
||||||
|
StatusEffect newChain = Server.GetWorldManager().GetStatusEffect((uint)StatusEffectId.EXPChain);
|
||||||
|
|
||||||
|
newChain.SetDuration(timeLimit);
|
||||||
|
newChain.SetTier((byte)(Math.Min(expChainNumber + 1, 255)));
|
||||||
|
attacker.statusEffects.AddStatusEffect(newChain, attacker, true, true);
|
||||||
|
|
||||||
|
actionContainer?.AddEXPActions(attacker.AddExp(baseExp, (byte)attacker.GetClass(), (byte)(totalBonus.Min(255))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -118,34 +118,33 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
return subpackets;
|
return subpackets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//This might need more work
|
||||||
|
//I think there migh be something that ties mobs to parties
|
||||||
|
//and the client checks if any mobs are tied to the current party
|
||||||
|
//and bases the color on that. Adding mob to party obviously doesn't work
|
||||||
|
//Based on depictionjudge script:
|
||||||
|
//HATE_TYPE_NONE is for passive
|
||||||
|
//HATE_TYPE_ENGAGED is for aggroed mobs
|
||||||
|
//HATE_TYPE_ENGAGED_PARTY is for claimed mobs, client uses occupancy group to determine if mob is claimed by player's party
|
||||||
|
//for now i'm just going to assume that occupancygroup will be BattleNpc's currentparties when they're in combat,
|
||||||
|
//so if party isn't null, they're claimed.
|
||||||
public SubPacket GetHateTypePacket(Player player)
|
public SubPacket GetHateTypePacket(Player player)
|
||||||
{
|
{
|
||||||
npcWork.hateType = 1;
|
npcWork.hateType = NpcWork.HATE_TYPE_NONE;
|
||||||
|
|
||||||
if (player != null)
|
if (player != null)
|
||||||
{
|
{
|
||||||
if (aiContainer.IsEngaged())
|
if (aiContainer.IsEngaged())
|
||||||
{
|
{
|
||||||
npcWork.hateType = 2;
|
npcWork.hateType = NpcWork.HATE_TYPE_ENGAGED;
|
||||||
}
|
|
||||||
|
|
||||||
if (player.actorId == this.currentLockedTarget)
|
if (this.currentParty != null)
|
||||||
{
|
|
||||||
npcWork.hateType = NpcWork.HATE_TYPE_ENGAGED_PARTY;
|
|
||||||
}
|
|
||||||
else if (player.currentParty != null)
|
|
||||||
{
|
|
||||||
foreach (var memberId in ((Party)player.currentParty).members)
|
|
||||||
{
|
{
|
||||||
if (this.currentLockedTarget == memberId)
|
npcWork.hateType = NpcWork.HATE_TYPE_ENGAGED_PARTY;
|
||||||
{
|
|
||||||
npcWork.hateType = NpcWork.HATE_TYPE_ENGAGED_PARTY;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var propPacketUtil = new ActorPropertyPacketUtil("npcWork", this);
|
npcWork.hateType = 3;
|
||||||
|
var propPacketUtil = new ActorPropertyPacketUtil("npcWork/hate", this);
|
||||||
propPacketUtil.AddProperty("npcWork.hateType");
|
propPacketUtil.AddProperty("npcWork.hateType");
|
||||||
return propPacketUtil.Done()[0];
|
return propPacketUtil.Done()[0];
|
||||||
}
|
}
|
||||||
@ -192,13 +191,28 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
public override bool CanCast(Character target, BattleCommand spell)
|
public override bool CanCast(Character target, BattleCommand spell)
|
||||||
{
|
{
|
||||||
// todo:
|
// todo:
|
||||||
return false;
|
if (target == null)
|
||||||
|
{
|
||||||
|
// Target does not exist.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (Utils.Distance(positionX, positionY, positionZ, target.positionX, target.positionY, target.positionZ) > spell.range)
|
||||||
|
{
|
||||||
|
// The target is out of range.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!IsValidTarget(target, spell.mainTarget) || !spell.IsValidMainTarget(this, target))
|
||||||
|
{
|
||||||
|
// error packet is set in IsValidTarget
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanWeaponSkill(Character target, BattleCommand skill)
|
public override bool CanWeaponSkill(Character target, BattleCommand skill)
|
||||||
{
|
{
|
||||||
// todo:
|
// todo:
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanUseAbility(Character target, BattleCommand ability)
|
public override bool CanUseAbility(Character target, BattleCommand ability)
|
||||||
@ -257,7 +271,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
updateFlags |= ActorUpdateFlags.AllNpc;
|
updateFlags |= ActorUpdateFlags.AllNpc;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Die(DateTime tick)
|
public override void Die(DateTime tick, BattleActionContainer actionContainer = null)
|
||||||
{
|
{
|
||||||
if (IsAlive())
|
if (IsAlive())
|
||||||
{
|
{
|
||||||
@ -271,24 +285,25 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
{
|
{
|
||||||
//I think this is, or should be odne in DoBattleAction. Packet capture had the message in the same packet as an attack
|
//I think this is, or should be odne in DoBattleAction. Packet capture had the message in the same packet as an attack
|
||||||
// <actor> defeat/defeats <target>
|
// <actor> defeat/defeats <target>
|
||||||
//((Player)lastAttacker).QueuePacket(BattleActionX01Packet.BuildPacket(lastAttacker.actorId, 0, 0, new BattleAction(actorId, 30108, 0)));
|
actionContainer?.AddEXPAction(new BattleAction(actorId, 30108, 0));
|
||||||
|
|
||||||
if (lastAttacker.currentParty != null && lastAttacker.currentParty is Party)
|
if (lastAttacker.currentParty != null && lastAttacker.currentParty is Party)
|
||||||
{
|
{
|
||||||
foreach (var memberId in ((Party)lastAttacker.currentParty).members)
|
foreach (var memberId in ((Party)lastAttacker.currentParty).members)
|
||||||
{
|
{
|
||||||
var partyMember = zone.FindActorInArea<Player>(memberId);
|
var partyMember = zone.FindActorInArea<Character>(memberId);
|
||||||
// onDeath(monster, player, killer)
|
// onDeath(monster, player, killer)
|
||||||
lua.LuaEngine.CallLuaBattleFunction(this, "onDeath", this, partyMember, lastAttacker);
|
lua.LuaEngine.CallLuaBattleFunction(this, "onDeath", this, partyMember, lastAttacker);
|
||||||
if (partyMember is Player)
|
|
||||||
((Player)partyMember).AddExp(1500, (byte)partyMember.GetClass(), 5);
|
// todo: add actual experience calculation and exp bonus values.
|
||||||
|
if (partyMember is Player player)
|
||||||
|
BattleUtils.AddBattleBonusEXP(player, this, actionContainer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// onDeath(monster, player, killer)
|
// onDeath(monster, player, killer)
|
||||||
lua.LuaEngine.CallLuaBattleFunction(this, "onDeath", this, lastAttacker, lastAttacker);
|
lua.LuaEngine.CallLuaBattleFunction(this, "onDeath", this, lastAttacker, lastAttacker);
|
||||||
((Player)lastAttacker).QueuePacket(BattleActionX01Packet.BuildPacket(lastAttacker.actorId, 0, 0, new BattleAction(actorId, 30108, 0)));
|
//((Player)lastAttacker).QueuePacket(BattleActionX01Packet.BuildPacket(lastAttacker.actorId, 0, 0, new BattleAction(actorId, 30108, 0)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
positionUpdates?.Clear();
|
positionUpdates?.Clear();
|
||||||
@ -359,18 +374,18 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
lua.LuaEngine.CallLuaBattleFunction(this, "onAttack", this, state.GetTarget(), action.amount);
|
lua.LuaEngine.CallLuaBattleFunction(this, "onAttack", this, state.GetTarget(), action.amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnCast(State state, BattleAction[] actions, ref BattleAction[] errors)
|
public override void OnCast(State state, BattleAction[] actions, BattleCommand spell, ref BattleAction[] errors)
|
||||||
{
|
{
|
||||||
base.OnCast(state, actions, ref errors);
|
base.OnCast(state, actions, spell, ref errors);
|
||||||
|
|
||||||
if (GetMobMod((uint)MobModifier.SpellScript) != 0)
|
if (GetMobMod((uint)MobModifier.SpellScript) != 0)
|
||||||
foreach (var action in actions)
|
foreach (var action in actions)
|
||||||
lua.LuaEngine.CallLuaBattleFunction(this, "onCast", this, zone.FindActorInArea<Character>(action.targetId), ((MagicState)state).GetSpell(), action);
|
lua.LuaEngine.CallLuaBattleFunction(this, "onCast", this, zone.FindActorInArea<Character>(action.targetId), ((MagicState)state).GetSpell(), action);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnAbility(State state, BattleAction[] actions, ref BattleAction[] errors)
|
public override void OnAbility(State state, BattleAction[] actions, BattleCommand ability, ref BattleAction[] errors)
|
||||||
{
|
{
|
||||||
base.OnAbility(state, actions, ref errors);
|
base.OnAbility(state, actions, ability, ref errors);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
if (GetMobMod((uint)MobModifier.AbilityScript) != 0)
|
if (GetMobMod((uint)MobModifier.AbilityScript) != 0)
|
||||||
@ -379,9 +394,9 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnWeaponSkill(State state, BattleAction[] actions, ref BattleAction[] errors)
|
public override void OnWeaponSkill(State state, BattleAction[] actions, BattleCommand skill, ref BattleAction[] errors)
|
||||||
{
|
{
|
||||||
base.OnWeaponSkill(state, actions, ref errors);
|
base.OnWeaponSkill(state, actions, skill, ref errors);
|
||||||
|
|
||||||
if (GetMobMod((uint)MobModifier.WeaponSkillScript) != 0)
|
if (GetMobMod((uint)MobModifier.WeaponSkillScript) != 0)
|
||||||
foreach (var action in actions)
|
foreach (var action in actions)
|
||||||
@ -414,6 +429,10 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
this.bnpcId = id;
|
this.bnpcId = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Int64 GetMobMod(MobModifier mobMod)
|
||||||
|
{
|
||||||
|
return GetMobMod((uint)mobMod);
|
||||||
|
}
|
||||||
|
|
||||||
public Int64 GetMobMod(uint mobModId)
|
public Int64 GetMobMod(uint mobModId)
|
||||||
{
|
{
|
||||||
@ -431,10 +450,11 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
mobModifiers.Add((MobModifier)mobModId, val);
|
mobModifiers.Add((MobModifier)mobModId, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnDamageTaken(Character attacker, BattleAction action, DamageTakenType damageTakenType)
|
public override void OnDamageTaken(Character attacker, BattleAction action, BattleActionContainer actionContainer = null)
|
||||||
{
|
{
|
||||||
if (GetMobMod((uint)MobModifier.DefendScript) != 0)
|
if (GetMobMod((uint)MobModifier.DefendScript) != 0)
|
||||||
lua.LuaEngine.CallLuaBattleFunction(this, "onDamageTaken", this, attacker, action.amount, (uint)damageTakenType);
|
lua.LuaEngine.CallLuaBattleFunction(this, "onDamageTaken", this, attacker, action.amount);
|
||||||
|
base.OnDamageTaken(attacker, action, actionContainer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,8 @@ namespace FFXIVClassic_Map_Server.actors.chara.npc
|
|||||||
CallForHelp = 22, // actor with this id outside of target's party with this can attack me
|
CallForHelp = 22, // actor with this id outside of target's party with this can attack me
|
||||||
FreeForAll = 23, // any actor can attack me
|
FreeForAll = 23, // any actor can attack me
|
||||||
Roams = 24, // Do I walk around?
|
Roams = 24, // Do I walk around?
|
||||||
RoamDelay = 25 // What is the delay between roam ticks
|
RoamDelay = 25, // What is the delay between roam ticks
|
||||||
|
Linked = 26, // Did I get aggroed via linking?
|
||||||
|
LinkCount = 27 // How many BattleNPCs got linked with me
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -194,8 +194,8 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
subpackets.Add(CreateSpeedPacket());
|
subpackets.Add(CreateSpeedPacket());
|
||||||
subpackets.Add(CreateSpawnPositonPacket(0x0));
|
subpackets.Add(CreateSpawnPositonPacket(0x0));
|
||||||
|
|
||||||
if (isMapObj)
|
if (isMapObj)
|
||||||
subpackets.Add(SetActorBGPropertiesPacket.BuildPacket(actorId, instance, layout));
|
subpackets.Add(SetActorBGPropertiesPacket.BuildPacket(actorId, instance, layout));
|
||||||
else
|
else
|
||||||
subpackets.Add(CreateAppearancePacket());
|
subpackets.Add(CreateAppearancePacket());
|
||||||
|
|
||||||
@ -245,7 +245,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
//Status Times
|
//Status Times
|
||||||
for (int i = 0; i < charaWork.statusShownTime.Length; i++)
|
for (int i = 0; i < charaWork.statusShownTime.Length; i++)
|
||||||
{
|
{
|
||||||
if (charaWork.statusShownTime[i] != 0xFFFFFFFF)
|
if (charaWork.statusShownTime[i] != 0)
|
||||||
propPacketUtil.AddProperty(String.Format("charaWork.statusShownTime[{0}]", i));
|
propPacketUtil.AddProperty(String.Format("charaWork.statusShownTime[{0}]", i));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,6 +163,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.player
|
|||||||
owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId));
|
owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId));
|
||||||
|
|
||||||
list[slot] = item;
|
list[slot] = item;
|
||||||
|
owner.CalculateBaseStats();// RecalculateStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ToggleDBWrite(bool flag)
|
public void ToggleDBWrite(bool flag)
|
||||||
@ -189,6 +190,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.player
|
|||||||
owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId));
|
owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId));
|
||||||
|
|
||||||
list[slot] = null;
|
list[slot] = null;
|
||||||
|
owner.RecalculateStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SendEquipmentPackets(ushort equipSlot, InventoryItem item)
|
private void SendEquipmentPackets(ushort equipSlot, InventoryItem item)
|
||||||
|
@ -26,6 +26,7 @@ using FFXIVClassic_Map_Server.packets.send.actor.battle;
|
|||||||
using FFXIVClassic_Map_Server.actors.chara.ai.utils;
|
using FFXIVClassic_Map_Server.actors.chara.ai.utils;
|
||||||
using FFXIVClassic_Map_Server.actors.chara.ai.state;
|
using FFXIVClassic_Map_Server.actors.chara.ai.state;
|
||||||
using FFXIVClassic_Map_Server.actors.chara.npc;
|
using FFXIVClassic_Map_Server.actors.chara.npc;
|
||||||
|
using FFXIVClassic_Map_Server.actors.chara;
|
||||||
|
|
||||||
namespace FFXIVClassic_Map_Server.Actors
|
namespace FFXIVClassic_Map_Server.Actors
|
||||||
{
|
{
|
||||||
@ -228,6 +229,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
|
|
||||||
this.aiContainer = new AIContainer(this, new PlayerController(this), null, new TargetFind(this));
|
this.aiContainer = new AIContainer(this, new PlayerController(this), null, new TargetFind(this));
|
||||||
allegiance = CharacterTargetingAllegiance.Player;
|
allegiance = CharacterTargetingAllegiance.Player;
|
||||||
|
CalculateBaseStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<SubPacket> Create0x132Packets()
|
public List<SubPacket> Create0x132Packets()
|
||||||
@ -361,7 +363,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
//Status Times
|
//Status Times
|
||||||
for (int i = 0; i < charaWork.statusShownTime.Length; i++)
|
for (int i = 0; i < charaWork.statusShownTime.Length; i++)
|
||||||
{
|
{
|
||||||
if (charaWork.statusShownTime[i] != 0xFFFFFFFF)
|
if (charaWork.statusShownTime[i] != 0)
|
||||||
propPacketUtil.AddProperty(String.Format("charaWork.statusShownTime[{0}]", i));
|
propPacketUtil.AddProperty(String.Format("charaWork.statusShownTime[{0}]", i));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -608,12 +610,12 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
|
|
||||||
packet.ReplaceActorID(actorId);
|
packet.ReplaceActorID(actorId);
|
||||||
var packets = packet.GetSubpackets();
|
var packets = packet.GetSubpackets();
|
||||||
|
|
||||||
QueuePackets(packets);
|
QueuePackets(packets);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
this.SendMessage(SendMessagePacket.MESSAGE_TYPE_SYSTEM_ERROR, "[SendPacket]", "Unable to send packet.");
|
this.SendMessage(SendMessagePacket.MESSAGE_TYPE_SYSTEM_ERROR, "[SendPacket]", "Unable to send packet.");
|
||||||
|
this.SendMessage(SendMessagePacket.MESSAGE_TYPE_SYSTEM_ERROR, "[SendPacket]", e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1705,20 +1707,22 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
|
|
||||||
Party partyGroup = (Party) currentParty;
|
Party partyGroup = (Party) currentParty;
|
||||||
|
|
||||||
for (int i = 0; i < partyGroup.members.Count; i++)
|
partyGroup.RemoveMember(actorId);
|
||||||
{
|
|
||||||
if (partyGroup.members[i] == actorId)
|
//for (int i = 0; i < partyGroup.members.Count; i++)
|
||||||
{
|
//{
|
||||||
partyGroup.members.RemoveAt(i);
|
// if (partyGroup.members[i] == actorId)
|
||||||
break;
|
// {
|
||||||
}
|
// partyGroup.members.RemoveAt(i);
|
||||||
}
|
// break;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
//currentParty.members.Remove(this);
|
//currentParty.members.Remove(this);
|
||||||
if (partyGroup.members.Count == 0)
|
if (partyGroup.members.Count == 0)
|
||||||
Server.GetWorldManager().NoMembersInParty((Party)currentParty);
|
Server.GetWorldManager().NoMembersInParty((Party)currentParty);
|
||||||
|
|
||||||
currentParty = null;
|
//currentParty = new Party(0, actorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void IssueChocobo(byte appearanceId, string nameResponse)
|
public void IssueChocobo(byte appearanceId, string nameResponse)
|
||||||
@ -1771,10 +1775,29 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ((updateFlags & ActorUpdateFlags.Stats) != 0)
|
||||||
|
{
|
||||||
|
var propPacketUtil = new ActorPropertyPacketUtil("charaWork/battleParameter", this);
|
||||||
|
|
||||||
|
for (uint i = 0; i < 35; i++)
|
||||||
|
{
|
||||||
|
if (GetMod(i) != charaWork.battleTemp.generalParameter[i])
|
||||||
|
{
|
||||||
|
charaWork.battleTemp.generalParameter[i] = (short)GetMod(i);
|
||||||
|
propPacketUtil.AddProperty($"charaWork.battleTemp.generalParameter[{i}]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QueuePackets(propPacketUtil.Done());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
base.PostUpdate(tick, packets);
|
base.PostUpdate(tick, packets);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Die(DateTime tick)
|
public override void Die(DateTime tick, BattleActionContainer actionContainer = null)
|
||||||
{
|
{
|
||||||
// todo: death timer
|
// todo: death timer
|
||||||
aiContainer.InternalDie(tick, 60);
|
aiContainer.InternalDie(tick, 60);
|
||||||
@ -1842,27 +1865,23 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
|
|
||||||
//If the class we're equipping for is the current class, we can just look at charawork.command
|
//If the class we're equipping for is the current class, we can just look at charawork.command
|
||||||
if(classId == charaWork.parameterSave.state_mainSkill[0])
|
if(classId == charaWork.parameterSave.state_mainSkill[0])
|
||||||
{
|
|
||||||
hotbarSlot = FindFirstCommandSlotById(0);
|
hotbarSlot = FindFirstCommandSlotById(0);
|
||||||
}
|
|
||||||
//Otherwise, we need to check the database.
|
//Otherwise, we need to check the database.
|
||||||
else
|
else
|
||||||
{
|
|
||||||
hotbarSlot = (ushort) (Database.FindFirstCommandSlot(this, classId) + charaWork.commandBorder);
|
hotbarSlot = (ushort) (Database.FindFirstCommandSlot(this, classId) + charaWork.commandBorder);
|
||||||
}
|
|
||||||
|
|
||||||
EquipAbility(classId, commandId, hotbarSlot, printMessage);
|
EquipAbility(classId, commandId, hotbarSlot, printMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Add commandId to classId's hotbar at hotbarSlot.
|
//Add commandId to classId's hotbar at hotbarSlot.
|
||||||
//If classId is not the current class, do it in the database
|
//If classId is not the current class, do it in the database
|
||||||
//hotbarSlot is 32-indexed
|
//hotbarSlot starts at 32
|
||||||
public void EquipAbility(byte classId, uint commandId, ushort hotbarSlot, bool printMessage = true)
|
public void EquipAbility(byte classId, uint commandId, ushort hotbarSlot, bool printMessage = true)
|
||||||
{
|
{
|
||||||
var ability = Server.GetWorldManager().GetBattleCommand(commandId);
|
var ability = Server.GetWorldManager().GetBattleCommand(commandId);
|
||||||
uint trueCommandId = commandId | 0xA0F00000;
|
uint trueCommandId = 0xA0F00000 + commandId;
|
||||||
ushort lowHotbarSlot = (ushort)(hotbarSlot - charaWork.commandBorder);
|
ushort lowHotbarSlot = (ushort)(hotbarSlot - charaWork.commandBorder);
|
||||||
ushort maxRecastTime = (ushort)ability.recastTimeSeconds;
|
ushort maxRecastTime = (ushort)(ability != null ? ability.maxRecastTimeSeconds : 5);
|
||||||
uint recastEnd = Utils.UnixTimeStampUTC() + maxRecastTime;
|
uint recastEnd = Utils.UnixTimeStampUTC() + maxRecastTime;
|
||||||
List<ushort> slotsToUpdate = new List<ushort>();
|
List<ushort> slotsToUpdate = new List<ushort>();
|
||||||
|
|
||||||
@ -1889,6 +1908,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
//hotbarSlot 1 and 2 are 32-indexed.
|
//hotbarSlot 1 and 2 are 32-indexed.
|
||||||
public void SwapAbilities(ushort hotbarSlot1, ushort hotbarSlot2)
|
public void SwapAbilities(ushort hotbarSlot1, ushort hotbarSlot2)
|
||||||
{
|
{
|
||||||
|
//0 indexed hotbar slots for saving to database and recast timers
|
||||||
uint lowHotbarSlot1 = (ushort)(hotbarSlot1 - charaWork.commandBorder);
|
uint lowHotbarSlot1 = (ushort)(hotbarSlot1 - charaWork.commandBorder);
|
||||||
uint lowHotbarSlot2 = (ushort)(hotbarSlot2 - charaWork.commandBorder);
|
uint lowHotbarSlot2 = (ushort)(hotbarSlot2 - charaWork.commandBorder);
|
||||||
|
|
||||||
@ -1906,10 +1926,12 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
charaWork.command[hotbarSlot2] = commandId;
|
charaWork.command[hotbarSlot2] = commandId;
|
||||||
charaWork.parameterTemp.maxCommandRecastTime[lowHotbarSlot2] = recastMax;
|
charaWork.parameterTemp.maxCommandRecastTime[lowHotbarSlot2] = recastMax;
|
||||||
charaWork.parameterSave.commandSlot_recastTime[lowHotbarSlot2] = recastEnd;
|
charaWork.parameterSave.commandSlot_recastTime[lowHotbarSlot2] = recastEnd;
|
||||||
|
|
||||||
//Save changes
|
|
||||||
Database.EquipAbility(this, charaWork.parameterSave.state_mainSkill[0], (ushort)(lowHotbarSlot1), charaWork.command[hotbarSlot1], charaWork.parameterSave.commandSlot_recastTime[lowHotbarSlot1]);
|
|
||||||
|
|
||||||
|
//Save changes to both slots
|
||||||
|
Database.EquipAbility(this, GetCurrentClassOrJob(), (ushort)(lowHotbarSlot1), 0xA0F00000 ^ charaWork.command[hotbarSlot1], charaWork.parameterSave.commandSlot_recastTime[lowHotbarSlot1]);
|
||||||
|
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>();
|
List<ushort> slotsToUpdate = new List<ushort>();
|
||||||
slotsToUpdate.Add(hotbarSlot1);
|
slotsToUpdate.Add(hotbarSlot1);
|
||||||
slotsToUpdate.Add(hotbarSlot2);
|
slotsToUpdate.Add(hotbarSlot2);
|
||||||
@ -1926,7 +1948,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
slotsToUpdate.Add(trueHotbarSlot);
|
slotsToUpdate.Add(trueHotbarSlot);
|
||||||
|
|
||||||
if(printMessage)
|
if(printMessage)
|
||||||
SendGameMessage(Server.GetWorldManager().GetActor(), 30604, 0x20, 0, commandId ^ 0xA0F00000);
|
SendGameMessage(Server.GetWorldManager().GetActor(), 30604, 0x20, 0, 0xA0F00000 ^ commandId);
|
||||||
|
|
||||||
UpdateHotbar(slotsToUpdate);
|
UpdateHotbar(slotsToUpdate);
|
||||||
}
|
}
|
||||||
@ -1952,10 +1974,10 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
return firstSlot;
|
return firstSlot;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateHotbarTimer(uint commandId, uint recastTimeSeconds)
|
private void UpdateHotbarTimer(uint commandId, uint recastTimeMs)
|
||||||
{
|
{
|
||||||
ushort slot = FindFirstCommandSlotById(commandId);
|
ushort slot = FindFirstCommandSlotById(commandId);
|
||||||
charaWork.parameterSave.commandSlot_recastTime[slot - charaWork.commandBorder] = Utils.UnixTimeStampUTC(DateTime.Now.AddSeconds(recastTimeSeconds));
|
charaWork.parameterSave.commandSlot_recastTime[slot - charaWork.commandBorder] = Utils.UnixTimeStampUTC(DateTime.Now.AddMilliseconds(recastTimeMs));
|
||||||
var slots = new List<ushort>();
|
var slots = new List<ushort>();
|
||||||
slots.Add(slot);
|
slots.Add(slot);
|
||||||
UpdateRecastTimers(slots);
|
UpdateRecastTimers(slots);
|
||||||
@ -2123,7 +2145,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
SendGameMessage(Server.GetWorldManager().GetActor(), 32539, 0x20, (uint)spell.id);
|
SendGameMessage(Server.GetWorldManager().GetActor(), 32539, 0x20, (uint)spell.id);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!IsValidTarget(target, spell.validTarget) || !spell.IsValidTarget(this, target))
|
if (!IsValidTarget(target, spell.mainTarget) || !spell.IsValidMainTarget(this, target))
|
||||||
{
|
{
|
||||||
// error packet is set in IsValidTarget
|
// error packet is set in IsValidTarget
|
||||||
return false;
|
return false;
|
||||||
@ -2141,29 +2163,36 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
SendGameMessage(Server.GetWorldManager().GetActor(), 32535, 0x20, (uint)skill.id);
|
SendGameMessage(Server.GetWorldManager().GetActor(), 32535, 0x20, (uint)skill.id);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target == null)
|
if (target == null)
|
||||||
{
|
{
|
||||||
// Target does not exist.
|
// Target does not exist.
|
||||||
SendGameMessage(Server.GetWorldManager().GetActor(), 32511, 0x20, (uint)skill.id);
|
SendGameMessage(Server.GetWorldManager().GetActor(), 32511, 0x20, (uint)skill.id);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Utils.Distance(positionX, positionY, positionZ, target.positionX, target.positionY, target.positionZ) > skill.range)
|
if (Utils.Distance(positionX, positionY, positionZ, target.positionX, target.positionY, target.positionZ) > skill.range)
|
||||||
{
|
{
|
||||||
// The target is out of range.
|
// The target is out of range.
|
||||||
SendGameMessage(Server.GetWorldManager().GetActor(), 32539, 0x20, (uint)skill.id);
|
SendGameMessage(Server.GetWorldManager().GetActor(), 32539, 0x20, (uint)skill.id);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!IsValidTarget(target, skill.validTarget) || !skill.IsValidTarget(this, target))
|
|
||||||
|
if (!IsValidTarget(target, skill.validTarget) || !skill.IsValidMainTarget(this, target))
|
||||||
{
|
{
|
||||||
// error packet is set in IsValidTarget
|
// error packet is set in IsValidTarget
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnAttack(State state, BattleAction action, ref BattleAction error)
|
public override void OnAttack(State state, BattleAction action, ref BattleAction error)
|
||||||
{
|
{
|
||||||
|
var target = state.GetTarget();
|
||||||
|
|
||||||
base.OnAttack(state, action, ref error);
|
base.OnAttack(state, action, ref error);
|
||||||
|
|
||||||
// todo: switch based on main weap (also probably move this anim assignment somewhere else)
|
// todo: switch based on main weap (also probably move this anim assignment somewhere else)
|
||||||
action.animation = 0x19001000;
|
action.animation = 0x19001000;
|
||||||
if (error == null)
|
if (error == null)
|
||||||
@ -2171,57 +2200,57 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
// melee attack animation
|
// melee attack animation
|
||||||
//action.animation = 0x19001000;
|
//action.animation = 0x19001000;
|
||||||
}
|
}
|
||||||
var target = state.GetTarget();
|
|
||||||
if (target is BattleNpc)
|
if (target is BattleNpc)
|
||||||
{
|
{
|
||||||
((BattleNpc)target).hateContainer.UpdateHate(this, action.amount);
|
((BattleNpc)target).hateContainer.UpdateHate(this, action.enmity);
|
||||||
}
|
}
|
||||||
|
|
||||||
LuaEngine.GetInstance().OnSignal("playerAttack");
|
LuaEngine.GetInstance().OnSignal("playerAttack");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnCast(State state, BattleAction[] actions, ref BattleAction[] errors)
|
public override void OnCast(State state, BattleAction[] actions, BattleCommand spell, ref BattleAction[] errors)
|
||||||
{
|
{
|
||||||
// todo: update hotbar timers to skill's recast time (also needs to be done on class change or equip crap)
|
// todo: update hotbar timers to skill's recast time (also needs to be done on class change or equip crap)
|
||||||
base.OnCast(state, actions, ref errors);
|
base.OnCast(state, actions, spell, ref errors);
|
||||||
|
|
||||||
var spell = ((MagicState)state).GetSpell();
|
|
||||||
// todo: should just make a thing that updates the one slot cause this is dumb as hell
|
// todo: should just make a thing that updates the one slot cause this is dumb as hell
|
||||||
UpdateHotbarTimer(spell.id, spell.recastTimeSeconds);
|
UpdateHotbarTimer(spell.id, spell.recastTimeMs);
|
||||||
LuaEngine.GetInstance().OnSignal("spellUse");
|
//LuaEngine.GetInstance().OnSignal("spellUse");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnWeaponSkill(State state, BattleAction[] actions, ref BattleAction[] errors)
|
public override void OnWeaponSkill(State state, BattleAction[] actions, BattleCommand skill, ref BattleAction[] errors)
|
||||||
{
|
{
|
||||||
// todo: update hotbar timers to skill's recast time (also needs to be done on class change or equip crap)
|
// todo: update hotbar timers to skill's recast time (also needs to be done on class change or equip crap)
|
||||||
base.OnWeaponSkill(state, actions, ref errors);
|
base.OnWeaponSkill(state, actions, skill, ref errors);
|
||||||
var skill = ((WeaponSkillState)state).GetWeaponSkill();
|
|
||||||
// todo: should just make a thing that updates the one slot cause this is dumb as hell
|
// todo: should just make a thing that updates the one slot cause this is dumb as hell
|
||||||
UpdateHotbarTimer(skill.id, skill.recastTimeSeconds);
|
UpdateHotbarTimer(skill.id, skill.recastTimeMs);
|
||||||
// todo: this really shouldnt be called on each ws?
|
// todo: this really shouldnt be called on each ws?
|
||||||
lua.LuaEngine.CallLuaBattleFunction(this, "onWeaponSkill", this, state.GetTarget(), skill);
|
lua.LuaEngine.CallLuaBattleFunction(this, "onWeaponSkill", this, state.GetTarget(), skill);
|
||||||
LuaEngine.GetInstance().OnSignal("weaponskillUse");
|
LuaEngine.GetInstance().OnSignal("weaponskillUse");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnAbility(State state, BattleAction[] actions, ref BattleAction[] errors)
|
public override void OnAbility(State state, BattleAction[] actions, BattleCommand ability, ref BattleAction[] errors)
|
||||||
{
|
{
|
||||||
base.OnAbility(state, actions, ref errors);
|
base.OnAbility(state, actions, ability, ref errors);
|
||||||
|
UpdateHotbarTimer(ability.id, ability.recastTimeMs);
|
||||||
LuaEngine.GetInstance().OnSignal("abilityUse");
|
LuaEngine.GetInstance().OnSignal("abilityUse");
|
||||||
}
|
}
|
||||||
|
|
||||||
//Handles exp being added, does not handle figuring out exp bonus from buffs or skill/link chains or any of that
|
//Handles exp being added, does not handle figuring out exp bonus from buffs or skill/link chains or any of that
|
||||||
public void AddExp(int exp, byte classId, int bonusPercent = 0)
|
//Returns BattleActions that can be sent to display the EXP gained number and level ups
|
||||||
{
|
public List<BattleAction> AddExp(int exp, byte classId, byte bonusPercent = 0)
|
||||||
|
{
|
||||||
|
List<BattleAction> actionList = new List<BattleAction>();
|
||||||
exp += (int) Math.Ceiling((exp * bonusPercent / 100.0f));
|
exp += (int) Math.Ceiling((exp * bonusPercent / 100.0f));
|
||||||
//You earn [exp](+[bonusPercent]%) experience point(s).
|
|
||||||
SendGameMessage(this, Server.GetWorldManager().GetActor(), 33934, 0x44, this, 0, 0, 0, 0, 0, 0, 0, 0, 0, exp, "", bonusPercent);
|
//33935: You earn [exp] (+[bonusPercent]%) experience points.
|
||||||
|
actionList.Add(new BattleAction(actorId, 33935, 0, (ushort)exp, bonusPercent));
|
||||||
|
|
||||||
bool leveled = false;
|
bool leveled = false;
|
||||||
int diff = MAXEXP[GetLevel() - 1] - charaWork.battleSave.skillPoint[classId - 1];
|
int diff = MAXEXP[GetLevel() - 1] - charaWork.battleSave.skillPoint[classId - 1];
|
||||||
//While there is enough experience to level up, keep leveling up, unlocking skills and removing experience from exp until we don't have enough to level up
|
//While there is enough experience to level up, keep leveling up, unlocking skills and removing experience from exp until we don't have enough to level up
|
||||||
while (exp >= diff && GetLevel() < charaWork.battleSave.skillLevelCap[classId])
|
while (exp >= diff && GetLevel() < charaWork.battleSave.skillLevelCap[classId])
|
||||||
{
|
{
|
||||||
|
|
||||||
//Level up
|
//Level up
|
||||||
LevelUp(classId);
|
LevelUp(classId);
|
||||||
leveled = true;
|
leveled = true;
|
||||||
@ -2255,9 +2284,12 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
|
|
||||||
QueuePackets(expPropertyPacket.Done());
|
QueuePackets(expPropertyPacket.Done());
|
||||||
Database.SetExp(this, classId, charaWork.battleSave.skillPoint[classId - 1]);
|
Database.SetExp(this, classId, charaWork.battleSave.skillPoint[classId - 1]);
|
||||||
|
|
||||||
|
return actionList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LevelUp(byte classId)
|
//Increaess level of current class and equips new abilities earned at that level
|
||||||
|
public void LevelUp(byte classId, List<BattleAction> actionList = null)
|
||||||
{
|
{
|
||||||
if (charaWork.battleSave.skillLevel[classId - 1] < charaWork.battleSave.skillLevelCap[classId])
|
if (charaWork.battleSave.skillLevel[classId - 1] < charaWork.battleSave.skillLevelCap[classId])
|
||||||
{
|
{
|
||||||
@ -2265,8 +2297,10 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
charaWork.battleSave.skillLevel[classId - 1]++;
|
charaWork.battleSave.skillLevel[classId - 1]++;
|
||||||
charaWork.parameterSave.state_mainSkillLevel++;
|
charaWork.parameterSave.state_mainSkillLevel++;
|
||||||
|
|
||||||
SendGameMessage(this, Server.GetWorldManager().GetActor(), 33909, 0x44, this, 0, 0, 0, 0, 0, 0, 0, 0, 0, (int) GetLevel());
|
//33909: You gain level [level]
|
||||||
//If there's an ability that unlocks at this level, equip it.
|
actionList?.Add(new BattleAction(actorId, 33909, 0, (ushort) charaWork.battleSave.skillLevel[classId - 1]));
|
||||||
|
|
||||||
|
//If there's any abilites that unlocks at this level, equip them.
|
||||||
List<uint> commandIds = Server.GetWorldManager().GetBattleCommandIdByLevel(classId, GetLevel());
|
List<uint> commandIds = Server.GetWorldManager().GetBattleCommandIdByLevel(classId, GetLevel());
|
||||||
foreach(uint commandId in commandIds)
|
foreach(uint commandId in commandIds)
|
||||||
{
|
{
|
||||||
@ -2274,6 +2308,9 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
byte jobId = ConvertClassIdToJobId(classId);
|
byte jobId = ConvertClassIdToJobId(classId);
|
||||||
if (jobId != classId)
|
if (jobId != classId)
|
||||||
EquipAbilityInFirstOpenSlot(jobId, commandId, false);
|
EquipAbilityInFirstOpenSlot(jobId, commandId, false);
|
||||||
|
|
||||||
|
//33926: You learn [command].
|
||||||
|
actionList?.Add(new BattleAction(actorId, 33926, commandId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2307,6 +2344,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
currentJob = jobId;
|
currentJob = jobId;
|
||||||
BroadcastPacket(SetCurrentJobPacket.BuildPacket(actorId, jobId), true);
|
BroadcastPacket(SetCurrentJobPacket.BuildPacket(actorId, jobId), true);
|
||||||
Database.LoadHotbar(this);
|
Database.LoadHotbar(this);
|
||||||
|
SendCharaExpInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Gets the id of the player's current job. If they aren't a job, gets the id of their class
|
//Gets the id of the player's current job. If they aren't a job, gets the id of their class
|
||||||
@ -2328,5 +2366,107 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||||||
updateFlags |= ActorUpdateFlags.HpTpMp;
|
updateFlags |= ActorUpdateFlags.HpTpMp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetCombos(int comboId1 = 0, int comboId2 = 0)
|
||||||
|
{
|
||||||
|
SetCombos(new int[] { comboId1, comboId2 });
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetCombos(int[] comboIds)
|
||||||
|
{
|
||||||
|
Array.Copy(comboIds, playerWork.comboNextCommandId, 2);
|
||||||
|
|
||||||
|
//If we're starting or continuing a combo chain, add the status effect and combo cost bonus
|
||||||
|
if (comboIds[0] != 0)
|
||||||
|
{
|
||||||
|
StatusEffect comboEffect = new StatusEffect(this, (uint) StatusEffectId.Combo, 1, 0, 13);
|
||||||
|
comboEffect.SetOverwritable(1);
|
||||||
|
statusEffects.AddStatusEffect(comboEffect, this, true);
|
||||||
|
playerWork.comboCostBonusRate = 1;
|
||||||
|
}
|
||||||
|
//Otherwise we're ending a combo, remove the status
|
||||||
|
else
|
||||||
|
{
|
||||||
|
statusEffects.RemoveStatusEffect(statusEffects.GetStatusEffectById((uint) StatusEffectId.Combo));
|
||||||
|
playerWork.comboCostBonusRate = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActorPropertyPacketUtil comboPropertyPacket = new ActorPropertyPacketUtil("playerWork/combo", this);
|
||||||
|
comboPropertyPacket.AddProperty($"playerWork.comboCostBonusRate");
|
||||||
|
comboPropertyPacket.AddProperty($"playerWork.comboNextCommandId[{0}]");
|
||||||
|
comboPropertyPacket.AddProperty($"playerWork.comboNextCommandId[{1}]");
|
||||||
|
QueuePackets(comboPropertyPacket.Done());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void CalculateBaseStats()
|
||||||
|
{
|
||||||
|
base.CalculateBaseStats();
|
||||||
|
//Add weapon property mod
|
||||||
|
var equip = GetEquipment();
|
||||||
|
var mainHandItem = equip.GetItemAtSlot(Equipment.SLOT_MAINHAND);
|
||||||
|
var damageAttribute = 0;
|
||||||
|
var attackDelay = 3000;
|
||||||
|
var hitCount = 1;
|
||||||
|
GetAttackDelayMs();
|
||||||
|
if (mainHandItem != null)
|
||||||
|
{
|
||||||
|
var mainHandWeapon = (Server.GetItemGamedata(mainHandItem.itemId) as WeaponItem);
|
||||||
|
damageAttribute = mainHandWeapon.damageAttributeType1;
|
||||||
|
attackDelay = (int) (mainHandWeapon.damageInterval * 1000);
|
||||||
|
hitCount = mainHandWeapon.frequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasShield = equip.GetItemAtSlot(Equipment.SLOT_OFFHAND) != null ? 1 : 0;
|
||||||
|
SetMod((uint)Modifier.HasShield, hasShield);
|
||||||
|
|
||||||
|
SetMod((uint)Modifier.AttackType, damageAttribute);
|
||||||
|
SetMod((uint)Modifier.AttackDelay, attackDelay);
|
||||||
|
SetMod((uint)Modifier.HitCount, hitCount);
|
||||||
|
|
||||||
|
//These stats all correlate in a 3:2 fashion
|
||||||
|
//It seems these stats don't actually increase their respective stats. The magic stats do, however
|
||||||
|
AddMod((uint)Modifier.Attack, (long)(GetMod(Modifier.Strength) * 0.667));
|
||||||
|
AddMod((uint)Modifier.Accuracy, (long)(GetMod(Modifier.Dexterity) * 0.667));
|
||||||
|
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.MagicAccuracy, (long)((float)GetMod(Modifier.Mind) * 0.25));
|
||||||
|
AddMod((uint)Modifier.MagicHeal, (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));
|
||||||
|
|
||||||
|
//VIT correlates to HP in a 1:1 fashion
|
||||||
|
AddMod((uint)Modifier.Hp, (long)((float)Modifier.Vitality));
|
||||||
|
|
||||||
|
CalculateTraitMods();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasTrait(ushort id)
|
||||||
|
{
|
||||||
|
BattleTrait trait = Server.GetWorldManager().GetBattleTrait(id);
|
||||||
|
|
||||||
|
return HasTrait(trait);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasTrait(BattleTrait trait)
|
||||||
|
{
|
||||||
|
return (trait != null) && (trait.job == GetClass()) && (trait.level <= GetLevel());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CalculateTraitMods()
|
||||||
|
{
|
||||||
|
var traitIds = Server.GetWorldManager().GetAllBattleTraitIdsForClass((byte) GetClass());
|
||||||
|
|
||||||
|
foreach(var traitId in traitIds)
|
||||||
|
{
|
||||||
|
var trait = Server.GetWorldManager().GetBattleTrait(traitId);
|
||||||
|
if(HasTrait(trait))
|
||||||
|
{
|
||||||
|
AddMod(trait.modifier, trait.bonus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,9 @@ namespace FFXIVClassic_Map_Server.actors.group
|
|||||||
public MonsterParty(ulong groupIndex, uint[] initialMonsterMembers)
|
public MonsterParty(ulong groupIndex, uint[] initialMonsterMembers)
|
||||||
: base(groupIndex)
|
: base(groupIndex)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < initialMonsterMembers.Length; i++)
|
if(initialMonsterMembers != null)
|
||||||
monsterMembers.Add(initialMonsterMembers[i]);
|
for (int i = 0; i < initialMonsterMembers.Length; i++)
|
||||||
|
monsterMembers.Add(initialMonsterMembers[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddMember(uint memberId)
|
public void AddMember(uint memberId)
|
||||||
@ -47,7 +48,7 @@ namespace FFXIVClassic_Map_Server.actors.group
|
|||||||
|
|
||||||
public override void SendInitWorkValues(Session session)
|
public override void SendInitWorkValues(Session session)
|
||||||
{
|
{
|
||||||
SynchGroupWorkValuesPacket groupWork = new SynchGroupWorkValuesPacket(groupIndex);
|
SynchGroupWorkValuesPacket groupWork = new SynchGroupWorkValuesPacket(groupIndex);
|
||||||
groupWork.setTarget("/_init");
|
groupWork.setTarget("/_init");
|
||||||
|
|
||||||
SubPacket test = groupWork.buildPacket(session.id, session.id);
|
SubPacket test = groupWork.buildPacket(session.id, session.id);
|
||||||
|
@ -81,6 +81,8 @@ namespace FFXIVClassic_Map_Server.actors.group
|
|||||||
{
|
{
|
||||||
members.Remove(memberId);
|
members.Remove(memberId);
|
||||||
SendGroupPacketsAll(members);
|
SendGroupPacketsAll(members);
|
||||||
|
if (members.Count == 0)
|
||||||
|
Server.GetWorldManager().NoMembersInParty(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -479,7 +479,7 @@ namespace FFXIVClassic_Map_Server.dataobjects
|
|||||||
public readonly short craftMagicProcessing;
|
public readonly short craftMagicProcessing;
|
||||||
public readonly short harvestPotency;
|
public readonly short harvestPotency;
|
||||||
public readonly short harvestLimit;
|
public readonly short harvestLimit;
|
||||||
public readonly byte frequency;
|
public readonly byte frequency; // hit count, 2 for h2h weapons
|
||||||
public readonly short rate;
|
public readonly short rate;
|
||||||
public readonly short magicRate;
|
public readonly short magicRate;
|
||||||
public readonly short craftProcessControl;
|
public readonly short craftProcessControl;
|
||||||
@ -488,7 +488,7 @@ namespace FFXIVClassic_Map_Server.dataobjects
|
|||||||
public readonly short magicCritical;
|
public readonly short magicCritical;
|
||||||
public readonly short parry;
|
public readonly short parry;
|
||||||
|
|
||||||
public readonly int damageAttributeType1;
|
public readonly int damageAttributeType1; // 1 slashing, 2 piercing, 3 blunt, 4 projectile
|
||||||
public readonly float damageAttributeValue1;
|
public readonly float damageAttributeValue1;
|
||||||
public readonly int damageAttributeType2;
|
public readonly int damageAttributeType2;
|
||||||
public readonly float damageAttributeValue2;
|
public readonly float damageAttributeValue2;
|
||||||
@ -498,6 +498,7 @@ namespace FFXIVClassic_Map_Server.dataobjects
|
|||||||
public readonly short damagePower;
|
public readonly short damagePower;
|
||||||
public readonly float damageInterval;
|
public readonly float damageInterval;
|
||||||
public readonly short ammoVirtualDamagePower;
|
public readonly short ammoVirtualDamagePower;
|
||||||
|
public readonly float dps;
|
||||||
|
|
||||||
public WeaponItem(MySqlDataReader reader)
|
public WeaponItem(MySqlDataReader reader)
|
||||||
: base(reader)
|
: base(reader)
|
||||||
@ -534,6 +535,7 @@ namespace FFXIVClassic_Map_Server.dataobjects
|
|||||||
damagePower = reader.GetInt16("damagePower");
|
damagePower = reader.GetInt16("damagePower");
|
||||||
damageInterval = reader.GetFloat("damageInterval");
|
damageInterval = reader.GetFloat("damageInterval");
|
||||||
ammoVirtualDamagePower = reader.GetInt16("ammoVirtualDamagePower");
|
ammoVirtualDamagePower = reader.GetInt16("ammoVirtualDamagePower");
|
||||||
|
dps = (damagePower + ammoVirtualDamagePower) / damageInterval;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ namespace FFXIVClassic_Map_Server.dataobjects
|
|||||||
{
|
{
|
||||||
QueuePacket(RemoveActorPacket.BuildPacket(actorInstanceList[i].actorId));
|
QueuePacket(RemoveActorPacket.BuildPacket(actorInstanceList[i].actorId));
|
||||||
actorInstanceList.RemoveAt(i);
|
actorInstanceList.RemoveAt(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Add new actors or move
|
//Add new actors or move
|
||||||
@ -123,7 +123,7 @@ namespace FFXIVClassic_Map_Server.dataobjects
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
QueuePacket(actor.GetSpawnPackets(playerActor, 1));
|
QueuePacket(actor.GetSpawnPackets(playerActor, 1));
|
||||||
|
|
||||||
QueuePacket(actor.GetInitPackets());
|
QueuePacket(actor.GetInitPackets());
|
||||||
QueuePacket(actor.GetSetEventStatusPackets());
|
QueuePacket(actor.GetSetEventStatusPackets());
|
||||||
|
@ -19,7 +19,6 @@ namespace FFXIVClassic_Map_Server.dataobjects
|
|||||||
|
|
||||||
public void QueuePacket(SubPacket subpacket)
|
public void QueuePacket(SubPacket subpacket)
|
||||||
{
|
{
|
||||||
//Temporary fix for r0
|
|
||||||
if(SendPacketQueue.Count == 1000)
|
if(SendPacketQueue.Count == 1000)
|
||||||
FlushQueuedSendPackets();
|
FlushQueuedSendPackets();
|
||||||
|
|
||||||
|
@ -176,8 +176,7 @@ namespace FFXIVClassic_Map_Server.lua
|
|||||||
public static int CallLuaStatusEffectFunction(Character actor, StatusEffect effect, string functionName, params object[] args)
|
public static int CallLuaStatusEffectFunction(Character actor, StatusEffect effect, string functionName, params object[] args)
|
||||||
{
|
{
|
||||||
// todo: this is stupid, load the actual effect name from db table
|
// todo: this is stupid, load the actual effect name from db table
|
||||||
var name = ((StatusEffectId)effect.GetStatusEffectId()).ToString().ToLower();
|
string path = $"./scripts/effects/{effect.GetName()}.lua";
|
||||||
string path = $"./scripts/effects/{name}.lua";
|
|
||||||
|
|
||||||
if (File.Exists(path))
|
if (File.Exists(path))
|
||||||
{
|
{
|
||||||
@ -247,7 +246,7 @@ namespace FFXIVClassic_Map_Server.lua
|
|||||||
Program.Log.Error($"LuaEngine.CallLuaBattleCommandFunction [{functionName}] {e.Message}");
|
Program.Log.Error($"LuaEngine.CallLuaBattleCommandFunction [{functionName}] {e.Message}");
|
||||||
}
|
}
|
||||||
DynValue res = new DynValue();
|
DynValue res = new DynValue();
|
||||||
DynValue r = script.Globals.Get(functionName);
|
// DynValue r = script.Globals.Get(functionName);
|
||||||
|
|
||||||
if (!script.Globals.Get(functionName).IsNil())
|
if (!script.Globals.Get(functionName).IsNil())
|
||||||
{
|
{
|
||||||
@ -259,6 +258,82 @@ namespace FFXIVClassic_Map_Server.lua
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void LoadBattleCommandScript(BattleCommand command, string folder)
|
||||||
|
{
|
||||||
|
string path = $"./scripts/commands/{folder}/{command.name}.lua";
|
||||||
|
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
var script = LoadGlobals();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
script.DoFile(path);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Program.Log.Error($"LuaEngine.CallLuaBattleCommandFunction {e.Message}");
|
||||||
|
}
|
||||||
|
command.script = script;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
path = $"./scripts/commands/{folder}/default.lua";
|
||||||
|
//Program.Log.Error($"LuaEngine.CallLuaBattleCommandFunction [{command.name}] Unable to find script {path}");
|
||||||
|
var script = LoadGlobals();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
script.DoFile(path);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Program.Log.Error($"LuaEngine.CallLuaBattleCommandFunction {e.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
command.script = script;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LoadStatusEffectScript(StatusEffect effect)
|
||||||
|
{
|
||||||
|
string path = $"./scripts/effects/{effect.GetName()}.lua";
|
||||||
|
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
var script = LoadGlobals();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
script.DoFile(path);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Program.Log.Error($"LuaEngine.CallLuaBattleCommandFunction {e.Message}");
|
||||||
|
}
|
||||||
|
effect.script = script;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
path = $"./scripts/effects/default.lua";
|
||||||
|
//Program.Log.Error($"LuaEngine.CallLuaBattleCommandFunction [{command.name}] Unable to find script {path}");
|
||||||
|
var script = LoadGlobals();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
script.DoFile(path);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Program.Log.Error($"LuaEngine.CallLuaBattleCommandFunction {e.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
effect.script = script;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static string GetScriptPath(Actor target)
|
public static string GetScriptPath(Actor target)
|
||||||
{
|
{
|
||||||
if (target is Player)
|
if (target is Player)
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
using FFXIVClassic.Common;
|
using FFXIVClassic.Common;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using FFXIVClassic_Map_Server.actors.chara.ai;
|
||||||
|
using FFXIVClassic_Map_Server.actors.chara.ai.utils;
|
||||||
|
using FFXIVClassic_Map_Server.Actors;
|
||||||
|
using FFXIVClassic_Map_Server.packets.send.actor.battle;
|
||||||
|
|
||||||
namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
||||||
{
|
{
|
||||||
@ -9,13 +14,20 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
|||||||
{
|
{
|
||||||
//All HitEffects have the last byte 0x8
|
//All HitEffects have the last byte 0x8
|
||||||
HitEffectType = 8 << 24,
|
HitEffectType = 8 << 24,
|
||||||
|
//Status effects use 32 << 24
|
||||||
|
StatusEffectType = 32 << 24,
|
||||||
|
//Magic effects use 48 << 24
|
||||||
|
MagicEffectType = 48 << 24,
|
||||||
|
|
||||||
//Not setting RecoilLv2 or RecoilLv3 results in the weaker RecoilLv1.
|
//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.
|
//These are the recoil animations that play on the target, ranging from weak to strong.
|
||||||
//The recoil that gets set was likely based on the percentage of HP lost from the attack.
|
//The recoil that gets set was likely based on the percentage of HP lost from the attack.
|
||||||
RecoilLv1 = 0 | HitEffectType,
|
//These also have a visual effect with heals and spells but in reverse. RecoilLv1 has a large effect, Lv3 has none. Crit is very large
|
||||||
RecoilLv2 = 1 << 0 | HitEffectType,
|
//For spells they represent resists. Lv0 is a max resist, Lv3 is no resist. Crit is still used for crits.
|
||||||
RecoilLv3 = 1 << 1 | HitEffectType,
|
//Heals used the same effects sometimes but it isn't clear what for, it seems random? Possibly something like a trait proccing or even just a bug
|
||||||
|
RecoilLv1 = 0,
|
||||||
|
RecoilLv2 = 1 << 0,
|
||||||
|
RecoilLv3 = 1 << 1,
|
||||||
|
|
||||||
//Setting both recoil flags triggers the "Critical!" pop-up text and hit visual effect.
|
//Setting both recoil flags triggers the "Critical!" pop-up text and hit visual effect.
|
||||||
CriticalHit = RecoilLv2 | RecoilLv3,
|
CriticalHit = RecoilLv2 | RecoilLv3,
|
||||||
@ -24,10 +36,19 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
|||||||
//Mixing these flags together will yield different results.
|
//Mixing these flags together will yield different results.
|
||||||
//Each visual likely relates to a specific weapon.
|
//Each visual likely relates to a specific weapon.
|
||||||
//Ex: HitVisual4 flag alone appears to be the visual and sound effect for hand-to-hand attacks.
|
//Ex: HitVisual4 flag alone appears to be the visual and sound effect for hand-to-hand attacks.
|
||||||
HitVisual1 = 1 << 2 | HitEffectType,
|
|
||||||
HitVisual2 = 1 << 3 | HitEffectType,
|
//HitVisual is probably based on attack property.
|
||||||
HitVisual3 = 1 << 4 | HitEffectType,
|
//HitVisual1 is for slashing attacks
|
||||||
HitVisual4 = 1 << 5 | HitEffectType,
|
//HitVisual2 is for piercing attacks
|
||||||
|
//HitVisual1 | Hitvisual2 is for blunt attacks
|
||||||
|
//HitVisual3 is for projectile attacks
|
||||||
|
//Basically take the attack property of a weapon and shift it left 2
|
||||||
|
//For auto attacks attack property is weapon's damageAttributeType1
|
||||||
|
//Still not totally sure how this works with weaponskills or what hitvisual4 or the other combinations are for
|
||||||
|
HitVisual1 = 1 << 2,
|
||||||
|
HitVisual2 = 1 << 3,
|
||||||
|
HitVisual3 = 1 << 4,
|
||||||
|
HitVisual4 = 1 << 5,
|
||||||
|
|
||||||
//An additional visual effect that plays on the target when attacked if:
|
//An additional visual effect that plays on the target when attacked if:
|
||||||
//The attack is physical and they have the protect buff on.
|
//The attack is physical and they have the protect buff on.
|
||||||
@ -40,19 +61,29 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
|||||||
Shell = 1 << 7 | HitEffectType,
|
Shell = 1 << 7 | HitEffectType,
|
||||||
ProtectShellSpecial = Protect | Shell,
|
ProtectShellSpecial = Protect | Shell,
|
||||||
|
|
||||||
//Unknown = 1 << 8, -- Not sure what this flag does.
|
// 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 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.
|
//If no hit effects are set, the "Miss!" pop-up is triggered and no hit visual is played.
|
||||||
HitEffect1 = 1 << 9 | HitEffectType,
|
HitEffect1 = 1 << 9,
|
||||||
HitEffect2 = 1 << 10 | HitEffectType, //Plays the standard hit visual effect, but with no sound if used alone.
|
HitEffect2 = 1 << 10, //Plays the standard hit visual effect, but with no sound if used alone.
|
||||||
Hit = HitEffect1 | HitEffect2, //A standard hit effect with sound effect.
|
HitEffect3 = 1 << 11, //Yellow effect, crit?
|
||||||
HitEffect3 = 1 << 11 | HitEffectType,
|
HitEffect4 = 1 << 12, //Plays the blocking animation
|
||||||
HitEffect4 = 1 << 12 | HitEffectType,
|
HitEffect5 = 1 << 13,
|
||||||
HitEffect5 = 1 << 13 | HitEffectType,
|
|
||||||
GustyHitEffect = HitEffect3 | HitEffect2,
|
GustyHitEffect = HitEffect3 | HitEffect2,
|
||||||
GreenTintedHitEffect = HitEffect4 | HitEffect1,
|
GreenTintedHitEffect = HitEffect4 | HitEffect1,
|
||||||
|
|
||||||
|
//For specific animations
|
||||||
|
Miss = 0,
|
||||||
|
Evade = HitEffect1,
|
||||||
|
Hit = HitEffect1 | HitEffect2,
|
||||||
|
Crit = HitEffect3,
|
||||||
|
Parry = Hit | HitEffect3,
|
||||||
|
Block = HitEffect4,
|
||||||
|
|
||||||
//Knocks you back away from the attacker.
|
//Knocks you back away from the attacker.
|
||||||
KnockbackLv1 = HitEffect4 | HitEffect2 | HitEffect1,
|
KnockbackLv1 = HitEffect4 | HitEffect2 | HitEffect1,
|
||||||
KnockbackLv2 = HitEffect4 | HitEffect3,
|
KnockbackLv2 = HitEffect4 | HitEffect3,
|
||||||
@ -80,10 +111,10 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
|||||||
//A special effect when performing appropriate skill combos in succession.
|
//A special effect when performing appropriate skill combos in succession.
|
||||||
//Ex: Thunder (SkillCombo1 Effect) -> Thundara (SkillCombo2 Effect) -> Thundaga (SkillCombo3 Effect)
|
//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.
|
//Special Note: SkillCombo4 was never actually used in 1.0 since combos only chained up to 3 times maximum.
|
||||||
SkillCombo1 = 1 << 15 | HitEffectType,
|
SkillCombo1 = 1 << 15,
|
||||||
SkillCombo2 = 1 << 16 | HitEffectType,
|
SkillCombo2 = 1 << 16,
|
||||||
SkillCombo3 = SkillCombo1 | SkillCombo2,
|
SkillCombo3 = SkillCombo1 | SkillCombo2,
|
||||||
SkillCombo4 = 1 << 17 | HitEffectType
|
SkillCombo4 = 1 << 17
|
||||||
|
|
||||||
//Flags beyond here are unknown/untested.
|
//Flags beyond here are unknown/untested.
|
||||||
}
|
}
|
||||||
@ -100,28 +131,203 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
|||||||
Left = 1 << 3
|
Left = 1 << 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum HitType : ushort
|
||||||
|
{
|
||||||
|
Miss = 0,
|
||||||
|
Evade = 1,
|
||||||
|
Parry = 2,
|
||||||
|
Block = 3,
|
||||||
|
Resist = 4,
|
||||||
|
Hit = 5,
|
||||||
|
Crit = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
//Type of action
|
||||||
|
public enum ActionType : ushort
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Physical = 1,
|
||||||
|
Magic = 2,
|
||||||
|
Heal = 3,
|
||||||
|
Status = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
//There's are two columns in gamecommand that are for action property and action element respectively and both have percentages next to them
|
||||||
|
//the percentages are for what percent that property or element factors into the attack. Astral and Umbral are always 33% because they are both 3 elments combined
|
||||||
|
//ActionProperty and ActionElement are slightly different. Property defines whta type of attack it is, and 11-13 are used for "sonic, breath, neutral". Neutral is always used for magic
|
||||||
|
//For Element 11-13 are used for astral, umbral, and healing magic.
|
||||||
|
//Right now we aren't actually using these but when things like resists get better defined we'll have to
|
||||||
|
public enum ActionProperty : ushort
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Slashing = 1,
|
||||||
|
Piercing = 2,
|
||||||
|
Blunt = 3,
|
||||||
|
Projectile = 4,
|
||||||
|
|
||||||
|
Fire = 5,
|
||||||
|
Ice = 6,
|
||||||
|
Wind = 7,
|
||||||
|
Earth = 8,
|
||||||
|
Lightning = 9,
|
||||||
|
Water = 10,
|
||||||
|
|
||||||
|
//These I'm not sure about. Check gameCommand.csv
|
||||||
|
Astral = 11,
|
||||||
|
Umbral = 12,
|
||||||
|
Heal = 13
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
public enum ActionProperty : ushort
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Slashing = 1,
|
||||||
|
Piercing = 2,
|
||||||
|
Blunt = 3,
|
||||||
|
Projectile = 4,
|
||||||
|
|
||||||
|
Fire = 5,
|
||||||
|
Ice = 6,
|
||||||
|
Wind = 7,
|
||||||
|
Earth = 8,
|
||||||
|
Lightning = 9,
|
||||||
|
Water = 10,
|
||||||
|
|
||||||
|
Sonic = 11,
|
||||||
|
Breath = 12,
|
||||||
|
Neutral = 13,
|
||||||
|
Astral = 14,
|
||||||
|
Umbral = 15
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ActionElement : ushort
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Slashing = 1,
|
||||||
|
Piercing = 2,
|
||||||
|
Blunt = 3,
|
||||||
|
Projectile = 4,
|
||||||
|
|
||||||
|
Fire = 5,
|
||||||
|
Ice = 6,
|
||||||
|
Wind = 7,
|
||||||
|
Earth = 8,
|
||||||
|
Lightning = 9,
|
||||||
|
Water = 10,
|
||||||
|
|
||||||
|
//These I'm not sure about. Check gameCommand.csv
|
||||||
|
Astral = 11,
|
||||||
|
Umbral = 12,
|
||||||
|
Heal = 13
|
||||||
|
}*/
|
||||||
|
|
||||||
|
|
||||||
class BattleAction
|
class BattleAction
|
||||||
{
|
{
|
||||||
public uint targetId;
|
public uint targetId;
|
||||||
public ushort amount;
|
public ushort amount;
|
||||||
|
public ushort amountMitigated; //Amount that got blocked/evaded or resisted
|
||||||
|
public ushort enmity; //Seperate from amount for abilities that cause a different amount of enmity than damage
|
||||||
public ushort worldMasterTextId;
|
public ushort worldMasterTextId;
|
||||||
public uint effectId;
|
public uint effectId; //Impact effect, damage/heal/status numbers or name
|
||||||
public byte param;
|
public byte param; //Which side the battle action is coming from
|
||||||
public byte unknown;
|
public byte hitNum; //Which hit in a sequence of hits this is
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// this field is not actually part of the packet struct
|
/// these fields are not actually part of the packet struct
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public uint animation;
|
public uint animation;
|
||||||
|
public CommandType commandType; //What type of command was used (ie weaponskill, ability, etc)
|
||||||
|
public ActionProperty actionProperty; //Damage type of the action
|
||||||
|
public ActionType actionType; //Type of this action (ie physical, magic, heal)
|
||||||
|
public HitType hitType;
|
||||||
|
|
||||||
public BattleAction(uint targetId, ushort worldMasterTextId, uint effectId, ushort amount = 0, byte param = 0, byte unknown = 1)
|
//Rates, I'm not sure if these need to be stored like this but with the way some buffs work maybe they do?
|
||||||
|
//Makes things like Blindside easy at least.
|
||||||
|
public double parryRate = 0.0;
|
||||||
|
public double blockRate = 0.0;
|
||||||
|
public double resistRate = 0.0;
|
||||||
|
public double hitRate = 0.0;
|
||||||
|
public double critRate = 0.0;
|
||||||
|
|
||||||
|
public BattleAction(uint targetId, ushort worldMasterTextId, uint effectId, ushort amount = 0, byte param = 0, byte hitNum = 1)
|
||||||
{
|
{
|
||||||
this.targetId = targetId;
|
this.targetId = targetId;
|
||||||
this.worldMasterTextId = worldMasterTextId;
|
this.worldMasterTextId = worldMasterTextId;
|
||||||
this.effectId = effectId;
|
this.effectId = effectId;
|
||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
this.param = param;
|
this.param = param;
|
||||||
this.unknown = unknown;
|
this.hitNum = hitNum;
|
||||||
|
this.hitType = HitType.Hit;
|
||||||
|
this.enmity = amount;
|
||||||
|
this.commandType = (byte) CommandType.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BattleAction(uint targetId, BattleCommand command, byte param = 0, byte hitNum = 1)
|
||||||
|
{
|
||||||
|
this.targetId = targetId;
|
||||||
|
this.worldMasterTextId = command.worldMasterTextId;
|
||||||
|
this.param = param;
|
||||||
|
this.hitNum = hitNum;
|
||||||
|
this.commandType = command.commandType;
|
||||||
|
this.actionProperty = command.actionProperty;
|
||||||
|
this.actionType = command.actionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Order of what (probably) happens when a skill is used:
|
||||||
|
//Buffs that alter things like recast times or that only happen once per skill usage like Power Surge are activated
|
||||||
|
//Script calculates damage and handles any special requirements
|
||||||
|
//Rates are calculated
|
||||||
|
//Buffs that impact indiviudal hits like Blindside or Blood for Blood are activated
|
||||||
|
//The final hit type is determined
|
||||||
|
//Stoneskin takes damage
|
||||||
|
//Final damage amount is calculated using the hit type and defender's stats
|
||||||
|
//Buffs that activate or respond to damage like Rampage. Stoneskin gets removed AFTER damage if it falls off.
|
||||||
|
//Additional effects that are a part of the skill itself or weapon in case of auto attacks take place like status effects
|
||||||
|
//Certain buffs that alter the whole skill fall off (Resonance, Excruciate)
|
||||||
|
|
||||||
|
public void DoAction(Character caster, Character target, BattleCommand skill, BattleActionContainer battleActions)
|
||||||
|
{
|
||||||
|
//First calculate rates for hit/block/etc
|
||||||
|
CalcRates(caster, target, skill);
|
||||||
|
|
||||||
|
//Next, modify those rates based on preaction buffs
|
||||||
|
//Still not sure how we shouldh andle these
|
||||||
|
PreAction(caster, target, skill, battleActions);
|
||||||
|
|
||||||
|
BattleUtils.DoAction(caster, target, skill, this, battleActions);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Calculate the chance of hitting/critting/etc
|
||||||
|
public void CalcRates(Character caster, Character target, BattleCommand skill)
|
||||||
|
{
|
||||||
|
hitRate = BattleUtils.GetHitRate(caster, target, skill, this);
|
||||||
|
critRate = BattleUtils.GetCritRate(caster, target, skill, this);
|
||||||
|
blockRate = BattleUtils.GetBlockRate(caster, target, skill, this);
|
||||||
|
parryRate = BattleUtils.GetParryRate(caster, target, skill, this);
|
||||||
|
resistRate = BattleUtils.GetResistRate(caster, target, skill, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
//These are buffs that activate before the action hits. Usually they change things like hit or crit rates or damage
|
||||||
|
public void PreAction(Character caster, Character target, BattleCommand skill, BattleActionContainer battleActions)
|
||||||
|
{
|
||||||
|
target.statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnPreactionTarget, "onPreAction", caster, target, skill, this, battleActions);
|
||||||
|
|
||||||
|
caster.statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnPreactionCaster, "onPreAction", caster, target, skill, this, battleActions);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Try and apply a status effect
|
||||||
|
public void TryStatus(Character caster, Character target, BattleCommand skill, BattleActionContainer battleActions, bool isAdditional = true)
|
||||||
|
{
|
||||||
|
BattleUtils.TryStatus(caster, target, skill, this, battleActions, isAdditional);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ushort GetHitType()
|
||||||
|
{
|
||||||
|
return (ushort)hitType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,76 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
||||||
|
{
|
||||||
|
class BattleActionContainer
|
||||||
|
{
|
||||||
|
private List<BattleAction> actionsList = new List<BattleAction>();
|
||||||
|
|
||||||
|
//EXP messages are always the last mesages in battlea ction packets, so they get appended after all the rest of the actions are done.
|
||||||
|
private List<BattleAction> expActionList = new List<BattleAction>();
|
||||||
|
|
||||||
|
public BattleActionContainer()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddAction(uint targetId, ushort worldMasterTextId, uint effectId, ushort amount = 0, byte param = 0, byte hitNum = 0)
|
||||||
|
{
|
||||||
|
AddAction(new BattleAction(targetId, worldMasterTextId, effectId, amount, param, hitNum));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Just to make scripting simpler
|
||||||
|
public void AddMPAction(uint targetId, ushort worldMasterTextId, ushort amount)
|
||||||
|
{
|
||||||
|
uint effectId = (uint) (HitEffect.MagicEffectType | HitEffect.MP | HitEffect.Heal);
|
||||||
|
AddAction(targetId, worldMasterTextId, effectId, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddHPAction(uint targetId, ushort worldMasterTextId, ushort amount)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
AddAction(targetId, worldMasterTextId, effectId, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddAction(BattleAction action)
|
||||||
|
{
|
||||||
|
if (action != null)
|
||||||
|
actionsList.Add(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddActions(List<BattleAction> actions)
|
||||||
|
{
|
||||||
|
actionsList.AddRange(actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddEXPAction(BattleAction action)
|
||||||
|
{
|
||||||
|
expActionList.Add(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddEXPActions(List<BattleAction> actionList)
|
||||||
|
{
|
||||||
|
expActionList.AddRange(actionList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CombineLists()
|
||||||
|
{
|
||||||
|
actionsList.AddRange(expActionList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<BattleAction> GetList()
|
||||||
|
{
|
||||||
|
return actionsList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
||||||
{
|
{
|
||||||
// see xtx_command
|
// see xtx_command
|
||||||
enum BattleActionX01PacketCommand : ushort
|
enum BattleActionX01PacketCommand : ushort
|
||||||
@ -25,7 +25,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
|||||||
using (BinaryWriter binWriter = new BinaryWriter(mem))
|
using (BinaryWriter binWriter = new BinaryWriter(mem))
|
||||||
{
|
{
|
||||||
binWriter.Write((UInt32)sourceActorId);
|
binWriter.Write((UInt32)sourceActorId);
|
||||||
|
|
||||||
binWriter.Write((UInt32)animationId);
|
binWriter.Write((UInt32)animationId);
|
||||||
|
|
||||||
//Missing... last value is float, string in here as well?
|
//Missing... last value is float, string in here as well?
|
||||||
@ -33,7 +33,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
|||||||
binWriter.Seek(0x20, SeekOrigin.Begin);
|
binWriter.Seek(0x20, SeekOrigin.Begin);
|
||||||
binWriter.Write((UInt32)1); //Num actions (always 1 for this)
|
binWriter.Write((UInt32)1); //Num actions (always 1 for this)
|
||||||
binWriter.Write((UInt16)commandId);
|
binWriter.Write((UInt16)commandId);
|
||||||
binWriter.Write((UInt16)0x810); //?
|
binWriter.Write((UInt16)0x801); //?
|
||||||
|
|
||||||
binWriter.Write((UInt32)action.targetId);
|
binWriter.Write((UInt32)action.targetId);
|
||||||
|
|
||||||
@ -41,13 +41,12 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
|||||||
binWriter.Write((UInt16)action.worldMasterTextId);
|
binWriter.Write((UInt16)action.worldMasterTextId);
|
||||||
|
|
||||||
binWriter.Write((UInt32)action.effectId);
|
binWriter.Write((UInt32)action.effectId);
|
||||||
|
|
||||||
binWriter.Write((Byte)action.param);
|
binWriter.Write((Byte)action.param);
|
||||||
binWriter.Write((Byte)1); //?
|
binWriter.Write((Byte)1); //?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
new SubPacket(OPCODE, sourceActorId, data).DebugPrintSubPacket();
|
||||||
return new SubPacket(OPCODE, sourceActorId, data);
|
return new SubPacket(OPCODE, sourceActorId, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -57,7 +57,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
|||||||
|
|
||||||
binWriter.Seek(0xAA, SeekOrigin.Begin);
|
binWriter.Seek(0xAA, SeekOrigin.Begin);
|
||||||
for (int i = 0; i < max; i++)
|
for (int i = 0; i < max; i++)
|
||||||
binWriter.Write((Byte)actionList[listOffset + i].unknown);
|
binWriter.Write((Byte)actionList[listOffset + i].hitNum);
|
||||||
|
|
||||||
listOffset += max;
|
listOffset += max;
|
||||||
}
|
}
|
||||||
@ -104,7 +104,9 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
|||||||
|
|
||||||
binWriter.Seek(0x78, SeekOrigin.Begin);
|
binWriter.Seek(0x78, SeekOrigin.Begin);
|
||||||
for (int i = 0; i < max; i++)
|
for (int i = 0; i < max; i++)
|
||||||
|
{
|
||||||
binWriter.Write((UInt32)actionList[listOffset + i].effectId);
|
binWriter.Write((UInt32)actionList[listOffset + i].effectId);
|
||||||
|
}
|
||||||
|
|
||||||
binWriter.Seek(0xA0, SeekOrigin.Begin);
|
binWriter.Seek(0xA0, SeekOrigin.Begin);
|
||||||
for (int i = 0; i < max; i++)
|
for (int i = 0; i < max; i++)
|
||||||
@ -112,7 +114,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
|||||||
|
|
||||||
binWriter.Seek(0xAA, SeekOrigin.Begin);
|
binWriter.Seek(0xAA, SeekOrigin.Begin);
|
||||||
for (int i = 0; i < max; i++)
|
for (int i = 0; i < max; i++)
|
||||||
binWriter.Write((Byte)actionList[listOffset + i].unknown);
|
binWriter.Write((Byte)actionList[listOffset + i].hitNum);
|
||||||
|
|
||||||
listOffset += max;
|
listOffset += max;
|
||||||
}
|
}
|
||||||
|
@ -31,9 +31,9 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
|||||||
//Missing... last value is float, string in here as well?
|
//Missing... last value is float, string in here as well?
|
||||||
|
|
||||||
binWriter.Seek(0x20, SeekOrigin.Begin);
|
binWriter.Seek(0x20, SeekOrigin.Begin);
|
||||||
binWriter.Write((UInt32)actionList.Length); //Num actions
|
binWriter.Write((UInt32)max); //Num actions
|
||||||
binWriter.Write((UInt16)commandId);
|
binWriter.Write((UInt16)commandId);
|
||||||
binWriter.Write((UInt16)0x810); //?
|
binWriter.Write((UInt16)0x818); //?
|
||||||
|
|
||||||
binWriter.Seek(0x28, SeekOrigin.Begin);
|
binWriter.Seek(0x28, SeekOrigin.Begin);
|
||||||
for (int i = 0; i < max; i++)
|
for (int i = 0; i < max; i++)
|
||||||
@ -57,7 +57,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
|||||||
|
|
||||||
binWriter.Seek(0x112, SeekOrigin.Begin);
|
binWriter.Seek(0x112, SeekOrigin.Begin);
|
||||||
for (int i = 0; i < max; i++)
|
for (int i = 0; i < max; i++)
|
||||||
binWriter.Write((Byte)actionList[listOffset + i].unknown);
|
binWriter.Write((Byte)actionList[listOffset + i].hitNum);
|
||||||
|
|
||||||
listOffset += max;
|
listOffset += max;
|
||||||
}
|
}
|
||||||
@ -88,31 +88,31 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
|||||||
binWriter.Seek(0x20, SeekOrigin.Begin);
|
binWriter.Seek(0x20, SeekOrigin.Begin);
|
||||||
binWriter.Write((UInt32)max); //Num actions
|
binWriter.Write((UInt32)max); //Num actions
|
||||||
binWriter.Write((UInt16)commandId);
|
binWriter.Write((UInt16)commandId);
|
||||||
binWriter.Write((UInt16)0x810); //?
|
binWriter.Write((UInt16)0x818); //?
|
||||||
|
|
||||||
binWriter.Seek(0x58, SeekOrigin.Begin);
|
binWriter.Seek(0x28, SeekOrigin.Begin);
|
||||||
for (int i = 0; i < max; i++)
|
for (int i = 0; i < max; i++)
|
||||||
binWriter.Write((UInt32)actionList[listOffset + i].targetId);
|
binWriter.Write((UInt32)actionList[listOffset + i].targetId);
|
||||||
|
|
||||||
binWriter.Seek(0xA0, SeekOrigin.Begin);
|
binWriter.Seek(0x70, SeekOrigin.Begin);
|
||||||
for (int i = 0; i < max; i++)
|
for (int i = 0; i < max; i++)
|
||||||
binWriter.Write((UInt16)actionList[listOffset + i].amount);
|
binWriter.Write((UInt16)actionList[listOffset + i].amount);
|
||||||
|
|
||||||
binWriter.Seek(0xC4, SeekOrigin.Begin);
|
binWriter.Seek(0x94, SeekOrigin.Begin);
|
||||||
for (int i = 0; i < max; i++)
|
for (int i = 0; i < max; i++)
|
||||||
binWriter.Write((UInt16)actionList[listOffset + i].worldMasterTextId);
|
binWriter.Write((UInt16)actionList[listOffset + i].worldMasterTextId);
|
||||||
|
|
||||||
binWriter.Seek(0xE8, SeekOrigin.Begin);
|
binWriter.Seek(0xB8, SeekOrigin.Begin);
|
||||||
for (int i = 0; i < max; i++)
|
for (int i = 0; i < max; i++)
|
||||||
binWriter.Write((UInt32)actionList[listOffset + i].effectId);
|
binWriter.Write((UInt32)actionList[listOffset + i].effectId);
|
||||||
|
|
||||||
binWriter.Seek(0x130, SeekOrigin.Begin);
|
binWriter.Seek(0x100, SeekOrigin.Begin);
|
||||||
for (int i = 0; i < max; i++)
|
for (int i = 0; i < max; i++)
|
||||||
binWriter.Write((Byte)actionList[listOffset + i].param);
|
binWriter.Write((Byte)actionList[listOffset + i].param);
|
||||||
|
|
||||||
binWriter.Seek(0x142, SeekOrigin.Begin);
|
binWriter.Seek(0x112, SeekOrigin.Begin);
|
||||||
for (int i = 0; i < max; i++)
|
for (int i = 0; i < max; i++)
|
||||||
binWriter.Write((Byte)actionList[listOffset + i].unknown);
|
binWriter.Write((Byte)actionList[listOffset + i].hitNum);
|
||||||
|
|
||||||
listOffset += max;
|
listOffset += max;
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ namespace FFXIVClassic_Map_Server.packets.send.group
|
|||||||
binWriter.Write(Encoding.ASCII.GetBytes(entry.name), 0, Encoding.ASCII.GetByteCount(entry.name) >= 0x20 ? 0x20 : Encoding.ASCII.GetByteCount(entry.name));
|
binWriter.Write(Encoding.ASCII.GetBytes(entry.name), 0, Encoding.ASCII.GetByteCount(entry.name) >= 0x20 ? 0x20 : Encoding.ASCII.GetByteCount(entry.name));
|
||||||
|
|
||||||
offset++;
|
offset++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
58
data/scripts/ability.lua
Normal file
58
data/scripts/ability.lua
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
-- todo: add enums for status effects in global.lua
|
||||||
|
require("global")
|
||||||
|
require("battleutils")
|
||||||
|
|
||||||
|
--[[
|
||||||
|
statId - see BattleTemp.cs
|
||||||
|
modifier - Modifier.Intelligence, Modifier.Mind (see Modifier.cs)
|
||||||
|
multiplier -
|
||||||
|
]]
|
||||||
|
function HandleHealingSkill(caster, target, skill, action, statId, modifierId, multiplier, baseAmount)
|
||||||
|
potency = potency or 1.0;
|
||||||
|
healAmount = baseAmount;
|
||||||
|
|
||||||
|
-- todo: shit based on mnd
|
||||||
|
local mind = caster.GetMod(Modifier.Mind);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function HandleAttackSkill(caster, target, skill, action, statId, modifierId, multiplier, baseAmount)
|
||||||
|
-- todo: actually handle this
|
||||||
|
damage = baseAmount or math.random(1,10) * 10;
|
||||||
|
|
||||||
|
return damage;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function HandleStoneskin(caster, target, skill, action, statId, modifierId, damage)
|
||||||
|
--[[
|
||||||
|
if target.statusEffects.HasStatusEffect(StatusEffect.Stoneskin) then
|
||||||
|
-- todo: damage reduction
|
||||||
|
return true;
|
||||||
|
end;
|
||||||
|
]]
|
||||||
|
return false;
|
||||||
|
end;
|
||||||
|
|
||||||
|
--For abilities that inflict statuses, like aegis boon or taunt
|
||||||
|
function onStatusAbilityFinish(caster, target, skill, action)
|
||||||
|
--action.CalcHitType(caster, target, skill);
|
||||||
|
action.DoAction(caster, target, skill);
|
||||||
|
action.TryStatus(caster, target, skill, false);
|
||||||
|
|
||||||
|
return action.amount;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAttackAbilityFinish(caster, target, skill, action)
|
||||||
|
local damage = math.random(50, 150);
|
||||||
|
action.amount = damage;
|
||||||
|
action.DoAction(caster, target, skill);
|
||||||
|
|
||||||
|
return action.amount;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onHealAbilityFinish(caster, target, skill, action)
|
||||||
|
local amount = math.random(150, 250);
|
||||||
|
action.amount = amount;
|
||||||
|
action.DoAction(caster, target, skill);
|
||||||
|
action.TryStatus(caster, target, skill, true);
|
||||||
|
return action.amount;
|
||||||
|
end;
|
@ -34,23 +34,27 @@ function allyGlobal.onDespawn(ally)
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--tryAggro serves the same purpose for now, keeping this around just in case
|
|
||||||
function allyGlobal.HelpPlayers(ally, contentGroupCharas, pickRandomTarget)
|
function allyGlobal.HelpPlayers(ally, contentGroupCharas, pickRandomTarget)
|
||||||
|
print("helpPlayers");
|
||||||
if contentGroupCharas and not ally.IsEngaged() then
|
if contentGroupCharas and not ally.IsEngaged() then
|
||||||
|
print("contentGroup exists");
|
||||||
for chara in contentGroupCharas do
|
for chara in contentGroupCharas do
|
||||||
|
print("looping");
|
||||||
if chara then
|
if chara then
|
||||||
-- probably a player, or another ally
|
-- probably a player, or another ally
|
||||||
-- todo: queue support actions, heal, try pull hate off player etc
|
-- todo: queue support actions, heal, try pull hate off player etc
|
||||||
if chara:IsPlayer() then
|
if chara.IsPlayer() then
|
||||||
|
print("chara is a player");
|
||||||
-- do stuff
|
-- do stuff
|
||||||
if not ally.IsEngaged() then
|
if not ally.IsEngaged() then
|
||||||
if chara.IsEngaged() then
|
if chara.IsEngaged() then
|
||||||
allyGlobal.EngageTarget(ally, chara.target, nil);
|
allyGlobal.EngageTarget(ally, chara.target, nil);
|
||||||
break;
|
break;
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
elseif chara.IsMonster() and chara.IsEngaged() then
|
elseif chara.IsMonster() and chara.IsEngaged() then
|
||||||
if not ally.IsEngaged() then
|
if not ally.IsEngaged() then
|
||||||
|
print("Engaging monster that is engaged");
|
||||||
allyGlobal.EngageTarget(ally, chara, nil);
|
allyGlobal.EngageTarget(ally, chara, nil);
|
||||||
break;
|
break;
|
||||||
end
|
end
|
||||||
@ -60,7 +64,6 @@ function allyGlobal.HelpPlayers(ally, contentGroupCharas, pickRandomTarget)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--Iterate over characters in contentGroup, if a player is in combat, assist them.
|
|
||||||
function allyGlobal.tryAggro(ally, contentGroupCharas)
|
function allyGlobal.tryAggro(ally, contentGroupCharas)
|
||||||
local count = 0;
|
local count = 0;
|
||||||
if contentGroupCharas and not ally.IsEngaged() then
|
if contentGroupCharas and not ally.IsEngaged() then
|
||||||
@ -76,14 +79,12 @@ function allyGlobal.tryAggro(ally, contentGroupCharas)
|
|||||||
break;
|
break;
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
|
||||||
elseif contentGroupCharas[i].IsMonster() and contentGroupCharas[i].IsEngaged() then
|
elseif contentGroupCharas[i].IsMonster() and contentGroupCharas[i].IsEngaged() then
|
||||||
if not ally.IsEngaged() then
|
if not ally.IsEngaged() then
|
||||||
print("Engaging monster that is engaged");
|
print("Engaging monster that is engaged");
|
||||||
allyGlobal.EngageTarget(ally, contentGroupCharas[i], nil);
|
allyGlobal.EngageTarget(ally, contentGroupCharas[i], nil);
|
||||||
break;
|
break;
|
||||||
end]]
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -109,6 +110,7 @@ function allyGlobal.EngageTarget(ally, target, contentGroupCharas)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
elseif target then
|
elseif target then
|
||||||
|
print("Engaging");
|
||||||
ally.Engage(target)
|
ally.Engage(target)
|
||||||
ally.hateContainer.AddBaseHate(target);
|
ally.hateContainer.AddBaseHate(target);
|
||||||
end
|
end
|
||||||
|
175
data/scripts/battlenpc.lua
Normal file
175
data/scripts/battlenpc.lua
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
local initClassItems, initRaceItems;
|
||||||
|
|
||||||
|
function onBeginLogin(player)
|
||||||
|
--New character, set the initial quest
|
||||||
|
if (player:GetPlayTime(false) == 0) then
|
||||||
|
initialTown = player:GetInitialTown();
|
||||||
|
|
||||||
|
if (initialTown == 1 and player:HasQuest(110001) == false) then
|
||||||
|
player:AddQuest(110001);
|
||||||
|
player:SetHomePoint(1280001);
|
||||||
|
elseif (initialTown == 2 and player:HasQuest(110005) == false) then
|
||||||
|
player:AddQuest(110005);
|
||||||
|
player:SetHomePoint(1280061);
|
||||||
|
elseif (initialTown == 3 and player:HasQuest(110009) == false) then
|
||||||
|
player:AddQuest(110009);
|
||||||
|
player:SetHomePoint(1280031);
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
--For Opening. Set Director and reset position incase d/c
|
||||||
|
if (player:HasQuest(110001) == true and player:GetZoneID() == 193) then
|
||||||
|
director = player:GetZone():CreateDirector("OpeningDirector", false);
|
||||||
|
player:AddDirector(director);
|
||||||
|
director:StartDirector(true);
|
||||||
|
player:SetLoginDirector(director);
|
||||||
|
player:KickEvent(director, "noticeEvent", true);
|
||||||
|
|
||||||
|
player.positionX = 0.016;
|
||||||
|
player.positionY = 10.35;
|
||||||
|
player.positionZ = -36.91;
|
||||||
|
player.rotation = 0.025;
|
||||||
|
player:GetQuest(110001):ClearQuestData();
|
||||||
|
player:GetQuest(110001):ClearQuestFlags();
|
||||||
|
elseif (player:HasQuest(110005) == true and player:GetZoneID() == 166) then
|
||||||
|
director = player:GetZone():CreateDirector("OpeningDirector", false);
|
||||||
|
player:AddDirector(director);
|
||||||
|
director:StartDirector(false);
|
||||||
|
player:SetLoginDirector(director);
|
||||||
|
player:KickEvent(director, "noticeEvent", true);
|
||||||
|
|
||||||
|
player.positionX = 369.5434;
|
||||||
|
player.positionY = 4.21;
|
||||||
|
player.positionZ = -706.1074;
|
||||||
|
player.rotation = -1.26721;
|
||||||
|
player:GetQuest(110005):ClearQuestData();
|
||||||
|
player:GetQuest(110005):ClearQuestFlags();
|
||||||
|
elseif (player:HasQuest(110009) == true and player:GetZoneID() == 184) then
|
||||||
|
--director = player:GetZone():CreateDirector("OpeningDirector", false);
|
||||||
|
--player:AddDirector(director);
|
||||||
|
--director:StartDirector(false);
|
||||||
|
--player:SetLoginDirector(director);
|
||||||
|
--player:KickEvent(director, "noticeEvent", true);
|
||||||
|
--
|
||||||
|
player.positionX = 5.364327;
|
||||||
|
player.positionY = 196.0;
|
||||||
|
player.positionZ = 133.6561;
|
||||||
|
player.rotation = -2.849384;
|
||||||
|
player:GetQuest(110009):ClearQuestData();
|
||||||
|
player:GetQuest(110009):ClearQuestFlags();
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function onLogin(player)
|
||||||
|
|
||||||
|
if (player:GetPlayTime(false) == 0) then
|
||||||
|
player:SendMessage(0x1D,"",">PlayTime == 0, new player!");
|
||||||
|
|
||||||
|
initClassItems(player);
|
||||||
|
initRaceItems(player);
|
||||||
|
|
||||||
|
player:SavePlayTime();
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function initClassItems(player)
|
||||||
|
|
||||||
|
local slotTable;
|
||||||
|
local invSlotTable;
|
||||||
|
|
||||||
|
--DoW
|
||||||
|
if (player.charaWork.parameterSave.state_mainSkill[0] == 2) then --PUG
|
||||||
|
player:GetInventory(0):AddItem({4020001, 8030701, 8050728, 8080601, 8090307});
|
||||||
|
player:GetEquipment():SetEquipment({0, 10, 12, 14, 15},{0, 1, 2, 3, 4});
|
||||||
|
elseif (player.charaWork.parameterSave.state_mainSkill[0] == 3) then --GLA
|
||||||
|
player:GetInventory(0):AddItem({4030010, 8031120, 8050245, 8080601, 8090307});
|
||||||
|
player:GetEquipment():SetEquipment({0, 10, 12, 14, 15},{0, 1, 2, 3, 4});
|
||||||
|
elseif (player.charaWork.parameterSave.state_mainSkill[0] == 4) then --MRD
|
||||||
|
player:GetInventory(0):AddItem({4040001, 8011001, 8050621, 8070346, 8090307});
|
||||||
|
player:GetEquipment():SetEquipment({0, 8, 12, 13, 15},{0, 1, 2, 3, 4});
|
||||||
|
elseif (player.charaWork.parameterSave.state_mainSkill[0] == 7) then --ARC
|
||||||
|
player:GetInventory(0):AddItem({4070001, 8030601, 8050622, 8080601, 8090307});
|
||||||
|
player:GetEquipment():SetEquipment({0, 10, 12, 14, 15},{0, 1, 2, 3, 4});
|
||||||
|
elseif (player.charaWork.parameterSave.state_mainSkill[0] == 8) then --LNC
|
||||||
|
player:GetInventory(0):AddItem({4080201, 8030801, 8051015, 8080501, 8090307});
|
||||||
|
player:GetEquipment():SetEquipment({0, 10, 12, 14, 15},{0, 1, 2, 3, 4});
|
||||||
|
--DoM
|
||||||
|
elseif (player.charaWork.parameterSave.state_mainSkill[0] == 22) then --THM
|
||||||
|
player:GetInventory(0):AddItem({5020001, 8030245, 8050346, 8080346, 8090208});
|
||||||
|
player:GetEquipment():SetEquipment({0, 10, 12, 14, 15},{0, 1, 2, 3, 4});
|
||||||
|
elseif (player.charaWork.parameterSave.state_mainSkill[0] == 23) then --CNJ
|
||||||
|
player:GetInventory(0):AddItem({5030101, 8030445, 8050031, 8080246, 8090208});
|
||||||
|
player:GetEquipment():SetEquipment({0, 10, 12, 14, 15},{0, 1, 2, 3, 4});
|
||||||
|
|
||||||
|
--DoH
|
||||||
|
elseif (player.charaWork.parameterSave.state_mainSkill[0] == 29) then --
|
||||||
|
elseif (player.charaWork.parameterSave.state_mainSkill[0] == 30) then --
|
||||||
|
elseif (player.charaWork.parameterSave.state_mainSkill[0] == 31) then --
|
||||||
|
elseif (player.charaWork.parameterSave.state_mainSkill[0] == 32) then --
|
||||||
|
elseif (player.charaWork.parameterSave.state_mainSkill[0] == 33) then --
|
||||||
|
elseif (player.charaWork.parameterSave.state_mainSkill[0] == 34) then --
|
||||||
|
elseif (player.charaWork.parameterSave.state_mainSkill[0] == 35) then --
|
||||||
|
elseif (player.charaWork.parameterSave.state_mainSkill[0] == 36) then --
|
||||||
|
|
||||||
|
--DoL
|
||||||
|
elseif (player.charaWork.parameterSave.state_mainSkill[0] == 39) then --MIN
|
||||||
|
elseif (player.charaWork.parameterSave.state_mainSkill[0] == 40) then --BTN
|
||||||
|
elseif (player.charaWork.parameterSave.state_mainSkill[0] == 41) then --FSH
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function initRaceItems(player)
|
||||||
|
|
||||||
|
if (player.playerWork.tribe == 1) then --Hyur Midlander Male
|
||||||
|
player:GetInventory(0):AddItem(8040001);
|
||||||
|
player:GetInventory(0):AddItem(8060001);
|
||||||
|
elseif (player.playerWork.tribe == 2) then --Hyur Midlander Female
|
||||||
|
player:GetInventory(0):AddItem(8040002);
|
||||||
|
player:GetInventory(0):AddItem(8060002);
|
||||||
|
elseif (player.playerWork.tribe == 3) then --Hyur Highlander Male
|
||||||
|
player:GetInventory(0):AddItem(8040003);
|
||||||
|
player:GetInventory(0):AddItem(8060003);
|
||||||
|
elseif (player.playerWork.tribe == 4) then --Elezen Wildwood Male
|
||||||
|
player:GetInventory(0):AddItem(8040004);
|
||||||
|
player:GetInventory(0):AddItem(8060004);
|
||||||
|
elseif (player.playerWork.tribe == 5) then --Elezen Wildwood Female
|
||||||
|
player:GetInventory(0):AddItem(8040006);
|
||||||
|
player:GetInventory(0):AddItem(8060006);
|
||||||
|
elseif (player.playerWork.tribe == 6) then --Elezen Duskwight Male
|
||||||
|
player:GetInventory(0):AddItem(8040005);
|
||||||
|
player:GetInventory(0):AddItem(8060005);
|
||||||
|
elseif (player.playerWork.tribe == 7) then --Elezen Duskwight Female
|
||||||
|
player:GetInventory(0):AddItem(8040007);
|
||||||
|
player:GetInventory(0):AddItem(8060007);
|
||||||
|
elseif (player.playerWork.tribe == 8) then --Lalafell Plainsfolk Male
|
||||||
|
player:GetInventory(0):AddItem(8040008);
|
||||||
|
player:GetInventory(0):AddItem(8060008);
|
||||||
|
elseif (player.playerWork.tribe == 9) then --Lalafell Plainsfolk Female
|
||||||
|
player:GetInventory(0):AddItem(8040010);
|
||||||
|
player:GetInventory(0):AddItem(8060010);
|
||||||
|
elseif (player.playerWork.tribe == 10) then --Lalafell Dunesfolk Male
|
||||||
|
player:GetInventory(0):AddItem(8040009);
|
||||||
|
player:GetInventory(0):AddItem(8060009);
|
||||||
|
elseif (player.playerWork.tribe == 11) then --Lalafell Dunesfolk Female
|
||||||
|
player:GetInventory(0):AddItem(8040011);
|
||||||
|
player:GetInventory(0):AddItem(8060011);
|
||||||
|
elseif (player.playerWork.tribe == 12) then --Miqo'te Seekers of the Sun
|
||||||
|
player:GetInventory(0):AddItem(8040012);
|
||||||
|
player:GetInventory(0):AddItem(8060012);
|
||||||
|
elseif (player.playerWork.tribe == 13) then --Miqo'te Seekers of the Moon
|
||||||
|
player:GetInventory(0):AddItem(8040013);
|
||||||
|
player:GetInventory(0):AddItem(8060013);
|
||||||
|
elseif (player.playerWork.tribe == 14) then --Roegadyn Sea Wolf
|
||||||
|
player:GetInventory(0):AddItem(8040014);
|
||||||
|
player:GetInventory(0):AddItem(8060014);
|
||||||
|
elseif (player.playerWork.tribe == 15) then --Roegadyn Hellsguard
|
||||||
|
player:GetInventory(0):AddItem(8040015);
|
||||||
|
player:GetInventory(0):AddItem(8060015);
|
||||||
|
end
|
||||||
|
|
||||||
|
player:GetEquipment():SetEquipment({9, 11},{5,6});
|
||||||
|
|
||||||
|
end
|
64
data/scripts/battleutils.lua
Normal file
64
data/scripts/battleutils.lua
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
CommandType =
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
AutoAttack = 1,
|
||||||
|
Weaponskill = 2,
|
||||||
|
Ability = 3,
|
||||||
|
Spell = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionType =
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Physical = 1,
|
||||||
|
Magic = 2,
|
||||||
|
Heal = 3,
|
||||||
|
Status = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionProperty =
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Physical = 1,
|
||||||
|
Magic = 2,
|
||||||
|
Heal = 4,
|
||||||
|
Status = 8,
|
||||||
|
Ranged = 16
|
||||||
|
}
|
||||||
|
|
||||||
|
DamageTakenType =
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Attack,
|
||||||
|
Magic,
|
||||||
|
Weaponskill,
|
||||||
|
Ability
|
||||||
|
}
|
||||||
|
|
||||||
|
HitDirection =
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Front = 1,
|
||||||
|
Right = 2,
|
||||||
|
Rear = 4,
|
||||||
|
Left = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
HitType =
|
||||||
|
{
|
||||||
|
Miss = 0,
|
||||||
|
Evade = 1,
|
||||||
|
Parry = 2,
|
||||||
|
Block = 3,
|
||||||
|
Resist = 4,
|
||||||
|
Hit = 5,
|
||||||
|
Crit = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
TargetFindAOEType =
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Circle = 1,
|
||||||
|
Cone = 2,
|
||||||
|
Box = 3
|
||||||
|
}
|
@ -14,15 +14,6 @@ local attackMagicHandlers = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onEventStarted(player, command, triggerName, arg1, arg2, arg3, arg4, targetActor, arg5, arg6, arg7, arg8)
|
function onEventStarted(player, command, triggerName, arg1, arg2, arg3, arg4, targetActor, arg5, arg6, arg7, arg8)
|
||||||
print(command.actorId)
|
|
||||||
--Are they in active mode?
|
|
||||||
if (player:GetState() != 2) then
|
|
||||||
player:SendGameMessage(GetWorldMaster(), 32503, 0x20);
|
|
||||||
player:endEvent();
|
|
||||||
return;
|
|
||||||
end
|
|
||||||
|
|
||||||
player.Ability(command.actorId, targetActor);
|
player.Ability(command.actorId, targetActor);
|
||||||
player:endEvent();
|
player:endEvent();
|
||||||
|
|
||||||
end
|
end
|
5
data/scripts/commands/AbilityCure.lua
Normal file
5
data/scripts/commands/AbilityCure.lua
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
require("global")
|
||||||
|
|
||||||
|
function onEventStarted(player, command, triggerName, arg1, arg2, arg3, arg4, targetActor, arg5, arg6, arg7, arg8)
|
||||||
|
|
||||||
|
end
|
@ -10,9 +10,9 @@ Switches between active and passive mode states
|
|||||||
|
|
||||||
function onEventStarted(player, command, triggerName)
|
function onEventStarted(player, command, triggerName)
|
||||||
|
|
||||||
if (player.newMainState == 0x0000) then
|
if (player.currentMainState == 0x0000) then
|
||||||
player.Engage(0, 0x0002);
|
player.Engage(0, 0x0002);
|
||||||
elseif (player.newMainState == 0x0002) then
|
elseif (player.currentMainState == 0x0002) then
|
||||||
player.Disengage(0x0000);
|
player.Disengage(0x0000);
|
||||||
end
|
end
|
||||||
player:endEvent();
|
player:endEvent();
|
||||||
|
20
data/scripts/commands/AttackAbility.lua
Normal file
20
data/scripts/commands/AttackAbility.lua
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
require ("global")
|
||||||
|
require ("utils")
|
||||||
|
|
||||||
|
--[[
|
||||||
|
|
||||||
|
AttackWeaponSkill Script
|
||||||
|
|
||||||
|
Finds the correct weaponskill subscript to fire when a weaponskill actor is activated.
|
||||||
|
|
||||||
|
--]]
|
||||||
|
|
||||||
|
local attackMagicHandlers = {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEventStarted(player, command, triggerName, arg1, arg2, arg3, arg4, targetActor, arg5, arg6, arg7, arg8)
|
||||||
|
player.Ability(command.actorId, targetActor);
|
||||||
|
player:endEvent();
|
||||||
|
|
||||||
|
end
|
6
data/scripts/commands/ChangeJobCommand.lua
Normal file
6
data/scripts/commands/ChangeJobCommand.lua
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
function onEventStarted(player, caller, commandRequest, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)
|
||||||
|
|
||||||
|
player:SetCurrentJob(17);
|
||||||
|
|
||||||
|
player:EndEvent();
|
||||||
|
end
|
5
data/scripts/commands/CureMagic.lua
Normal file
5
data/scripts/commands/CureMagic.lua
Normal 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
|
5
data/scripts/commands/CuregaMagic.lua
Normal file
5
data/scripts/commands/CuregaMagic.lua
Normal 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
|
26
data/scripts/commands/DevideAttackWeaponSkill.lua
Normal file
26
data/scripts/commands/DevideAttackWeaponSkill.lua
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
require ("global")
|
||||||
|
require ("utils")
|
||||||
|
|
||||||
|
--[[
|
||||||
|
|
||||||
|
AttackWeaponSkill Script
|
||||||
|
|
||||||
|
Finds the correct weaponskill subscript to fire when a weaponskill actor is activated.
|
||||||
|
|
||||||
|
--]]
|
||||||
|
|
||||||
|
function onEventStarted(player, command, triggerName, arg1, arg2, arg3, arg4, targetActor, arg5, arg6, arg7, arg8)
|
||||||
|
|
||||||
|
--Are they in active mode?
|
||||||
|
if (player:GetState() != 2) then
|
||||||
|
player:SendGameMessage(GetWorldMaster(), 32503, 0x20);
|
||||||
|
player:endEvent();
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
if not player.aiContainer.IsEngaged() then
|
||||||
|
player.Engage(targetActor);
|
||||||
|
end;
|
||||||
|
player.WeaponSkill(command.actorId, targetActor);
|
||||||
|
player:endEvent();
|
||||||
|
end;
|
5
data/scripts/commands/EffectMagic.lua
Normal file
5
data/scripts/commands/EffectMagic.lua
Normal 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
|
@ -61,7 +61,7 @@ function onEventStarted(player, equipAbilityWidget, triggername, slot, commandid
|
|||||||
player:SwapAbilities(oldSlot, slot + player.charaWork.commandBorder);
|
player:SwapAbilities(oldSlot, slot + player.charaWork.commandBorder);
|
||||||
else
|
else
|
||||||
local tslot = slot + player.charaWork.commandBorder;
|
local tslot = slot + player.charaWork.commandBorder;
|
||||||
player:EquipAbility(player.GetJob(), commandid, tslot, true);
|
player:EquipAbility(player.GetCurrentClassOrJob(), commandid, tslot, true);
|
||||||
end
|
end
|
||||||
|
|
||||||
--Unequip
|
--Unequip
|
||||||
@ -70,7 +70,7 @@ function onEventStarted(player, equipAbilityWidget, triggername, slot, commandid
|
|||||||
ability = worldManager.GetBattleCommand(commandid);
|
ability = worldManager.GetBattleCommand(commandid);
|
||||||
--Is the ability a part of the player's current class?
|
--Is the ability a part of the player's current class?
|
||||||
--This check isn't correct because of jobs having different ids
|
--This check isn't correct because of jobs having different ids
|
||||||
local classId = player:GetJob();
|
local classId = player:GetCurrentClassOrJob();
|
||||||
local jobId = player:ConvertClassIdToJobId(classId);
|
local jobId = player:ConvertClassIdToJobId(classId);
|
||||||
|
|
||||||
if(ability.job == classId or ability.job == jobId) then
|
if(ability.job == classId or ability.job == jobId) then
|
||||||
|
@ -145,6 +145,12 @@ function equipItem(player, equipSlot, item)
|
|||||||
--Item Equipped message
|
--Item Equipped message
|
||||||
player:SendGameMessage(player, worldMaster, 30601, 0x20, equipSlot+1, item.itemId, item.quality, 0, 0, 1);
|
player:SendGameMessage(player, worldMaster, 30601, 0x20, equipSlot+1, item.itemId, item.quality, 0, 0, 1);
|
||||||
|
|
||||||
|
--Load gearset for new class and begin class change
|
||||||
|
if (classId ~= nil) then
|
||||||
|
loadGearset(player, classId);
|
||||||
|
player:DoClassChange(classId);
|
||||||
|
end
|
||||||
|
|
||||||
player:GetEquipment():Equip(equipSlot, item);
|
player:GetEquipment():Equip(equipSlot, item);
|
||||||
|
|
||||||
if (equipSlot == EQUIPSLOT_MAINHAND and gItem:IsNailWeapon() == false) then graphicSlot = GRAPHICSLOT_MAINHAND;
|
if (equipSlot == EQUIPSLOT_MAINHAND and gItem:IsNailWeapon() == false) then graphicSlot = GRAPHICSLOT_MAINHAND;
|
||||||
@ -170,14 +176,7 @@ function equipItem(player, equipSlot, item)
|
|||||||
elseif (equipSlot == EQUIPSLOT_EARS) then
|
elseif (equipSlot == EQUIPSLOT_EARS) then
|
||||||
player:GraphicChange(GRAPHICSLOT_R_EAR, item);
|
player:GraphicChange(GRAPHICSLOT_R_EAR, item);
|
||||||
player:GraphicChange(GRAPHICSLOT_L_EAR, item);
|
player:GraphicChange(GRAPHICSLOT_L_EAR, item);
|
||||||
end
|
end
|
||||||
|
|
||||||
--Load gearset for new class and begin class change
|
|
||||||
if (classId ~= nil) then
|
|
||||||
loadGearset(player, classId);
|
|
||||||
player:DoClassChange(classId);
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
7
data/scripts/commands/PointSearchAbility.lua
Normal file
7
data/scripts/commands/PointSearchAbility.lua
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
function onEventStarted(player, command, triggerName, arg1, arg2, arg3, arg4, targetActor, arg5, arg6, arg7, arg8)
|
||||||
|
|
||||||
|
|
||||||
|
player.Cast(command.actorId, targetActor);
|
||||||
|
player:endEvent();
|
||||||
|
end
|
15
data/scripts/commands/ShotCommand.lua
Normal file
15
data/scripts/commands/ShotCommand.lua
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
require ("global")
|
||||||
|
require ("utils")
|
||||||
|
|
||||||
|
--[[
|
||||||
|
|
||||||
|
AttackWeaponSkill Script
|
||||||
|
|
||||||
|
Finds the correct weaponskill subscript to fire when a weaponskill actor is activated.
|
||||||
|
|
||||||
|
--]]
|
||||||
|
|
||||||
|
function onEventStarted(player, command, triggerName, arg1, arg2, arg3, arg4, targetActor, arg5, arg6, arg7, arg8)
|
||||||
|
player.Ability(command.actorId, targetActor);
|
||||||
|
player:endEvent();
|
||||||
|
end;
|
19
data/scripts/commands/SongMagic.lua
Normal file
19
data/scripts/commands/SongMagic.lua
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
require ("global")
|
||||||
|
require ("utils")
|
||||||
|
|
||||||
|
--[[
|
||||||
|
|
||||||
|
AttackWeaponSkill Script
|
||||||
|
|
||||||
|
Finds the correct weaponskill subscript to fire when a weaponskill actor is activated.
|
||||||
|
|
||||||
|
--]]
|
||||||
|
|
||||||
|
local attackMagicHandlers = {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEventStarted(player, command, triggerName, arg1, arg2, arg3, arg4, targetActor, arg5, arg6, arg7, arg8)
|
||||||
|
player.Cast(command.actorId, targetActor);
|
||||||
|
player:endEvent();
|
||||||
|
end;
|
18
data/scripts/commands/ability/aegis_boon.lua
Normal file
18
data/scripts/commands/ability/aegis_boon.lua
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, ability)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, ability)
|
||||||
|
--27164: Swift Aegis Boon
|
||||||
|
if caster.HasTrait(27164) then
|
||||||
|
ability.recastTimeMs = ability.recastTimeMs - 15000;
|
||||||
|
end
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onSkillFinish(caster, target, skill, action, actionContainer)
|
||||||
|
action.DoAction(caster, target, skill, actionContainer);
|
||||||
|
end;
|
22
data/scripts/commands/ability/barrage.lua
Normal file
22
data/scripts/commands/ability/barrage.lua
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
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)
|
||||||
|
ability.statusMagnitude = 4;
|
||||||
|
|
||||||
|
--27242: Enhanced Barrage: Adds an additional attack to barrage ( 4 -> 5 )
|
||||||
|
if caster.HasTrait(27242) then
|
||||||
|
ability.statusMagnitude = 5;
|
||||||
|
end
|
||||||
|
|
||||||
|
--DoAction handles rates, buffs, dealing damage
|
||||||
|
action.DoAction(caster, target, skill, actionContainer);
|
||||||
|
end;
|
19
data/scripts/commands/ability/berserk.lua
Normal file
19
data/scripts/commands/ability/berserk.lua
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, ability)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, ability)
|
||||||
|
--27205: Enhanced Berserk: Increases the effect of Berserk by 20%
|
||||||
|
if caster.HasTrait(27205) 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;
|
19
data/scripts/commands/ability/blindside.lua
Normal file
19
data/scripts/commands/ability/blindside.lua
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, ability)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, ability)
|
||||||
|
--27121: Enhanced Blindside
|
||||||
|
if caster.HasTrait(27121) 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;
|
43
data/scripts/commands/ability/blissful_mind.lua
Normal file
43
data/scripts/commands/ability/blissful_mind.lua
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, ability)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, ability)
|
||||||
|
--27362: Enhanced Blissful Mind
|
||||||
|
if caster.HasTrait(27362) then
|
||||||
|
ability.statusTier = 2;
|
||||||
|
end
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onSkillFinish(caster, target, skill, action, actionContainer)
|
||||||
|
--Blissful Mind
|
||||||
|
--223228: Blissful Mind
|
||||||
|
--223242: Fully Blissful Mind
|
||||||
|
local buff = caster.statusEffects.GetStatusEffectById(223228) or caster.statusEffects.GetStatusEffectById(223242);
|
||||||
|
|
||||||
|
--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, 30321, amount);
|
||||||
|
actionContainer.AddAction(remAction);
|
||||||
|
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);
|
||||||
|
skill.statusMagnitude = amount;
|
||||||
|
|
||||||
|
--DoAction handles rates, buffs, dealing damage
|
||||||
|
action.DoAction(caster, target, skill, actionContainer);
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end;
|
24
data/scripts/commands/ability/blood_for_blood.lua
Normal file
24
data/scripts/commands/ability/blood_for_blood.lua
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, ability)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, ability)
|
||||||
|
--27283: Enhanced Blood for Blood: Increases damage dealt to enemies by B4B by 25%
|
||||||
|
if caster.HasTrait(27283) then
|
||||||
|
ability.statusTier = 2;
|
||||||
|
end
|
||||||
|
|
||||||
|
--27284: Swift Blood for Blood: Reduces recast time of B4B by 15 seconds
|
||||||
|
if caster.HasTrait(27284) then
|
||||||
|
ability.recastTimeMs = ability.recastTimeMs - 15000;
|
||||||
|
end
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onSkillFinish(caster, target, skill, action, actionContainer)
|
||||||
|
--DoAction handles rates, buffs, dealing damage
|
||||||
|
action.DoAction(caster, target, skill, actionContainer);
|
||||||
|
end;
|
19
data/scripts/commands/ability/bloodbath.lua
Normal file
19
data/scripts/commands/ability/bloodbath.lua
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, ability)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, ability)
|
||||||
|
--27202: Swift Bloodbath
|
||||||
|
if caster.HasTrait(27202) then
|
||||||
|
ability.recastTimeMs = ability.recastTimeMs - 15000;
|
||||||
|
end
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onSkillFinish(caster, target, skill, action, actionContainer)
|
||||||
|
--DoAction handles rates, buffs, dealing damage
|
||||||
|
action.DoAction(caster, target, skill, actionContainer);
|
||||||
|
end;
|
18
data/scripts/commands/ability/chameleon.lua
Normal file
18
data/scripts/commands/ability/chameleon.lua
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, ability)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, ability)
|
||||||
|
--27245: Swift Chameleon
|
||||||
|
if caster.HasTrait(27245) then
|
||||||
|
ability.recastTimeMs = ability.recastTimeMs - 60000;
|
||||||
|
end
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onSkillFinish(caster, target, skill, action, actionContainer)
|
||||||
|
target.hateContainer.UpdateHate(caster, -840);
|
||||||
|
end;
|
15
data/scripts/commands/ability/cleric_stance.lua
Normal file
15
data/scripts/commands/ability/cleric_stance.lua
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
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)
|
||||||
|
--DoAction handles rates, buffs, dealing damage
|
||||||
|
action.DoAction(caster, target, skill, actionContainer);
|
||||||
|
end;
|
19
data/scripts/commands/ability/collusion.lua
Normal file
19
data/scripts/commands/ability/collusion.lua
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
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)
|
||||||
|
--8032701: Fighter's Gauntlets: Reduces Collusion cooldown by 10 seconds
|
||||||
|
if caster.GetEquipment().GetItemAtSlot(14).itemId == 8032701 then
|
||||||
|
skill.recastTimeMs = skill.recastTimeMs - 10000;
|
||||||
|
end
|
||||||
|
|
||||||
|
action.DoAction(caster, target, skill, actionContainer);
|
||||||
|
end;
|
23
data/scripts/commands/ability/cover.lua
Normal file
23
data/scripts/commands/ability/cover.lua
Normal file
@ -0,0 +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)
|
||||||
|
--This is for the "Cover" effect the caster receives.
|
||||||
|
local coverTier = 1
|
||||||
|
--8032701: Gallant Surcoat: Enhances Cover
|
||||||
|
if caster.GetEquipment().GetItemAtSlot(10).itemId == 8032701 then
|
||||||
|
coverTier = 2;
|
||||||
|
end
|
||||||
|
|
||||||
|
actionContainer.AddAction(caster.statusEffects.AddStatusForBattleAction(223063, coverTier));
|
||||||
|
|
||||||
|
action.DoAction(caster, target, skill, actionContainer);
|
||||||
|
end;
|
19
data/scripts/commands/ability/dark_seal.lua
Normal file
19
data/scripts/commands/ability/dark_seal.lua
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, ability)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, ability)
|
||||||
|
--27320: Swift Dark Seal
|
||||||
|
if caster.HasTrait(27320) then
|
||||||
|
ability.recastTimeMs = ability.recastTimeMs - 30000;
|
||||||
|
end
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onSkillFinish(caster, target, skill, action, actionContainer)
|
||||||
|
--DoAction handles rates, buffs, dealing damage
|
||||||
|
action.DoAction(caster, target, skill, actionContainer);
|
||||||
|
end;
|
19
data/scripts/commands/ability/decoy.lua
Normal file
19
data/scripts/commands/ability/decoy.lua
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, ability)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, ability)
|
||||||
|
--27244: Enhanced Decoy: Renders Decoy capable of evading melee attacks
|
||||||
|
if caster.HasTrait(27244) then
|
||||||
|
ability.statusId = 223238;
|
||||||
|
end
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onSkillFinish(caster, target, skill, action, actionContainer)
|
||||||
|
--DoAction handles rates, buffs, dealing damage
|
||||||
|
action.DoAction(caster, target, skill, actionContainer);
|
||||||
|
end;
|
13
data/scripts/commands/ability/default.lua
Normal file
13
data/scripts/commands/ability/default.lua
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, skill)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, skill)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onSkillFinish(caster, target, skill, action)
|
||||||
|
end;
|
20
data/scripts/commands/ability/divine_veil.lua
Normal file
20
data/scripts/commands/ability/divine_veil.lua
Normal 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)
|
||||||
|
--8051401: Gallant Cuisses
|
||||||
|
if caster.GetEquipment().GetItemAtSlot(14).itemId == 8051401 then
|
||||||
|
ability.statusTier = 2;
|
||||||
|
end
|
||||||
|
|
||||||
|
--DoAction handles rates, buffs, dealing damage
|
||||||
|
action.DoAction(caster, target, skill, actionContainer);
|
||||||
|
end;
|
16
data/scripts/commands/ability/dragonfire_dive.lua
Normal file
16
data/scripts/commands/ability/dragonfire_dive.lua
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
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;
|
24
data/scripts/commands/ability/dread_spike.lua
Normal file
24
data/scripts/commands/ability/dread_spike.lua
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(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)
|
||||||
|
|
||||||
|
if remAction ~= nil then
|
||||||
|
actionContainer.AddAction(remAction);
|
||||||
|
skill.statusTier = 2;
|
||||||
|
break;
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
--DoAction handles rates, buffs, dealing damage
|
||||||
|
action.DoAction(caster, target, skill, actionContainer);
|
||||||
|
end;
|
16
data/scripts/commands/ability/elusive_jump.lua
Normal file
16
data/scripts/commands/ability/elusive_jump.lua
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
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)
|
||||||
|
--How to do enmity?
|
||||||
|
--DoAction handles rates, buffs, dealing damage
|
||||||
|
action.DoAction(caster, target, skill, actionContainer);
|
||||||
|
end;
|
19
data/scripts/commands/ability/enduring_march.lua
Normal file
19
data/scripts/commands/ability/enduring_march.lua
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, ability)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, ability)
|
||||||
|
--27203: Enhanced Outmaneuver
|
||||||
|
if caster.HasTrait(27203) 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;
|
19
data/scripts/commands/ability/excruciate.lua
Normal file
19
data/scripts/commands/ability/excruciate.lua
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, ability)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, ability)
|
||||||
|
--27321: Enhanced Excruciate: Increases critical rate bonus from Excruciate.
|
||||||
|
if caster.HasTrait(27321) 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;
|
19
data/scripts/commands/ability/featherfoot.lua
Normal file
19
data/scripts/commands/ability/featherfoot.lua
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, ability)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, ability)
|
||||||
|
--27123: Enhanced Featherfoot
|
||||||
|
if caster.HasTrait(27123) 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;
|
19
data/scripts/commands/ability/fists_of_earth.lua
Normal file
19
data/scripts/commands/ability/fists_of_earth.lua
Normal 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 Earth
|
||||||
|
if caster.HasTrait(27125) 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;
|
19
data/scripts/commands/ability/fists_of_fire.lua
Normal file
19
data/scripts/commands/ability/fists_of_fire.lua
Normal 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;
|
27
data/scripts/commands/ability/flash.lua
Normal file
27
data/scripts/commands/ability/flash.lua
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, ability)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, ability)
|
||||||
|
--27161: Enhanced Flash: Adds Blind effect to flash
|
||||||
|
if caster.HasTrait(27161) then
|
||||||
|
ability.statusChance = 1;
|
||||||
|
end
|
||||||
|
|
||||||
|
--27162: Enhanced Flash II: Expands Flash to affect enemies near target
|
||||||
|
if caster.HasTrait(27162) then
|
||||||
|
ability.aoeTarget = TargetFindAOEType.Circle;
|
||||||
|
end
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onSkillFinish(caster, target, skill, action, actionContainer)
|
||||||
|
action.enmity = 400;
|
||||||
|
|
||||||
|
--DoAction handles rates, buffs, dealing damage
|
||||||
|
action.DoAction(caster, target, skill, actionContainer);
|
||||||
|
end;
|
19
data/scripts/commands/ability/foresight.lua
Normal file
19
data/scripts/commands/ability/foresight.lua
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, ability)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, ability)
|
||||||
|
--27201: Swift Foresight
|
||||||
|
if caster.HasTrait(27201) then
|
||||||
|
ability.recastTimeMs = ability.recastTimeMs - 15000;
|
||||||
|
end
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onSkillFinish(caster, target, skill, action, actionContainer)
|
||||||
|
--DoAction handles rates, buffs, dealing damage
|
||||||
|
action.DoAction(caster, target, skill, actionContainer);
|
||||||
|
end;
|
28
data/scripts/commands/ability/hallowed_ground.lua
Normal file
28
data/scripts/commands/ability/hallowed_ground.lua
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, ability)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, ability)
|
||||||
|
--27245: Swift Chameleon
|
||||||
|
if caster.HasTrait(27245) then
|
||||||
|
ability.recastTimeMs = ability.recastTimeMs - 60000;
|
||||||
|
end
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
--Get all targets with hate on caster and spread 1140 enmity between them.
|
||||||
|
function onSkillFinish(caster, target, skill, action, actionContainer)
|
||||||
|
--[[
|
||||||
|
local enemies = caster.GetTargetsWithHate()
|
||||||
|
local enmity = 1140 / enemies.Count
|
||||||
|
for enemy in enemies do
|
||||||
|
enemy.hateContainer.updateHate(enmity);
|
||||||
|
end]]
|
||||||
|
|
||||||
|
|
||||||
|
--DoAction handles rates, buffs, dealing damage
|
||||||
|
action.DoAction(caster, target, skill, actionContainer);
|
||||||
|
end;
|
19
data/scripts/commands/ability/hawks_eye.lua
Normal file
19
data/scripts/commands/ability/hawks_eye.lua
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, ability)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, ability)
|
||||||
|
--27240: Enhanced Hawks Eye
|
||||||
|
--Increases accuracy gained by 50%. (Hawks Eye normally gives 12.5% of your accuracy, Traited it gives 18.75%)
|
||||||
|
if caster.HasTrait(27240) then
|
||||||
|
ability.statusTier = 2
|
||||||
|
end
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onSkillFinish(caster, target, skill, action, actionContainer)
|
||||||
|
action.DoAction(caster, target, skill, actionContainer);
|
||||||
|
end;
|
29
data/scripts/commands/ability/invigorate.lua
Normal file
29
data/scripts/commands/ability/invigorate.lua
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
require("global");
|
||||||
|
require("Ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, ability)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, ability)
|
||||||
|
--27280: Enhanced Invigorate: Increases duration of Invigorate by 15 seconds
|
||||||
|
if caster.HasTrait(27280) then
|
||||||
|
ability.statusDuration = ability.statusDuration + 15;
|
||||||
|
end
|
||||||
|
|
||||||
|
--Drachen Mail: Increases Invigorate TP tick from 100 to 120.
|
||||||
|
local magnitude = 100;
|
||||||
|
|
||||||
|
--8032704: Drachen Mail
|
||||||
|
if caster.GetEquipment().GetItemAtSlot(10).itemId == 8032704 then
|
||||||
|
magnitude = 120;
|
||||||
|
end
|
||||||
|
|
||||||
|
ability.statusMagnitude = magnitude;
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onSkillFinish(caster, target, skill, action, actionContainer)
|
||||||
|
--DoAction handles rates, buffs, dealing damage
|
||||||
|
action.DoAction(caster, target, skill, actionContainer);
|
||||||
|
end;
|
17
data/scripts/commands/ability/jump.lua
Normal file
17
data/scripts/commands/ability/jump.lua
Normal 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;
|
19
data/scripts/commands/ability/keen_flurry.lua
Normal file
19
data/scripts/commands/ability/keen_flurry.lua
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, ability)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, ability)
|
||||||
|
--27285: Enhanced Keen Flurry: Reduces recast time of WS used during KF by 50%
|
||||||
|
if caster.HasTrait(27285) 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;
|
53
data/scripts/commands/ability/life_surge.lua
Normal file
53
data/scripts/commands/ability/life_surge.lua
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, ability)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, ability)
|
||||||
|
--27282: Enhanced Life Surge: Increases effect of Life Surge by 20%
|
||||||
|
if caster.HasTrait(27282) then
|
||||||
|
ability.statusTier = 2;
|
||||||
|
end
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onSkillFinish(caster, target, skill, action, actionContainer)
|
||||||
|
--Need a better way to do this
|
||||||
|
--223212: Power Surge I
|
||||||
|
--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);
|
||||||
|
|
||||||
|
|
||||||
|
--Using this ability moves to the next LS buff
|
||||||
|
local removeId = 0;
|
||||||
|
--223215: Life Surge I
|
||||||
|
--223216: Life Surge II
|
||||||
|
--223217: Life Surge III
|
||||||
|
if caster.statusEffects.HasStatusEffect(223215) then
|
||||||
|
removeId = 223215;
|
||||||
|
skill.statusId = 223216;
|
||||||
|
skill.statusTier = 2;
|
||||||
|
elseif caster.statusEffects.HasStatusEffect(223216) then
|
||||||
|
removeId = 223216;
|
||||||
|
skill.statusId = 223217;
|
||||||
|
skill.statusTier = 3;
|
||||||
|
elseif caster.statusEffects.HasStatusEffect(223217) then
|
||||||
|
effect = caster.statusEffects.GetStatusEffectById(223217)
|
||||||
|
effect.RefreshTime();
|
||||||
|
skill.statusId = 223217;
|
||||||
|
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
|
||||||
|
action.DoAction(caster, target, skill, actionContainer);
|
||||||
|
end;
|
16
data/scripts/commands/ability/light_shot.lua
Normal file
16
data/scripts/commands/ability/light_shot.lua
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
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;
|
19
data/scripts/commands/ability/necrogenesis.lua
Normal file
19
data/scripts/commands/ability/necrogenesis.lua
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, ability)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, ability)
|
||||||
|
--27322: Swift Dark Seal
|
||||||
|
if caster.HasTrait(27322) then
|
||||||
|
ability.recastTimeMs = ability.recastTimeMs - 30000;
|
||||||
|
end
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onSkillFinish(caster, target, skill, action, actionContainer)
|
||||||
|
--DoAction handles rates, buffs, dealing damage
|
||||||
|
action.DoAction(caster, target, skill, actionContainer);
|
||||||
|
end;
|
19
data/scripts/commands/ability/outmaneuver.lua
Normal file
19
data/scripts/commands/ability/outmaneuver.lua
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, ability)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, ability)
|
||||||
|
--27164: Enhanced Outmaneuver
|
||||||
|
if caster.HasTrait(27164) 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;
|
19
data/scripts/commands/ability/parsimony.lua
Normal file
19
data/scripts/commands/ability/parsimony.lua
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, ability)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, ability)
|
||||||
|
--27323: Enhanced Parsimony: Increases MP gained from Parsimony by 25%
|
||||||
|
if caster.HasTrait(27323) 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;
|
24
data/scripts/commands/ability/power_surge.lua
Normal file
24
data/scripts/commands/ability/power_surge.lua
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, ability)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, ability)
|
||||||
|
--27281: Enhanced Power Surge: Increases effect of Power Surge by 50%
|
||||||
|
if caster.HasTrait(27281) then
|
||||||
|
ability.statusTier = 2;
|
||||||
|
end
|
||||||
|
return 0;
|
||||||
|
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));
|
||||||
|
|
||||||
|
--DoAction handles rates, buffs, dealing damage
|
||||||
|
action.DoAction(caster, target, skill, actionContainer);
|
||||||
|
end;
|
21
data/scripts/commands/ability/provoke.lua
Normal file
21
data/scripts/commands/ability/provoke.lua
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, ability)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, ability)
|
||||||
|
--27200: Enhanced Provoke: Adds Attack Down effect to Provoke.
|
||||||
|
if caster.HasTrait(27200) then
|
||||||
|
ability.statusChance = 1.0;
|
||||||
|
end
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
--http://forum.square-enix.com/ffxiv/threads/47393-Tachi-s-Guide-to-Paladin-%28post-1.22b%29
|
||||||
|
function onSkillFinish(caster, target, skill, action, actionContainer)
|
||||||
|
action.enmity = 750;
|
||||||
|
--DoAction handles rates, buffs, dealing damage
|
||||||
|
action.DoAction(caster, target, skill, actionContainer);
|
||||||
|
end;
|
29
data/scripts/commands/ability/quelling_strike.lua
Normal file
29
data/scripts/commands/ability/quelling_strike.lua
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
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)
|
||||||
|
--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
|
||||||
|
|
||||||
|
--DoAction handles rates, buffs, dealing damage
|
||||||
|
action.DoAction(caster, target, skill, actionContainer);
|
||||||
|
end;
|
19
data/scripts/commands/ability/raging_strike.lua
Normal file
19
data/scripts/commands/ability/raging_strike.lua
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, ability)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, ability)
|
||||||
|
--27243: Enhanced Raging Strike: Increases effect of Raging Strike by 50%
|
||||||
|
if caster.HasTrait(27241) 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;
|
19
data/scripts/commands/ability/rampage.lua
Normal file
19
data/scripts/commands/ability/rampage.lua
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, ability)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, ability)
|
||||||
|
--27204: Enhanced Rampage
|
||||||
|
if caster.HasTrait(27204) 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;
|
27
data/scripts/commands/ability/rampart.lua
Normal file
27
data/scripts/commands/ability/rampart.lua
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
require("global");
|
||||||
|
require("ability");
|
||||||
|
require("battleutils")
|
||||||
|
|
||||||
|
function onAbilityPrepare(caster, target, ability)
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAbilityStart(caster, target, ability)
|
||||||
|
|
||||||
|
--27163: Enhanced Rampart:Expands rampart to affect party members
|
||||||
|
if caster.HasTrait(27163) then
|
||||||
|
ability.aoeType = TargetFindAOEType.Circle;
|
||||||
|
end
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
--http://forum.square-enix.com/ffxiv/threads/47393-Tachi-s-Guide-to-Paladin-%28post-1.22b%29
|
||||||
|
--180 enmity per member that has enmity on the current enemy
|
||||||
|
--Need to figure out enmity system
|
||||||
|
function onSkillFinish(caster, target, skill, action, actionContainer)
|
||||||
|
action.enmity = 180;
|
||||||
|
|
||||||
|
--DoAction handles rates, buffs, dealing damage
|
||||||
|
action.DoAction(caster, target, skill, actionContainer);
|
||||||
|
end;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user