diff --git a/FFXIVClassic Map Server/Database.cs b/FFXIVClassic Map Server/Database.cs index faa92875..e0e8fd22 100644 --- a/FFXIVClassic Map Server/Database.cs +++ b/FFXIVClassic Map Server/Database.cs @@ -1799,6 +1799,36 @@ namespace FFXIVClassic_Map_Server } return effects; } + + public static void SavePlayerStatusEffects(Player player) + { + 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(); + + // we'll run them all at once instead of one at a time + string queries = ""; + foreach (var effect in player.statusEffects.GetStatusEffects()) + { + var duration = effect.GetDurationMs() + effect.GetStartTime().Millisecond - Program.Tick.Millisecond; + + queries += Environment.NewLine + $"REPLACE INTO characters_statuseffect(characterId, statusId, magnitude, duration, tick, tier, extra) VALUES ({player.actorId}, {effect.GetEffectId()}, {effect.GetMagnitude()}, {duration}, {effect.GetTickMs()}, {effect.GetTier()}, {effect.GetExtra()});"; + } + MySqlCommand cmd = new MySqlCommand(queries, conn); + cmd.ExecuteNonQuery(); + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + } } } diff --git a/FFXIVClassic Map Server/actors/chara/ai/StatusEffect.cs b/FFXIVClassic Map Server/actors/chara/ai/StatusEffect.cs index 47fffda9..713be06f 100644 --- a/FFXIVClassic Map Server/actors/chara/ai/StatusEffect.cs +++ b/FFXIVClassic Map Server/actors/chara/ai/StatusEffect.cs @@ -1,5 +1,6 @@ using FFXIVClassic_Map_Server.Actors; using FFXIVClassic_Map_Server.lua; +using FFXIVClassic_Map_Server.packets.send.actor; using System; using System.Collections.Generic; using System.Linq; @@ -440,6 +441,16 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai return (uint)id; } + public ushort GetEffectIdForCharaWork() + { + return (ushort)(id - 200000); + } + + public DateTime GetStartTime() + { + return startTime; + } + public string GetName() { return name; @@ -480,6 +491,12 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai return (byte)overwrite; } + public void SetStartTime(DateTime time) + { + this.startTime = time; + this.lastTick = time; + } + public void SetOwner(Character owner) { this.owner = owner; diff --git a/FFXIVClassic Map Server/actors/chara/ai/StatusEffectContainer.cs b/FFXIVClassic Map Server/actors/chara/ai/StatusEffectContainer.cs index a5283557..b59e4884 100644 --- a/FFXIVClassic Map Server/actors/chara/ai/StatusEffectContainer.cs +++ b/FFXIVClassic Map Server/actors/chara/ai/StatusEffectContainer.cs @@ -16,11 +16,12 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai { private Character owner; private readonly Dictionary effects; + public static readonly int MAX_EFFECTS = 20; public StatusEffectContainer(Character owner) { this.owner = owner; - this.effects = new Dictionary(20); + this.effects = new Dictionary(); } public void Update(DateTime tick) @@ -54,9 +55,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai (overwritable == (uint)StatusEffectOverwrite.GreaterOrEqualTo && (effect.GetDurationMs() == newEffect.GetDurationMs() || effect.GetMagnitude() == newEffect.GetMagnitude())); } - if (canOverwrite || effects.ContainsKey(effect.GetEffectId())) + if (canOverwrite || effect == null) { - if (!silent || (effect.GetFlags() & (uint)StatusEffectFlags.Silent) == 0) + if (!silent || (effect?.GetFlags() & (uint)StatusEffectFlags.Silent) == 0) { // todo: send packet to client with effect added message } @@ -65,8 +66,17 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai effects.Remove(effect.GetEffectId()); effects.Add(newEffect.GetEffectId(), newEffect); + + // todo: this is retarded.. + { + var index = Array.IndexOf(effects.Values.ToArray(), newEffect); + owner.charaWork.status[index] = effect.GetEffectIdForCharaWork(); + owner.charaWork.statusShownTime[index] = effect.GetDurationMs() / 1000; + this.owner.zone.BroadcastPacketAroundActor(this.owner, SetActorStatusPacket.BuildPacket(this.owner.actorId, (ushort)index, (ushort)effect.GetEffectId())); + } + return true; } - return true; + return false; } public void RemoveStatusEffect(StatusEffect effect, bool silent = false) @@ -76,10 +86,16 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai // send packet to client with effect remove message if (!silent || (effect.GetFlags() & (uint)StatusEffectFlags.Silent) == 0) { - // todo: send packet to client with effect added message + // todo: send packet to client with effect removed message } - // function onLose(actor, effec) + // todo: this is retarded.. + { + var index = Array.IndexOf(effects.Values.ToArray(), effect); + owner.charaWork.status[index] = effect.GetEffectIdForCharaWork(); + this.owner.zone.BroadcastPacketAroundActor(this.owner, SetActorStatusPacket.BuildPacket(owner.actorId, (ushort)index, (ushort)0)); + } + // function onLose(actor, effect LuaEngine.CallLuaStatusEffectFunction(this.owner, effect, "onLose", this.owner, effect); effects.Remove(effect.GetEffectId()); } @@ -158,5 +174,13 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai { return effects.Values; } + + void SaveStatusEffectsToDatabase(StatusEffectFlags removeEffectFlags = StatusEffectFlags.None) + { + if (owner is Player) + { + Database.SavePlayerStatusEffects((Player)owner); + } + } } } diff --git a/FFXIVClassic Map Server/actors/chara/ai/state/AttackState.cs b/FFXIVClassic Map Server/actors/chara/ai/state/AttackState.cs index ae486d89..821abf36 100644 --- a/FFXIVClassic Map Server/actors/chara/ai/state/AttackState.cs +++ b/FFXIVClassic Map Server/actors/chara/ai/state/AttackState.cs @@ -50,7 +50,8 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state { var damage = utils.AttackUtils.CalculateDamage(owner, target); - lua.LuaEngine.GetInstance().CallLuaFunction(owner, target, "onAttack", false, damage); + // onAttack(actor, target, damage) + lua.LuaEngine.CallLuaBattleAction(owner, "onAttack", false, owner, target, damage); //var packet = BattleAction1Packet.BuildPacket(owner.actorId, target.actorId); diff --git a/FFXIVClassic Map Server/actors/chara/player/Player.cs b/FFXIVClassic Map Server/actors/chara/player/Player.cs index 42715695..6bd200c0 100644 --- a/FFXIVClassic Map Server/actors/chara/player/Player.cs +++ b/FFXIVClassic Map Server/actors/chara/player/Player.cs @@ -684,6 +684,7 @@ namespace FFXIVClassic_Map_Server.Actors //Save Player Database.SavePlayerPlayTime(this); Database.SavePlayerPosition(this); + Database.SavePlayerStatusEffects(this); } public void CleanupAndSave(uint destinationZone, ushort spawnType, float destinationX, float destinationY, float destinationZ, float destinationRot) @@ -706,7 +707,9 @@ namespace FFXIVClassic_Map_Server.Actors //Save Player Database.SavePlayerPlayTime(this); - Database.SavePlayerPosition(this); + Database.SavePlayerPosition(this); + this.statusEffects.RemoveStatusEffectsByFlags((uint)StatusEffectFlags.LoseOnZoning, true); + Database.SavePlayerStatusEffects(this); } public Area GetZone() @@ -721,13 +724,16 @@ namespace FFXIVClassic_Map_Server.Actors public void Logout() { + // todo: really this should be in CleanupAndSave but we might want logout/disconnect handled separately for some effects QueuePacket(LogoutPacket.BuildPacket(actorId)); + statusEffects.RemoveStatusEffectsByFlags((uint)StatusEffectFlags.LoseOnLogout); CleanupAndSave(); } public void QuitGame() { QueuePacket(QuitPacket.BuildPacket(actorId)); + statusEffects.RemoveStatusEffectsByFlags((uint)StatusEffectFlags.LoseOnLogout); CleanupAndSave(); } diff --git a/FFXIVClassic Map Server/lua/LuaEngine.cs b/FFXIVClassic Map Server/lua/LuaEngine.cs index c5780a9a..b94ef340 100644 --- a/FFXIVClassic Map Server/lua/LuaEngine.cs +++ b/FFXIVClassic Map Server/lua/LuaEngine.cs @@ -134,32 +134,33 @@ namespace FFXIVClassic_Map_Server.lua /// // todo: this is dumb, should probably make a function for each action with different default return values /// or just make generic function and pass default value as first arg after functionName /// - public static void CallLuaMonsterAction(Character actor, string functionName, params object[] args) + public static void CallLuaBattleAction(Character actor, string functionName, params object[] args) { + string path = $"./scripts/unique/{actor.zone.zoneName}/Monster/{actor.customDisplayName}.lua"; + // todo: should we call this for players too? - if (actor is BattleNpc) + if (actor is Player) { // todo: check this is correct - string path = $"./scripts/unique/{actor.zone.zoneName}/Monster/{actor.customDisplayName}.lua"; - - // dont wanna throw an error if file doesnt exist - if (File.Exists(path)) + path = FILEPATH_PLAYER; + } + // dont wanna throw an error if file doesnt exist + if (File.Exists(path)) + { + var script = LoadGlobals(); + try { - var script = LoadGlobals(); - try - { - script.DoFile(path); - } - catch (Exception e) - { - Program.Log.Error($"LuaEngine.CallLuaMonsterAction [{functionName}] {e.Message}"); - } - DynValue res = new DynValue(); + script.DoFile(path); + } + catch (Exception e) + { + Program.Log.Error($"LuaEngine.CallLuaBattleAction [{functionName}] {e.Message}"); + } + DynValue res = new DynValue(); - if (!script.Globals.Get(functionName).IsNil()) - { - res = script.Call(script.Globals.Get(functionName), args); - } + if (!script.Globals.Get(functionName).IsNil()) + { + res = script.Call(script.Globals.Get(functionName), args); } } } @@ -390,42 +391,41 @@ namespace FFXIVClassic_Map_Server.lua return null; } - public void CallLuaFunction(Actor actor, Actor target, string funcName, bool optional, params object[] args) + public void CallLuaFunction(Player player, Actor target, string funcName, bool optional, params object[] args) { - bool isPlayer = actor is Player; //Need a seperate case for NPCs cause that child/parent thing. - if (target is Npc && isPlayer) + if (target is Npc) { - CallLuaFunctionNpc((Player)actor, (Npc)target, funcName, optional, args); + CallLuaFunctionNpc(player, (Npc)target, funcName, optional, args); return; } object[] args2 = new object[args.Length + 2]; Array.Copy(args, 0, args2, 2, args.Length); - args2[0] = actor; + args2[0] = player; args2[1] = target; string luaPath = GetScriptPath(target); LuaScript script = LoadScript(luaPath); if (script != null) { - if (!script.Globals.Get(funcName).IsNil() && isPlayer) + if (!script.Globals.Get(funcName).IsNil()) { Coroutine coroutine = script.CreateCoroutine(script.Globals[funcName]).Coroutine; DynValue value = coroutine.Resume(args2); - ResolveResume((Player)actor, coroutine, value); + ResolveResume(player, coroutine, value); } else { if (!optional) - SendError((Player)actor, String.Format("Could not find function '{0}' for actor {1}.", funcName, target.GetName())); + SendError(player, String.Format("Could not find function '{0}' for actor {1}.", funcName, target.GetName())); } } else { - if (!(target is Area) && !optional && isPlayer) - SendError((Player)actor, String.Format("Could not find script for actor {0}.", target.GetName())); - } + if (!(target is Area) && !optional) + SendError(player, String.Format("Could not find script for actor {0}.", target.GetName())); + } } public void EventStarted(Player player, Actor target, EventStartPacket eventStart) @@ -456,18 +456,16 @@ namespace FFXIVClassic_Map_Server.lua } } - public DynValue ResolveResume(Actor actor, Coroutine coroutine, DynValue value) + public DynValue ResolveResume(Player player, Coroutine coroutine, DynValue value) { - var isPlayer = actor is Player; - if (value == null || value.IsVoid()) return value; - if (isPlayer && value.String != null && value.String.Equals("_WAIT_EVENT")) + if (player != null && value.String != null && value.String.Equals("_WAIT_EVENT")) { - GetInstance().AddWaitEventCoroutine((Player)actor, coroutine); + GetInstance().AddWaitEventCoroutine(player, coroutine); } - else if (isPlayer && value.Tuple != null && value.Tuple.Length >= 1 && value.Tuple[0].String != null) + else if (player != null && value.Tuple != null && value.Tuple.Length >= 1 && value.Tuple[0].String != null) { switch (value.Tuple[0].String) { @@ -619,12 +617,12 @@ namespace FFXIVClassic_Map_Server.lua LuaParam.Insert(1, i - (playerNull ? 2 : 0)); // run the script - script.Call(script.Globals["onTrigger"], LuaParam.ToArray()); + //script.Call(script.Globals["onTrigger"], LuaParam.ToArray()); // gm commands dont need to be coroutines? - //Coroutine coroutine = script.CreateCoroutine(script.Globals["onTrigger"]).Coroutine; - //DynValue value = coroutine.Resume(LuaParam.ToArray()); - //ResolveResume(player, coroutine, value); + Coroutine coroutine = script.CreateCoroutine(script.Globals["onTrigger"]).Coroutine; + DynValue value = coroutine.Resume(LuaParam.ToArray()); + LuaEngine.GetInstance().ResolveResume(player, coroutine, value); return; } } diff --git a/sql/status_effects.sql b/sql/status_effects.sql index 2679587e..40f6db9a 100644 --- a/sql/status_effects.sql +++ b/sql/status_effects.sql @@ -16,13 +16,13 @@ /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; -- --- Table structure for table `status_effect` +-- Table structure for table `status_effects` -- -DROP TABLE IF EXISTS `status_effect`; +DROP TABLE IF EXISTS `status_effects`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; -CREATE TABLE `status_effect` ( +CREATE TABLE `status_effects` ( `id` smallint(5) unsigned NOT NULL, `name` varchar(128) NOT NULL, `flags` int(10) unsigned NOT NULL, @@ -32,12 +32,12 @@ CREATE TABLE `status_effect` ( /*!40101 SET character_set_client = @saved_cs_client */; -- --- Dumping data for table `status_effect` +-- Dumping data for table `status_effects` -- -LOCK TABLES `status_effect` WRITE; -/*!40000 ALTER TABLE `status_effect` DISABLE KEYS */; -/*!40000 ALTER TABLE `status_effect` ENABLE KEYS */; +LOCK TABLES `status_effects` WRITE; +/*!40000 ALTER TABLE `status_effects` DISABLE KEYS */; +/*!40000 ALTER TABLE `status_effects` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;