diff --git a/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj b/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj
index ae9023ba..0b56c529 100644
--- a/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj
+++ b/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj
@@ -109,6 +109,7 @@
+
diff --git a/FFXIVClassic Map Server/PacketProcessor.cs b/FFXIVClassic Map Server/PacketProcessor.cs
index 3fdad311..3e0f051c 100644
--- a/FFXIVClassic Map Server/PacketProcessor.cs
+++ b/FFXIVClassic Map Server/PacketProcessor.cs
@@ -154,6 +154,7 @@ namespace FFXIVClassic_Map_Server
case 0x00CC:
LockTargetPacket lockTarget = new LockTargetPacket(subpacket.data);
session.GetActor().currentLockedTarget = lockTarget.actorID;
+ // todo: this really needs figuring out..
session.GetActor().isAutoAttackEnabled = lockTarget.otherVal == 0x00000040;
break;
diff --git a/FFXIVClassic Map Server/WorldManager.cs b/FFXIVClassic Map Server/WorldManager.cs
index 52a8a587..bf1f2844 100644
--- a/FFXIVClassic Map Server/WorldManager.cs
+++ b/FFXIVClassic Map Server/WorldManager.cs
@@ -25,6 +25,7 @@ using System.Diagnostics;
using FFXIVClassic_Map_Server.actors.director;
using FFXIVClassic_Map_Server.actors.chara.ai;
using FFXIVClassic_Map_Server.actors.chara;
+using FFXIVClassic_Map_Server.Actors.Chara;
namespace FFXIVClassic_Map_Server
{
@@ -427,7 +428,7 @@ namespace FFXIVClassic_Map_Server
{
conn.Open();
var query = @"
- SELECT bsl.groupId, bsl.positionX, bsl.positionY, bsl.positionZ, bsl.rotation,
+ SELECT bsl.bnpcId, bsl.groupId, bsl.positionX, bsl.positionY, bsl.positionZ, bsl.rotation,
bgr.groupId, bgr.poolId, bgr.actorClassId, bgr.scriptName, bgr.minLevel, bgr.maxLevel, bgr.respawnTime, bgr.hp, bgr.mp,
bgr.dropListId, bgr.allegiance, bgr.spawnType, bgr.animationId, bgr.actorState, bgr.privateAreaName, bgr.privateAreaLevel, bgr.zoneId,
bpo.poolId, bpo.genusId, bpo.currentJob, bpo.combatSkill, bpo.combatDelay, bpo.combatDmgMult, bpo.aggroType,
@@ -439,7 +440,7 @@ namespace FFXIVClassic_Map_Server
INNER JOIN server_battlenpc_groups bgr ON bsl.groupId = bgr.groupId
INNER JOIN server_battlenpc_pools bpo ON bgr.poolId = bpo.poolId
INNER JOIN server_battlenpc_genus bge ON bpo.genusId = bge.genusId
- WHERE bgr.zoneId = @zoneId GROUP BY bsl.bnpcIndex;
+ WHERE bgr.zoneId = @zoneId GROUP BY bsl.bnpcId;
";
var count = 0;
@@ -457,20 +458,19 @@ namespace FFXIVClassic_Map_Server
int actorId = zone.GetActorCount() + 1;
// todo: add to private areas, set up immunity, mob linking,
- // - load skill/spell/drop lists, set npcWork.hateType,
+ // - load skill/spell/drop lists, set detection icon, load pool/family/group mods
var battleNpc = new BattleNpc(actorId, Server.GetWorldManager().GetActorClass(reader.GetUInt32("actorClassId")),
reader.GetString("scriptName"), zone, reader.GetFloat("positionX"), reader.GetFloat("positionY"), reader.GetFloat("positionZ"), reader.GetFloat("rotation"),
reader.GetUInt16("actorState"), reader.GetUInt32("animationId"), "");
+ battleNpc.SetBattleNpcId(reader.GetUInt32("bnpcId"));
battleNpc.neutral = reader.GetByte("aggroType") == 0;
battleNpc.SetDetectionType(reader.GetUInt32("detection"));
battleNpc.kindredType = (KindredType)reader.GetUInt32("kindredId");
battleNpc.npcSpawnType = (NpcSpawnType)reader.GetUInt32("spawnType");
- // todo: set hateType to appropriate detectionType thing
- //battleNpc.npcWork.hateType
battleNpc.charaWork.parameterSave.state_mainSkill[0] = reader.GetByte("currentJob");
battleNpc.charaWork.parameterSave.state_mainSkillLevel = (short)Program.Random.Next(reader.GetByte("minLevel"), reader.GetByte("maxLevel"));
@@ -527,6 +527,112 @@ namespace FFXIVClassic_Map_Server
z.SpawnAllActors(true);
}
+ public void SpawnBattleNpcById(uint id)
+ {
+ // todo: this is stupid duplicate code and really needs to die, think of a better way later
+ 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
+ {
+ conn.Open();
+ var query = @"
+ SELECT bsl.bnpcId, bsl.groupId, bsl.positionX, bsl.positionY, bsl.positionZ, bsl.rotation,
+ bgr.groupId, bgr.poolId, bgr.actorClassId, bgr.scriptName, bgr.minLevel, bgr.maxLevel, bgr.respawnTime, bgr.hp, bgr.mp,
+ bgr.dropListId, bgr.allegiance, bgr.spawnType, bgr.animationId, bgr.actorState, bgr.privateAreaName, bgr.privateAreaLevel, bgr.zoneId,
+ bpo.poolId, bpo.genusId, bpo.currentJob, bpo.combatSkill, bpo.combatDelay, bpo.combatDmgMult, bpo.aggroType,
+ bpo.immunity, bpo.linkType, bpo.skillListId, bpo.spellListId,
+ bge.genusId, bge.modelSize, bge.kindredId, bge.detection, bge.hpp, bge.mpp, bge.tpp, bge.str, bge.vit, bge.dex,
+ bge.int, bge.mnd, bge.pie, bge.att, bge.acc, bge.def, bge.eva, bge.slash, bge.pierce, bge.h2h, bge.blunt,
+ bge.fire, bge.ice, bge.wind, bge.lightning, bge.earth, bge.water
+ FROM server_battlenpc_spawn_locations bsl
+ INNER JOIN server_battlenpc_groups bgr ON bsl.groupId = bgr.groupId
+ INNER JOIN server_battlenpc_pools bpo ON bgr.poolId = bpo.poolId
+ INNER JOIN server_battlenpc_genus bge ON bpo.genusId = bge.genusId
+ WHERE bsl.bnpcId = @bnpcId GROUP BY bsl.bnpcId;
+ ";
+
+ var count = 0;
+
+ MySqlCommand cmd = new MySqlCommand(query, conn);
+ cmd.Parameters.AddWithValue("@bnpcId", id);
+
+ using (MySqlDataReader reader = cmd.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ var zone = Server.GetWorldManager().GetZone(reader.GetUInt16("zoneId"));
+ int actorId = zone.GetActorCount() + 1;
+ var bnpc = zone.GetBattleNpcById(id);
+
+ if (bnpc != null)
+ {
+ bnpc.ForceRespawn();
+ break;
+ }
+
+ // todo: add to private areas, set up immunity, mob linking,
+ // - load skill/spell/drop lists, set detection icon, load pool/family/group mods
+
+ var battleNpc = new BattleNpc(actorId, Server.GetWorldManager().GetActorClass(reader.GetUInt32("actorClassId")),
+ reader.GetString("scriptName"), zone, reader.GetFloat("positionX"), reader.GetFloat("positionY"), reader.GetFloat("positionZ"), reader.GetFloat("rotation"),
+ reader.GetUInt16("actorState"), reader.GetUInt32("animationId"), "");
+
+ battleNpc.SetBattleNpcId(reader.GetUInt32("bnpcId"));
+ battleNpc.neutral = reader.GetByte("aggroType") == 0;
+
+ battleNpc.SetDetectionType(reader.GetUInt32("detection"));
+ battleNpc.kindredType = (KindredType)reader.GetUInt32("kindredId");
+ battleNpc.npcSpawnType = (NpcSpawnType)reader.GetUInt32("spawnType");
+
+ battleNpc.charaWork.parameterSave.state_mainSkill[0] = reader.GetByte("currentJob");
+ battleNpc.charaWork.parameterSave.state_mainSkillLevel = (short)Program.Random.Next(reader.GetByte("minLevel"), reader.GetByte("maxLevel"));
+
+ battleNpc.allegiance = (CharacterTargetingAllegiance)reader.GetByte("allegiance");
+
+ // todo: setup private areas and other crap and
+ // set up rest of stat resists
+ battleNpc.SetMod((uint)Modifier.Hp, reader.GetUInt32("hp"));
+ battleNpc.SetMod((uint)Modifier.HpPercent, reader.GetUInt32("hpp"));
+ battleNpc.SetMod((uint)Modifier.Mp, reader.GetUInt32("mp"));
+ battleNpc.SetMod((uint)Modifier.MpPercent, reader.GetUInt32("mpp"));
+ battleNpc.SetMod((uint)Modifier.TpPercent, reader.GetUInt32("tpp"));
+
+ battleNpc.SetMod((uint)Modifier.Strength, reader.GetUInt32("str"));
+ battleNpc.SetMod((uint)Modifier.Vitality, reader.GetUInt32("vit"));
+ battleNpc.SetMod((uint)Modifier.Dexterity, reader.GetUInt32("dex"));
+ battleNpc.SetMod((uint)Modifier.Intelligence, reader.GetUInt32("int"));
+ battleNpc.SetMod((uint)Modifier.Mind, reader.GetUInt32("mnd"));
+ battleNpc.SetMod((uint)Modifier.Piety, reader.GetUInt32("pie"));
+ battleNpc.SetMod((uint)Modifier.Attack, reader.GetUInt32("att"));
+ battleNpc.SetMod((uint)Modifier.Accuracy, reader.GetUInt32("acc"));
+ battleNpc.SetMod((uint)Modifier.Defense, reader.GetUInt32("def"));
+ battleNpc.SetMod((uint)Modifier.Evasion, reader.GetUInt32("eva"));
+
+
+ battleNpc.dropListId = reader.GetUInt32("dropListId");
+ battleNpc.spellListId = reader.GetUInt32("spellListId");
+ battleNpc.skillListId = reader.GetUInt32("skillListId");
+
+ battleNpc.SetBattleNpcId(reader.GetUInt32("bnpcId"));
+ //battleNpc.SetMod((uint)Modifier.ResistFire, )
+
+ zone.AddActorToZone(battleNpc);
+ count++;
+ }
+ }
+ Program.Log.Info("WorldManager.SpawnBattleNpcById spawned BattleNpc {0}.", id);
+ }
+ catch (MySqlException e)
+ {
+ Program.Log.Error(e.ToString());
+ }
+ finally
+ {
+ conn.Dispose();
+ }
+ }
+ }
+
//Moves the actor to the new zone if exists. No packets are sent nor position changed. Merged zone is removed.
public void DoSeamlessZoneChange(Player player, uint destinationZoneId)
{
diff --git a/FFXIVClassic Map Server/actors/Actor.cs b/FFXIVClassic Map Server/actors/Actor.cs
index 3a549e7d..ff589d52 100644
--- a/FFXIVClassic Map Server/actors/Actor.cs
+++ b/FFXIVClassic Map Server/actors/Actor.cs
@@ -25,9 +25,10 @@ namespace FFXIVClassic_Map_Server.Actors
Name = 0x08,
Appearance = 0x10,
Speed = 0x20,
+ Work = 0x40,
- AllNpc = 0x2F,
- AllPlayer = 0x3F
+ AllNpc = 0x6F,
+ AllPlayer = 0x9F
}
class Actor
diff --git a/FFXIVClassic Map Server/actors/area/Area.cs b/FFXIVClassic Map Server/actors/area/Area.cs
index dac57595..3918f2f6 100644
--- a/FFXIVClassic Map Server/actors/area/Area.cs
+++ b/FFXIVClassic Map Server/actors/area/Area.cs
@@ -526,6 +526,16 @@ namespace FFXIVClassic_Map_Server.Actors
}
}
+ public BattleNpc GetBattleNpcById(uint id)
+ {
+ foreach (var bnpc in GetAllActors())
+ {
+ if (bnpc.GetBattleNpcId() == id)
+ return bnpc;
+ }
+ return null;
+ }
+
public void DespawnActor(string uniqueId)
{
RemoveActorFromZone(FindActorInZoneByUniqueID(uniqueId));
diff --git a/FFXIVClassic Map Server/actors/chara/Character.cs b/FFXIVClassic Map Server/actors/chara/Character.cs
index 9c046c17..0af7b0be 100644
--- a/FFXIVClassic Map Server/actors/chara/Character.cs
+++ b/FFXIVClassic Map Server/actors/chara/Character.cs
@@ -159,7 +159,6 @@ namespace FFXIVClassic_Map_Server.Actors
ActorPropertyPacketUtil propPacketUtil = new ActorPropertyPacketUtil("charaWork/currentContentGroup", this);
propPacketUtil.AddProperty("charaWork.currentContentGroup");
zone.BroadcastPacketsAroundActor(this, propPacketUtil.Done());
-
}
public List GetActorStatusPackets()
@@ -593,6 +592,7 @@ namespace FFXIVClassic_Map_Server.Actors
//var packet = BattleActionX01Packet.BuildPacket(owner.actorId, owner.actorId, target.actorId, (uint)0x19001000, (uint)0x8000604, (ushort)0x765D, (ushort)BattleActionX01PacketCommand.Attack, (ushort)damage, (byte)0x1);
}
+ target.OnDamageTaken(this, action);
// todo: call onAttack/onDamageTaken
target.DelHP(action.amount);
if (target is BattleNpc)
@@ -605,6 +605,9 @@ namespace FFXIVClassic_Map_Server.Actors
// damage is handled in script
this.DelMP(spell.mpCost); // mpCost can be set in script e.g. if caster has something for free spells
+ foreach (var action in actions)
+ zone.FindActorInArea(action.targetId).OnDamageTaken(this, action);
+
if (target is BattleNpc)
((BattleNpc)target).lastAttacker = this;
}
@@ -615,6 +618,9 @@ namespace FFXIVClassic_Map_Server.Actors
// damage is handled in script
this.DelTP(skill.tpCost);
+ foreach (var action in actions)
+ zone.FindActorInArea(action.targetId)?.OnDamageTaken(this, action);
+
if (target is BattleNpc)
((BattleNpc)target).lastAttacker = this;
}
@@ -623,6 +629,9 @@ namespace FFXIVClassic_Map_Server.Actors
{
if (target is BattleNpc)
((BattleNpc)target).lastAttacker = this;
+
+ foreach (var action in actions)
+ zone.FindActorInArea(action.targetId)?.OnDamageTaken(this, action);
}
public virtual void OnSpawn()
@@ -638,6 +647,11 @@ namespace FFXIVClassic_Map_Server.Actors
public virtual void OnDespawn()
{
+ }
+
+ public virtual void OnDamageTaken(Character attacker, BattleAction action)
+ {
+
}
#endregion
}
diff --git a/FFXIVClassic Map Server/actors/chara/ai/ActionQueue.cs b/FFXIVClassic Map Server/actors/chara/ai/ActionQueue.cs
index b69caed8..87112724 100644
--- a/FFXIVClassic Map Server/actors/chara/ai/ActionQueue.cs
+++ b/FFXIVClassic Map Server/actors/chara/ai/ActionQueue.cs
@@ -6,6 +6,7 @@ using System.Threading.Tasks;
using FFXIVClassic_Map_Server.Actors;
using MoonSharp;
using MoonSharp.Interpreter;
+using FFXIVClassic_Map_Server.lua;
namespace FFXIVClassic_Map_Server.actors.chara.ai
{
@@ -15,8 +16,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
public uint durationMs;
public bool checkState;
// todo: lua function
- Script script;
+ LuaScript script;
}
+
class ActionQueue
{
private Character owner;
@@ -27,7 +29,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
public ActionQueue(Character owner)
{
-
+ this.owner = owner;
+ actionQueue = new Queue();
+ timerQueue = new Queue();
}
public void PushAction(Action action)
@@ -45,5 +49,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
}
+ public void CheckAction(DateTime tick)
+ {
+
+ }
}
}
diff --git a/FFXIVClassic Map Server/actors/chara/ai/controllers/BattleNpcController.cs b/FFXIVClassic Map Server/actors/chara/ai/controllers/BattleNpcController.cs
index ca709462..5b8ef878 100644
--- a/FFXIVClassic Map Server/actors/chara/ai/controllers/BattleNpcController.cs
+++ b/FFXIVClassic Map Server/actors/chara/ai/controllers/BattleNpcController.cs
@@ -363,8 +363,12 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
public override void ChangeTarget(Character target)
{
owner.target = target;
- owner.currentLockedTarget = target != null ? target.actorId : 0xC0000000;
- owner.currentTarget = target != null ? target.actorId : 0xC0000000;
+ owner.currentLockedTarget = target?.actorId ?? 0xC0000000;
+ owner.currentTarget = target?.actorId ?? 0xC0000000;
+
+ foreach (var player in owner.zone.GetActorsAroundActor(owner, 50))
+ player.QueuePacket(owner.GetHateTypePacket(player));
+
base.ChangeTarget(target);
}
}
diff --git a/FFXIVClassic Map Server/actors/chara/ai/state/AttackState.cs b/FFXIVClassic Map Server/actors/chara/ai/state/AttackState.cs
index f0e8df36..5994f140 100644
--- a/FFXIVClassic Map Server/actors/chara/ai/state/AttackState.cs
+++ b/FFXIVClassic Map Server/actors/chara/ai/state/AttackState.cs
@@ -155,7 +155,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
owner.aiContainer.ChangeTarget(null);
return false;
}
- else if (!owner.aiContainer.GetTargetFind().CanTarget(target, false, true))
+ else if (!owner.IsValidTarget(target, ValidTarget.Enemy) || !owner.aiContainer.GetTargetFind().CanTarget(target, false, true))
{
return false;
}
diff --git a/FFXIVClassic Map Server/actors/chara/npc/BattleNpc.cs b/FFXIVClassic Map Server/actors/chara/npc/BattleNpc.cs
index 6aa386fd..610a0e3e 100644
--- a/FFXIVClassic Map Server/actors/chara/npc/BattleNpc.cs
+++ b/FFXIVClassic Map Server/actors/chara/npc/BattleNpc.cs
@@ -17,6 +17,7 @@ using FFXIVClassic_Map_Server.packets.send.actor.battle;
using FFXIVClassic_Map_Server.actors.chara.ai.utils;
using FFXIVClassic_Map_Server.actors.group;
using FFXIVClassic_Map_Server.packets.send;
+using FFXIVClassic_Map_Server.Actors.Chara;
namespace FFXIVClassic_Map_Server.Actors
{
@@ -56,12 +57,13 @@ namespace FFXIVClassic_Map_Server.Actors
private uint despawnTime;
private uint respawnTime;
private uint spawnDistance;
-
+ private uint bnpcId;
public Character lastAttacker;
public uint spellListId, skillListId, dropListId;
public Dictionary skillList = new Dictionary();
public Dictionary spellList = new Dictionary();
+ private Dictionary mobModifiers = new Dictionary();
public BattleNpc(int actorNumber, ActorClass actorClass, string uniqueId, Area spawnedArea, float posX, float posY, float posZ, float rot,
ushort actorState, uint animationId, string customDisplayName)
@@ -110,10 +112,43 @@ namespace FFXIVClassic_Map_Server.Actors
subpackets.Add(CreateSetActorIconPacket());
subpackets.Add(CreateIsZoneingPacket());
subpackets.Add(CreateScriptBindPacket(player));
+ subpackets.Add(GetHateTypePacket(player));
}
return subpackets;
}
+ public SubPacket GetHateTypePacket(Player player)
+ {
+ npcWork.hateType = 1;
+
+ if (player != null)
+ {
+ if (aiContainer.IsEngaged())
+ {
+ npcWork.hateType = 2;
+ }
+
+ if (player.actorId == this.currentLockedTarget)
+ {
+ 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;
+ break;
+ }
+ }
+ }
+ }
+ var propPacketUtil = new ActorPropertyPacketUtil("npcWork", this);
+ propPacketUtil.AddProperty("npcWork.hateType");
+ return propPacketUtil.Done()[0];
+ }
+
public uint GetDetectionType()
{
return (uint)detectionType;
@@ -220,6 +255,23 @@ namespace FFXIVClassic_Map_Server.Actors
}
}
+ public void ForceRespawn()
+ {
+ base.Spawn(Program.Tick);
+
+ this.isMovingToSpawn = false;
+ this.ResetMoveSpeeds();
+ this.hateContainer.ClearHate();
+ zone.BroadcastPacketsAroundActor(this, GetSpawnPackets(null, 0x01));
+ zone.BroadcastPacketsAroundActor(this, GetInitPackets());
+ charaWork.parameterSave.hp = charaWork.parameterSave.hpMax;
+ charaWork.parameterSave.mp = charaWork.parameterSave.mpMax;
+ RecalculateStats();
+
+ OnSpawn();
+ updateFlags |= ActorUpdateFlags.AllNpc;
+ }
+
public override void Die(DateTime tick)
{
if (IsAlive())
@@ -308,6 +360,25 @@ namespace FFXIVClassic_Map_Server.Actors
base.OnAttack(state, action, ref error);
// todo: move this somewhere else prolly and change based on model/appearance (so maybe in Character.cs instead)
action.animation = 0x11001000; // (temporary) wolf anim
+
+ if (GetMobMod((uint)MobModifier.AttackScript) != 0)
+ lua.LuaEngine.CallLuaBattleFunction(this, "onAttack", this, state.GetTarget(), action.amount);
+ }
+
+ public override void OnCast(State state, BattleAction[] actions, ref BattleAction[] errors)
+ {
+ base.OnCast(state, actions, ref errors);
+
+ }
+
+ public override void OnAbility(State state, BattleAction[] actions, ref BattleAction[] errors)
+ {
+ base.OnAbility(state, actions, ref errors);
+ }
+
+ public override void OnWeaponSkill(State state, BattleAction[] actions, ref BattleAction[] errors)
+ {
+ base.OnWeaponSkill(state, actions, ref errors);
}
public override void OnSpawn()
@@ -325,5 +396,38 @@ namespace FFXIVClassic_Map_Server.Actors
{
base.OnDespawn();
}
+
+ public uint GetBattleNpcId()
+ {
+ return bnpcId;
+ }
+
+ public void SetBattleNpcId(uint id)
+ {
+ this.bnpcId = id;
+ }
+
+
+ public Int64 GetMobMod(uint mobModId)
+ {
+ Int64 res;
+ if (mobModifiers.TryGetValue((MobModifier)mobModId, out res))
+ return res;
+ return 0;
+ }
+
+ public void SetMobMod(uint mobModId, Int64 val)
+ {
+ if (mobModifiers.ContainsKey((MobModifier)mobModId))
+ mobModifiers[(MobModifier)mobModId] = val;
+ else
+ mobModifiers.Add((MobModifier)mobModId, val);
+ }
+
+ public override void OnDamageTaken(Character attacker, BattleAction action)
+ {
+ if (GetMobMod((uint)MobModifier.DefendScript) != 0)
+ lua.LuaEngine.CallLuaBattleFunction(this, "onDamageTaken", this, attacker, action.amount);
+ }
}
}
diff --git a/FFXIVClassic Map Server/actors/chara/npc/MobModifier.cs b/FFXIVClassic Map Server/actors/chara/npc/MobModifier.cs
new file mode 100644
index 00000000..7b2cc67b
--- /dev/null
+++ b/FFXIVClassic Map Server/actors/chara/npc/MobModifier.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace FFXIVClassic_Map_Server.actors.chara.npc
+{
+ enum MobModifier
+ {
+ None = 0,
+ SpawnLeash = 1, // how far can i move before i deaggro target
+ SightRange = 2, // how close does target need to be for me to detect by sight
+ SoundRange = 3, // how close does target need to be for me to detect by sound
+ BuffChance = 4,
+ HealChance = 5,
+ SkillUseChance = 6,
+ LinkRadius = 7,
+ MagicDelay = 8,
+ SpecialDelay = 9,
+ ExpBonus = 10, //
+ IgnoreSpawnLeash = 11, // pursue target forever
+ DrawIn = 12, // do i suck people in around me
+ HpScale = 13, //
+ Assist = 14, // gotta call the bois
+ NoMove = 15, // cant move
+ ShareTarget = 16, // use this actor's id as target id
+ AttackScript = 17, // call my script's onAttack whenever i attack
+ DefendScript = 18, // call my script's onDamageTaken whenever i take damage
+ SpellScript = 19, // call my script's onSpellCast whenever i finish casting
+ WeaponskillScript = 20, // call my script's onWeaponSkill whenever i finish using a weaponskill
+ AbilityScript = 21, // call my script's onAbility whenever i finish using an ability
+ CallForHelp = 22, // actor with this id outside of target's party with this can attack me
+ FreeForAll = 23, // any actor can attack me
+ }
+}
\ No newline at end of file
diff --git a/FFXIVClassic Map Server/actors/chara/npc/Npc.cs b/FFXIVClassic Map Server/actors/chara/npc/Npc.cs
index 845dda05..8feb3f51 100644
--- a/FFXIVClassic Map Server/actors/chara/npc/Npc.cs
+++ b/FFXIVClassic Map Server/actors/chara/npc/Npc.cs
@@ -414,6 +414,17 @@ namespace FFXIVClassic_Map_Server.Actors
aiContainer.Update(tick);
}
+ public override void PostUpdate(DateTime tick, List packets = null)
+ {
+ packets = packets ?? new List();
+
+ if ((updateFlags & ActorUpdateFlags.Work) != 0)
+ {
+
+ }
+ base.PostUpdate(tick, packets);
+ }
+
public override void OnSpawn()
{
base.OnSpawn();
diff --git a/FFXIVClassic Map Server/actors/chara/npc/NpcWork.cs b/FFXIVClassic Map Server/actors/chara/npc/NpcWork.cs
index ca21d437..7827f7f5 100644
--- a/FFXIVClassic Map Server/actors/chara/npc/NpcWork.cs
+++ b/FFXIVClassic Map Server/actors/chara/npc/NpcWork.cs
@@ -2,6 +2,10 @@
{
class NpcWork
{
+ public static byte HATE_TYPE_NONE = 0;
+ public static byte HATE_TYPE_ENGAGED = 2;
+ public static byte HATE_TYPE_ENGAGED_PARTY = 3;
+
public ushort pushCommand;
public int pushCommandSub;
public byte pushCommandPriority;
diff --git a/FFXIVClassic Map Server/actors/chara/player/Player.cs b/FFXIVClassic Map Server/actors/chara/player/Player.cs
index 2d6686a5..fab60a05 100644
--- a/FFXIVClassic Map Server/actors/chara/player/Player.cs
+++ b/FFXIVClassic Map Server/actors/chara/player/Player.cs
@@ -25,6 +25,7 @@ using FFXIVClassic_Map_Server.actors.chara.ai.controllers;
using FFXIVClassic_Map_Server.packets.send.actor.battle;
using FFXIVClassic_Map_Server.actors.chara.ai.utils;
using FFXIVClassic_Map_Server.actors.chara.ai.state;
+using FFXIVClassic_Map_Server.actors.chara.npc;
namespace FFXIVClassic_Map_Server.Actors
{
@@ -2037,6 +2038,47 @@ namespace FFXIVClassic_Map_Server.Actors
SendGameMessage(Server.GetWorldManager().GetActor(), 32549, 0x20);
return false;
}
+
+ bool partyEngaged = false;
+ // todo: replace with confrontation status effect? (see how dsp does it)
+ if (target.aiContainer.IsEngaged())
+ {
+ if (currentParty != null)
+ {
+ if (target is BattleNpc)
+ {
+ var helpingActorId = ((BattleNpc)target).GetMobMod((uint)MobModifier.CallForHelp);
+ partyEngaged = this.actorId == helpingActorId || (((BattleNpc)target).GetMobMod((uint)MobModifier.FreeForAll) != 0);
+ }
+
+ if (!partyEngaged)
+ {
+ foreach (var memberId in ((Party)currentParty).members)
+ {
+ if (memberId == target.currentLockedTarget)
+ {
+ partyEngaged = true;
+ break;
+ }
+ }
+ }
+ }
+ else if (target.currentLockedTarget == actorId)
+ {
+ partyEngaged = true;
+ }
+ }
+ else
+ {
+ partyEngaged = true;
+ }
+
+ if (!partyEngaged)
+ {
+ // That target is already engaged.
+ SendGameMessage(Server.GetWorldManager().GetActor(), 32520, 0x20);
+ return false;
+ }
}
if ((validTarget & ValidTarget.Ally) != 0 && target.allegiance != allegiance)
diff --git a/data/scripts/commands/weaponskill/fast_blade.lua b/data/scripts/commands/weaponskill/fast_blade.lua
new file mode 100644
index 00000000..e44a637d
--- /dev/null
+++ b/data/scripts/commands/weaponskill/fast_blade.lua
@@ -0,0 +1,27 @@
+require("global");
+require("weaponskill");
+
+function onSkillPrepare(caster, target, spell)
+ return 0;
+end;
+
+function onSkillStart(caster, target, spell)
+ return 0;
+end;
+
+function onSkillFinish(caster, target, spell, action)
+ local damage = math.random(10, 100);
+
+ -- todo: populate a global script with statuses and modifiers
+ action.worldMasterTextId = 0x765D;
+
+ -- todo: populate a global script with statuses and modifiers
+ -- magic.HandleAttackSkill(caster, target, spell, action)
+ -- action.effectId = bit32.bxor(0x8000000, spell.effectAnimation, 15636);
+ action.effectId = bit32.bxor(0x8000000, spell.effectAnimation, 15636);
+
+ if target.hateContainer then
+ target.hateContainer.UpdateHate(caster, damage);
+ end;
+ return damage;
+end;
\ No newline at end of file
diff --git a/data/scripts/magic.lua b/data/scripts/magic.lua
index 35318f58..a2965fa4 100644
--- a/data/scripts/magic.lua
+++ b/data/scripts/magic.lua
@@ -7,10 +7,11 @@ magic =
};
--[[
- modifier - Modifier.Intelligence, Modifier.Mind (see Modifier.cs)
+ statId - see BattleTemp.cs
+ modifierId - Modifier.Intelligence, Modifier.Mind (see Modifier.cs)
multiplier -
]]
-function magic.HandleHealingMagic(caster, target, spell, action, modifierId, multiplier, baseAmount)
+function magic.HandleHealingMagic(caster, target, spell, action, statId, modifierId, multiplier, baseAmount)
potency = potency or 1.0;
healAmount = baseAmount;
@@ -18,19 +19,19 @@ function magic.HandleHealingMagic(caster, target, spell, action, modifierId, mul
local mind = caster.GetMod(Modifier.Mind);
end;
-function magic.HandleAttackMagic(caster, target, spell, action, modifierId, multiplier, baseAmount)
+function magic.HandleAttackMagic(caster, target, spell, action, statId, modifierId, multiplier, baseAmount)
-- todo: actually handle this
damage = baseAmount or math.random(1,10) * 10;
return damage;
end;
-function magic.HandleEvasion(caster, target, spell, action, modifierId)
+function magic.HandleEvasion(caster, target, spell, action, statId, modifierId)
return false;
end;
-function magic.HandleStoneskin(caster, target, spell, action, modifierId, damage)
+function magic.HandleStoneskin(caster, target, spell, action, statId, modifierId, damage)
--[[
if target.statusEffects.HasStatusEffect(StatusEffect.Stoneskin) then
-- todo: damage reduction
diff --git a/data/scripts/weaponskill.lua b/data/scripts/weaponskill.lua
new file mode 100644
index 00000000..84d79cc7
--- /dev/null
+++ b/data/scripts/weaponskill.lua
@@ -0,0 +1,42 @@
+-- todo: add enums for status effects in global.lua
+require("global")
+
+weaponskill =
+{
+
+};
+
+--[[
+ statId - see BattleTemp.cs
+ modifier - Modifier.Intelligence, Modifier.Mind (see Modifier.cs)
+ multiplier -
+ ]]
+function weaponskill.HandleHealingSkill(caster, target, spell, 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 weaponskill.HandleAttackSkill(caster, target, spell, action, statId, modifierId, multiplier, baseAmount)
+ -- todo: actually handle this
+ damage = baseAmount or math.random(1,10) * 10;
+
+ return damage;
+end;
+
+function weaponskill.HandleEvasion(caster, target, spell, action, statId, modifierId)
+
+ return false;
+end;
+
+function weaponskill.HandleStoneskin(caster, target, spell, action, statId, modifierId, damage)
+ --[[
+ if target.statusEffects.HasStatusEffect(StatusEffect.Stoneskin) then
+ -- todo: damage reduction
+ return true;
+ end;
+ ]]
+ return false;
+end;
\ No newline at end of file
diff --git a/sql/server_battlenpc_spawn_locations.sql b/sql/server_battlenpc_spawn_locations.sql
index d6f42bf2..91194f28 100644
--- a/sql/server_battlenpc_spawn_locations.sql
+++ b/sql/server_battlenpc_spawn_locations.sql
@@ -23,14 +23,14 @@ DROP TABLE IF EXISTS `server_battlenpc_spawn_locations`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `server_battlenpc_spawn_locations` (
- `bnpcIndex` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `bnpcId` int(10) unsigned NOT NULL AUTO_INCREMENT,
`customDisplayName` varchar(32) NOT NULL DEFAULT '',
`groupId` int(10) unsigned NOT NULL,
`positionX` float NOT NULL,
`positionY` float NOT NULL,
`positionZ` float NOT NULL,
`rotation` float NOT NULL,
- PRIMARY KEY (`bnpcIndex`)
+ PRIMARY KEY (`bnpcId`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
@@ -56,4 +56,4 @@ commit;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
--- Dump completed on 2017-09-07 21:54:41
+-- Dump completed on 2017-09-10 2:47:43