diff --git a/Source/Game/Entities/Player/Player.cs b/Source/Game/Entities/Player/Player.cs index 07e2fd066..5fec09408 100644 --- a/Source/Game/Entities/Player/Player.cs +++ b/Source/Game/Entities/Player/Player.cs @@ -2913,6 +2913,8 @@ namespace Game.Entities packet.AreaID = zoneid; packet.SubareaID = areaid; + Global.WorldStateMgr.FillInitialWorldStates(packet, GetMap()); + packet.AddState(2264, 0); // SCOURGE_EVENT_WORLDSTATE_EASTERN_PLAGUELANDS packet.AddState(2263, 0); // SCOURGE_EVENT_WORLDSTATE_TANARIS packet.AddState(2262, 0); // SCOURGE_EVENT_WORLDSTATE_BURNING_STEPPES diff --git a/Source/Game/Globals/Global.cs b/Source/Game/Globals/Global.cs index 472345ffe..5a72ba2d8 100644 --- a/Source/Game/Globals/Global.cs +++ b/Source/Game/Globals/Global.cs @@ -115,4 +115,6 @@ public static class Global public static SupportManager SupportMgr { get { return SupportManager.Instance; } } public static WardenCheckManager WardenCheckMgr { get { return WardenCheckManager.Instance; } } public static BlackMarketManager BlackMarketMgr { get { return BlackMarketManager.Instance; } } + + public static WorldStateManager WorldStateMgr { get { return WorldStateManager.Instance; } } } diff --git a/Source/Game/Maps/Map.cs b/Source/Game/Maps/Map.cs index 7b822b83c..f8e11188e 100644 --- a/Source/Game/Maps/Map.cs +++ b/Source/Game/Maps/Map.cs @@ -87,6 +87,8 @@ namespace Game.Maps Global.MMapMgr.LoadMapInstance(Global.WorldMgr.GetDataPath(), GetId(), i_InstanceId); + _worldStateValues = Global.WorldStateMgr.GetInitialWorldStatesForMap(this); + Global.ScriptMgr.OnCreateMap(this); } @@ -574,6 +576,29 @@ namespace Game.Maps Cell cell = new(player.GetPositionX(), player.GetPositionY()); GetMultiPersonalPhaseTracker().OnOwnerPhaseChanged(player, GetGrid(cell.GetGridX(), cell.GetGridY()), this, cell); } + + public int GetWorldStateValue(int worldStateId) + { + return _worldStateValues.LookupByKey(worldStateId); + } + + public Dictionary GetWorldStateValues() { return _worldStateValues; } + + public void SetWorldStateValue(int worldStateId, int value) + { + int oldValue = _worldStateValues.LookupByKey(worldStateId); + _worldStateValues[worldStateId] = value; + + WorldStateTemplate worldStateTemplate = Global.WorldStateMgr.GetWorldStateTemplate(worldStateId); + if (worldStateTemplate != null) + Global.ScriptMgr.OnWorldStateValueChange(worldStateTemplate, oldValue, value, this); + + // Broadcast update to all players on the map + UpdateWorldState updateWorldState = new(); + updateWorldState.VariableID = (uint)worldStateId; + updateWorldState.Value = value; + SendToPlayers(updateWorldState); + } void InitializeObject(WorldObject obj) { @@ -5257,6 +5282,8 @@ namespace Game.Maps Queue _farSpellCallbacks = new(); MultiPersonalPhaseTracker _multiPersonalPhaseTracker = new(); + + Dictionary _worldStateValues = new(); #endregion } diff --git a/Source/Game/Networking/Packets/WorldStatePackets.cs b/Source/Game/Networking/Packets/WorldStatePackets.cs index 39cc67170..ebf7410c8 100644 --- a/Source/Game/Networking/Packets/WorldStatePackets.cs +++ b/Source/Game/Networking/Packets/WorldStatePackets.cs @@ -48,6 +48,11 @@ namespace Game.Networking.Packets Worldstates.Add(new WorldStateInfo(variableID, (int)value)); } + public void AddState(int variableID, int value) + { + Worldstates.Add(new WorldStateInfo((uint)variableID, value)); + } + public void AddState(WorldStates variableID, bool value) { AddState((uint)variableID, value); diff --git a/Source/Game/Scripting/CoreScripts.cs b/Source/Game/Scripting/CoreScripts.cs index 262e9edb9..9f258fc39 100644 --- a/Source/Game/Scripting/CoreScripts.cs +++ b/Source/Game/Scripting/CoreScripts.cs @@ -843,4 +843,17 @@ namespace Game.Scripting // Called when a quest objective data change public virtual void OnQuestObjectiveChange(Player player, Quest quest, QuestObjective objective, int oldAmount, int newAmount) { } } + + public class WorldStateScript : ScriptObject + { + public WorldStateScript(string name) : base(name) + { + Global.ScriptMgr.AddScript(this); + } + + public override bool IsDatabaseBound() { return true; } + + // Called when worldstate changes value, map is optional + public virtual void OnValueChange(int worldStateId, int oldValue, int newValue, Map map) { } + } } diff --git a/Source/Game/Scripting/ScriptManager.cs b/Source/Game/Scripting/ScriptManager.cs index 7d63d0a6f..925d717fc 100644 --- a/Source/Game/Scripting/ScriptManager.cs +++ b/Source/Game/Scripting/ScriptManager.cs @@ -1198,6 +1198,14 @@ namespace Game.Scripting RunScript(script => script.OnQuestObjectiveChange(player, quest, objective, oldAmount, newAmount), quest.ScriptId); } + // WorldState + public void OnWorldStateValueChange(WorldStateTemplate worldStateTemplate, int oldValue, int newValue, Map map) + { + Cypher.Assert(worldStateTemplate != null); + + RunScript(script => script.OnValueChange(worldStateTemplate.Id, oldValue, newValue, map), worldStateTemplate.ScriptId); + } + public void ForEach(Action a) where T : ScriptObject { var reg = GetScriptRegistry(); diff --git a/Source/Game/Server/WorldManager.cs b/Source/Game/World/WorldManager.cs similarity index 99% rename from Source/Game/Server/WorldManager.cs rename to Source/Game/World/WorldManager.cs index 0caef01e2..fb4ee6fe3 100644 --- a/Source/Game/Server/WorldManager.cs +++ b/Source/Game/World/WorldManager.cs @@ -889,6 +889,9 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loading Creature Formations..."); FormationMgr.LoadCreatureFormations(); + Log.outInfo(LogFilter.ServerLoading, "Loading World State templates..."); + Global.WorldStateMgr.LoadFromDB(); + Log.outInfo(LogFilter.ServerLoading, "Loading World States..."); // must be loaded before Battleground, outdoor PvP and conditions LoadWorldStates(); diff --git a/Source/Game/World/WorldStateManager.cs b/Source/Game/World/WorldStateManager.cs new file mode 100644 index 000000000..d091d12ad --- /dev/null +++ b/Source/Game/World/WorldStateManager.cs @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2012-2020 CypherCore + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Database; +using Game.Maps; +using Game.Networking.Packets; +using System.Collections.Generic; + +namespace Game +{ + public class WorldStateManager : Singleton + { + Dictionary _worldStateTemplates = new(); + Dictionary _realmWorldStateValues = new(); + Dictionary> _worldStatesByMap = new(); + + WorldStateManager() { } + + public void LoadFromDB() + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 2 3 + SQLResult result = DB.World.Query("SELECT ID, DefaultValue, MapID, ScriptName FROM world_state"); + if (result.IsEmpty()) + return; + + do + { + int id = result.Read(0); + WorldStateTemplate worldState = new(); + worldState.Id = id; + worldState.DefaultValue = result.Read(1); + if (!result.IsNull(2)) + worldState.MapId = result.Read(2); + + worldState.ScriptId = Global.ObjectMgr.GetScriptId(result.Read(3)); + + if (worldState.MapId.HasValue) + { + if (!_worldStatesByMap.ContainsKey(worldState.MapId.Value)) + _worldStatesByMap[worldState.MapId.Value] = new(); + + _worldStatesByMap[worldState.MapId.Value][id] = worldState.DefaultValue; + } + else + _realmWorldStateValues[id] = worldState.DefaultValue; + + _worldStateTemplates[id] = worldState; + + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, $"Loaded {_worldStateTemplates.Count} world state templates {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); + } + + public WorldStateTemplate GetWorldStateTemplate(int worldStateId) + { + return _worldStateTemplates.LookupByKey(worldStateId); + } + + public int GetValue(int worldStateId, Map map) + { + WorldStateTemplate worldStateTemplate = GetWorldStateTemplate(worldStateId); + if (worldStateTemplate == null || !worldStateTemplate.MapId.HasValue) + return _realmWorldStateValues.LookupByKey(worldStateId); + + if (map.GetId() != worldStateTemplate.MapId) + return 0; + + return map.GetWorldStateValue(worldStateId); + } + + public void SetValue(int worldStateId, int value, Map map) + { + WorldStateTemplate worldStateTemplate = GetWorldStateTemplate(worldStateId); + if (worldStateTemplate == null || !worldStateTemplate.MapId.HasValue) + { + int oldValue = _realmWorldStateValues.LookupByKey(worldStateId); + _realmWorldStateValues[worldStateId] = value; + + if (worldStateTemplate != null) + Global.ScriptMgr.OnWorldStateValueChange(worldStateTemplate, oldValue, value, null); + + // Broadcast update to all players on the server + UpdateWorldState updateWorldState = new(); + updateWorldState.VariableID = (uint)worldStateId; + updateWorldState.Value = value; + Global.WorldMgr.SendGlobalMessage(updateWorldState); + return; + } + + if (map.GetId() != worldStateTemplate.MapId) + return; + + map.SetWorldStateValue(worldStateId, value); + } + + public Dictionary GetInitialWorldStatesForMap(Map map) + { + if (_worldStatesByMap.TryGetValue(map.GetId(), out Dictionary initialValues)) + return initialValues; + + return new Dictionary(); + } + + public void FillInitialWorldStates(InitWorldStates initWorldStates, Map map) + { + foreach (var (worldStateId, value) in _realmWorldStateValues) + initWorldStates.AddState(worldStateId, value); + + foreach (var (worldStateId, value) in map.GetWorldStateValues()) + initWorldStates.AddState(worldStateId, value); + } + } + + public class WorldStateTemplate + { + public int Id; + public int DefaultValue; + public uint ScriptId; + + public uint? MapId; + } +} diff --git a/sql/updates/world/master/2022_06_25_01_world.sql b/sql/updates/world/master/2022_06_25_01_world.sql new file mode 100644 index 000000000..72693acf0 --- /dev/null +++ b/sql/updates/world/master/2022_06_25_01_world.sql @@ -0,0 +1,19 @@ +-- +-- Table structure for table `world_state` +-- +DROP TABLE IF EXISTS `world_state`; +CREATE TABLE `world_state` ( + `ID` int NOT NULL, + `DefaultValue` int NOT NULL, + `MapID` int unsigned DEFAULT NULL, + `ScriptName` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `Comment` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + PRIMARY KEY (`ID`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +INSERT INTO `world_state` (`ID`, `DefaultValue`, `MapID`, `Comment`) VALUES +(5644, 0, 669, 'Blackwing Descent - Omnotron Defense System - Achieve-a-tron'), +(5645, 0, 669, 'Blackwing Descent - Omnotron Defense System - Achieve-a-tron'), +(5646, 0, 669, 'Blackwing Descent - Omnotron Defense System - Achieve-a-tron'), +(5647, 0, 669, 'Blackwing Descent - Omnotron Defense System - Achieve-a-tron'), +(5117, 0, 670, 'Grim Batol - Erudax - Don\'t need to Break Eggs to Make an Omelet');