diff --git a/Source/Framework/Constants/GameObjectConst.cs b/Source/Framework/Constants/GameObjectConst.cs
index cc4bd6409..b69800985 100644
--- a/Source/Framework/Constants/GameObjectConst.cs
+++ b/Source/Framework/Constants/GameObjectConst.cs
@@ -13,7 +13,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
- */
+ */
namespace Framework.Constants
{
@@ -149,4 +149,54 @@ namespace Framework.Constants
TimedOrCorpseDespawn = 0, // despawns after a specified time OR when the summoner dies
TimedDespawn = 1 // despawns after a specified time
}
+
+ public enum GameObjectActions
+ {
+ // Name from client executable // Comments
+ None = 0, // -NONE-
+ AnimateCustom0 = 1, // Animate Custom0
+ AnimateCustom1 = 2, // Animate Custom1
+ AnimateCustom2 = 3, // Animate Custom2
+ AnimateCustom3 = 4, // Animate Custom3
+ Disturb = 5, // Disturb // Triggers trap
+ Unlock = 6, // Unlock // Resets GO_FLAG_LOCKED
+ Lock = 7, // Lock // Sets GO_FLAG_LOCKED
+ Open = 8, // Open // Sets GO_STATE_ACTIVE
+ OpenAndUnlock = 9, // Open + Unlock // Sets GO_STATE_ACTIVE and resets GO_FLAG_LOCKED
+ Close = 10, // Close // Sets GO_STATE_READY
+ ToggleOpen = 11, // Toggle Open
+ Destroy = 12, // Destroy // Sets GO_STATE_DESTROYED
+ Rebuild = 13, // Rebuild // Resets from GO_STATE_DESTROYED
+ Creation = 14, // Creation
+ Despawn = 15, // Despawn
+ MakeInert = 16, // Make Inert // Disables interactions
+ MakeActive = 17, // Make Active // Enables interactions
+ CloseAndLock = 18, // Close + Lock // Sets GO_STATE_READY and sets GO_FLAG_LOCKED
+ UseArtKit0 = 19, // Use ArtKit0 // 46904: 121
+ UseArtKit1 = 20, // Use ArtKit1 // 36639: 81, 46903: 122
+ UseArtKit2 = 21, // Use ArtKit2
+ UseArtKit3 = 22, // Use ArtKit3
+ SetTapList = 23, // Set Tap List
+ GoTo1stFloor = 24, // Go to 1st floor
+ GoTo2ndFloor = 25, // Go to 2nd floor
+ GoTo3rdFloor = 26, // Go to 3rd floor
+ GoTo4thFloor = 27, // Go to 4th floor
+ GoTo5thFloor = 28, // Go to 5th floor
+ GoTo6thFloor = 29, // Go to 6th floor
+ GoTo7thFloor = 30, // Go to 7th floor
+ GoTo8thFloor = 31, // Go to 8th floor
+ GoTo9thFloor = 32, // Go to 9th floor
+ GoTo10thFloor = 33, // Go to 10th floor
+ UseArtKit4 = 34, // Use ArtKit4
+ PlayAnimKit = 35, // Play Anim Kit "{AnimKit}"
+ OpenAndPlayAnimKit = 36, // Open + Play Anim Kit "{AnimKit}"
+ CloseAndPlayAnimKit = 37, // Close + Play Anim Kit "{AnimKit}"
+ PlayOneShotAnimKit = 38, // Play One-shot Anim Kit "{AnimKit}"
+ StopAnimKit = 39, // Stop Anim Kit
+ OpenAndStopAnimKit = 40, // Open + Stop Anim Kit
+ CloseAndStopAnimKit = 41, // Close + Stop Anim Kit
+ PlaySpellVisual = 42, // Play Spell Visual "{SpellVisual}"
+ StopSpellVisual = 43, // Stop Spell Visual
+ SetTappedToChallengePlayers = 44, // Set Tapped to Challenge Players
+ }
}
diff --git a/Source/Framework/Database/Databases/HotfixDatabase.cs b/Source/Framework/Database/Databases/HotfixDatabase.cs
index 47be63cc4..cb1cc2b51 100644
--- a/Source/Framework/Database/Databases/HotfixDatabase.cs
+++ b/Source/Framework/Database/Databases/HotfixDatabase.cs
@@ -422,6 +422,8 @@ namespace Framework.Database
PrepareStatement(HotfixStatements.SEL_FRIENDSHIP_REPUTATION, "SELECT Description, StandingModified, StandingChanged, ID, FactionID, TextureFileID, Flags FROM friendship_reputation");
PrepareStatement(HotfixStatements.SEL_FRIENDSHIP_REPUTATION_LOCALE, "SELECT ID, Description_lang, StandingModified_lang, StandingChanged_lang FROM friendship_reputation_locale WHERE locale = ?");
+ // GameobjectArtKit.db2
+ PrepareStatement(HotfixStatements.SEL_GAMEOBJECT_ART_KIT, "SELECT ID, AttachModelFileID, TextureVariationFileID1, TextureVariationFileID2, TextureVariationFileID3 FROM gameobject_art_kit");
// GameobjectDisplayInfo.db2
PrepareStatement(HotfixStatements.SEL_GAMEOBJECT_DISPLAY_INFO, "SELECT ID, GeoBoxMinX, GeoBoxMinY, GeoBoxMinZ, GeoBoxMaxX, GeoBoxMaxY, GeoBoxMaxZ, " +
@@ -1495,6 +1497,8 @@ namespace Framework.Database
SEL_FRIENDSHIP_REPUTATION,
SEL_FRIENDSHIP_REPUTATION_LOCALE,
+ SEL_GAMEOBJECT_ART_KIT,
+
SEL_GAMEOBJECT_DISPLAY_INFO,
SEL_GAMEOBJECTS,
diff --git a/Source/Game/DataStorage/CliDB.cs b/Source/Game/DataStorage/CliDB.cs
index 531e5e6e9..8d7616934 100644
--- a/Source/Game/DataStorage/CliDB.cs
+++ b/Source/Game/DataStorage/CliDB.cs
@@ -143,6 +143,7 @@ namespace Game.DataStorage
FactionTemplateStorage = ReadDB2("FactionTemplate.db2", HotfixStatements.SEL_FACTION_TEMPLATE);
FriendshipRepReactionStorage = ReadDB2("FriendshipRepReaction.db2", HotfixStatements.SEL_FRIENDSHIP_REP_REACTION, HotfixStatements.SEL_FRIENDSHIP_REP_REACTION_LOCALE);
FriendshipReputationStorage = ReadDB2("FriendshipReputation.db2", HotfixStatements.SEL_FRIENDSHIP_REPUTATION, HotfixStatements.SEL_FRIENDSHIP_REPUTATION_LOCALE);
+ GameObjectArtKitStorage = ReadDB2("GameObjectArtKit.db2", HotfixStatements.SEL_GAMEOBJECT_ART_KIT);
GameObjectDisplayInfoStorage = ReadDB2("GameObjectDisplayInfo.db2", HotfixStatements.SEL_GAMEOBJECT_DISPLAY_INFO);
GameObjectsStorage = ReadDB2("GameObjects.db2", HotfixStatements.SEL_GAMEOBJECTS, HotfixStatements.SEL_GAMEOBJECTS_LOCALE);
GarrAbilityStorage = ReadDB2("GarrAbility.db2", HotfixStatements.SEL_GARR_ABILITY, HotfixStatements.SEL_GARR_ABILITY_LOCALE);
@@ -533,8 +534,9 @@ namespace Game.DataStorage
public static DB6Storage FactionTemplateStorage;
public static DB6Storage FriendshipRepReactionStorage;
public static DB6Storage FriendshipReputationStorage;
- public static DB6Storage GameObjectsStorage;
+ public static DB6Storage GameObjectArtKitStorage;
public static DB6Storage GameObjectDisplayInfoStorage;
+ public static DB6Storage GameObjectsStorage;
public static DB6Storage GarrAbilityStorage;
public static DB6Storage GarrBuildingStorage;
public static DB6Storage GarrBuildingPlotInstStorage;
diff --git a/Source/Game/DataStorage/Structs/G_Records.cs b/Source/Game/DataStorage/Structs/G_Records.cs
index 28655096a..f2353091e 100644
--- a/Source/Game/DataStorage/Structs/G_Records.cs
+++ b/Source/Game/DataStorage/Structs/G_Records.cs
@@ -20,6 +20,13 @@ using System.Numerics;
namespace Game.DataStorage
{
+ public sealed class GameObjectArtKitRecord
+ {
+ public uint Id;
+ public int AttachModelFileID;
+ public int[] TextureVariationFileID = new int[3];
+ }
+
public sealed class GameObjectDisplayInfoRecord
{
public uint Id;
diff --git a/Source/Game/Entities/GameObject/GameObject.cs b/Source/Game/Entities/GameObject/GameObject.cs
index 9ceaff7a2..a44149385 100644
--- a/Source/Game/Entities/GameObject/GameObject.cs
+++ b/Source/Game/Entities/GameObject/GameObject.cs
@@ -2880,7 +2880,7 @@ namespace Game.Entities
SendMessageToSet(activateAnimKit, true);
}
- void SetSpellVisualId(uint spellVisualId, ObjectGuid activatorGuid = default)
+ public void SetSpellVisualId(uint spellVisualId, ObjectGuid activatorGuid = default)
{
SetUpdateFieldValue(m_values.ModifyValue(m_gameObjectData).ModifyValue(m_gameObjectData.SpellVisualID), spellVisualId);
diff --git a/Source/Game/Entities/GameObject/GameObjectData.cs b/Source/Game/Entities/GameObject/GameObjectData.cs
index 30550f846..6dcdd5896 100644
--- a/Source/Game/Entities/GameObject/GameObjectData.cs
+++ b/Source/Game/Entities/GameObject/GameObjectData.cs
@@ -1410,6 +1410,7 @@ namespace Game.Entities
{
public uint Mingold;
public uint Maxgold;
+ public uint[] ArtKits = new uint[5];
public uint WorldEffectID;
public uint AIAnimKitID;
}
diff --git a/Source/Game/Globals/ObjectManager.cs b/Source/Game/Globals/ObjectManager.cs
index ee952ee03..0090f67bc 100644
--- a/Source/Game/Globals/ObjectManager.cs
+++ b/Source/Game/Globals/ObjectManager.cs
@@ -4072,8 +4072,8 @@ namespace Game
{
uint oldMSTime = Time.GetMSTime();
- // 0 1 2 3 4 5 6
- SQLResult result = DB.World.Query("SELECT entry, faction, flags, mingold, maxgold, WorldEffectID, AIAnimKitID FROM gameobject_template_addon");
+ // 0 1 2 3 4 5 6 7 8 9 10 11
+ SQLResult result = DB.World.Query("SELECT entry, faction, flags, mingold, maxgold, artkit0, artkit1, artkit2, artkit3, artkit4, WorldEffectID, AIAnimKitID FROM gameobject_template_addon");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 gameobject template addon definitions. DB table `gameobject_template_addon` is empty.");
@@ -4097,8 +4097,23 @@ namespace Game
gameObjectAddon.Flags = (GameObjectFlags)result.Read(2);
gameObjectAddon.Mingold = result.Read(3);
gameObjectAddon.Maxgold = result.Read(4);
- gameObjectAddon.WorldEffectID = result.Read(5);
- gameObjectAddon.AIAnimKitID = result.Read(6);
+ gameObjectAddon.WorldEffectID = result.Read(10);
+ gameObjectAddon.AIAnimKitID = result.Read(11);
+
+ for (int i = 0; i < gameObjectAddon.ArtKits.Length; ++i)
+ {
+ uint artKitID = result.Read(5 + i);
+ if (artKitID == 0)
+ continue;
+
+ if (!CliDB.GameObjectArtKitStorage.ContainsKey(artKitID))
+ {
+ Log.outError(LogFilter.Sql, $"GameObject (Entry: {entry}) has invalid `artkit{i}` ({artKitID}) defined, set to zero instead.");
+ continue;
+ }
+
+ gameObjectAddon.ArtKits[i] = artKitID;
+ }
// checks
if (gameObjectAddon.Faction != 0 && !CliDB.FactionTemplateStorage.ContainsKey(gameObjectAddon.Faction))
diff --git a/Source/Game/Spells/SpellEffects.cs b/Source/Game/Spells/SpellEffects.cs
index 330f087a4..0fef413f7 100644
--- a/Source/Game/Spells/SpellEffects.cs
+++ b/Source/Game/Spells/SpellEffects.cs
@@ -3020,12 +3020,144 @@ namespace Game.Spells
if (gameObjTarget == null)
return;
- ScriptInfo activateCommand = new();
- activateCommand.command = ScriptCommands.ActivateObject;
+ GameObjectActions action = (GameObjectActions)effectInfo.MiscValue;
- // int unk = effectInfo.MiscValue; // This is set for EffectActivateObject spells; needs research
+ switch (action)
+ {
+ case GameObjectActions.AnimateCustom0:
+ case GameObjectActions.AnimateCustom1:
+ case GameObjectActions.AnimateCustom2:
+ case GameObjectActions.AnimateCustom3:
+ gameObjTarget.SendCustomAnim((uint)(action - GameObjectActions.AnimateCustom0));
+ break;
+ case GameObjectActions.Disturb: // What's the difference with Open?
+ case GameObjectActions.Open:
+ {
+ Unit unitCaster = m_caster.ToUnit();
+ if (unitCaster != null)
+ gameObjTarget.Use(unitCaster);
+ break;
+ }
+ case GameObjectActions.OpenAndUnlock:
+ {
+ Unit unitCaster = m_caster.ToUnit();
+ if (unitCaster != null)
+ gameObjTarget.UseDoorOrButton(0, false, unitCaster);
- gameObjTarget.GetMap().ScriptCommandStart(activateCommand, 0, m_caster, gameObjTarget);
+ gameObjTarget.RemoveFlag(GameObjectFlags.Locked);
+ break;
+ }
+ case GameObjectActions.Unlock:
+ gameObjTarget.RemoveFlag(GameObjectFlags.Locked);
+ break;
+ case GameObjectActions.Lock:
+ gameObjTarget.AddFlag(GameObjectFlags.Locked);
+ break;
+ case GameObjectActions.Close:
+ case GameObjectActions.Rebuild:
+ gameObjTarget.ResetDoorOrButton();
+ break;
+ case GameObjectActions.Despawn:
+ gameObjTarget.DespawnOrUnsummon();
+ break;
+ case GameObjectActions.MakeInert:
+ gameObjTarget.AddFlag(GameObjectFlags.NotSelectable);
+ break;
+ case GameObjectActions.MakeActive:
+ gameObjTarget.RemoveFlag(GameObjectFlags.NotSelectable);
+ break;
+ case GameObjectActions.CloseAndLock:
+ gameObjTarget.ResetDoorOrButton();
+ gameObjTarget.AddFlag(GameObjectFlags.Locked);
+ break;
+ case GameObjectActions.Destroy:
+ {
+ Unit unitCaster = m_caster.ToUnit();
+ if (unitCaster != null)
+ gameObjTarget.UseDoorOrButton(0, true, unitCaster);
+ break;
+ }
+ case GameObjectActions.UseArtKit0:
+ case GameObjectActions.UseArtKit1:
+ case GameObjectActions.UseArtKit2:
+ case GameObjectActions.UseArtKit3:
+ case GameObjectActions.UseArtKit4:
+ {
+ GameObjectTemplateAddon templateAddon = gameObjTarget.GetTemplateAddon();
+
+ uint artKitIndex = action != GameObjectActions.UseArtKit4 ? (uint)(action - GameObjectActions.UseArtKit0) : 4;
+ uint artKitValue = 0;
+ if (templateAddon != null)
+ artKitValue = templateAddon.ArtKits[artKitIndex];
+
+ if (artKitValue == 0)
+ Log.outError(LogFilter.Sql, $"GameObject {gameObjTarget.GetEntry()} hit by spell {m_spellInfo.Id} needs `artkit{artKitIndex}` in `gameobject_template_addon`");
+ else
+ gameObjTarget.SetGoArtKit((byte)artKitValue);
+
+ break;
+ }
+ case GameObjectActions.GoTo1stFloor:
+ case GameObjectActions.GoTo2ndFloor:
+ case GameObjectActions.GoTo3rdFloor:
+ case GameObjectActions.GoTo4thFloor:
+ case GameObjectActions.GoTo5thFloor:
+ case GameObjectActions.GoTo6thFloor:
+ case GameObjectActions.GoTo7thFloor:
+ case GameObjectActions.GoTo8thFloor:
+ case GameObjectActions.GoTo9thFloor:
+ case GameObjectActions.GoTo10thFloor:
+ if (gameObjTarget.GetGoType() == GameObjectTypes.Transport)
+ gameObjTarget.SetTransportState(GameObjectState.TransportStopped, (uint)(action - GameObjectActions.GoTo1stFloor));
+ else
+ Log.outError(LogFilter.Spells, $"Spell {m_spellInfo.Id} targeted non-transport gameobject for transport only action \"Go to Floor\" {action} in effect {effectInfo.EffectIndex}");
+ break;
+ case GameObjectActions.PlayAnimKit:
+ gameObjTarget.SetAnimKitId((ushort)effectInfo.MiscValueB, false);
+ break;
+ case GameObjectActions.OpenAndPlayAnimKit:
+ {
+ Unit unitCaster = m_caster.ToUnit();
+ if (unitCaster != null)
+ gameObjTarget.UseDoorOrButton(0, false, unitCaster);
+ gameObjTarget.SetAnimKitId((ushort)effectInfo.MiscValueB, false);
+ break;
+ }
+ case GameObjectActions.CloseAndPlayAnimKit:
+ gameObjTarget.ResetDoorOrButton();
+ gameObjTarget.SetAnimKitId((ushort)effectInfo.MiscValueB, false);
+ break;
+ case GameObjectActions.PlayOneShotAnimKit:
+ gameObjTarget.SetAnimKitId((ushort)effectInfo.MiscValueB, true);
+ break;
+ case GameObjectActions.StopAnimKit:
+ gameObjTarget.SetAnimKitId(0, false);
+ break;
+ case GameObjectActions.OpenAndStopAnimKit:
+ {
+ Unit unitCaster = m_caster.ToUnit();
+ if (unitCaster != null)
+ gameObjTarget.UseDoorOrButton(0, false, unitCaster);
+ gameObjTarget.SetAnimKitId(0, false);
+ break;
+ }
+ case GameObjectActions.CloseAndStopAnimKit:
+ gameObjTarget.ResetDoorOrButton();
+ gameObjTarget.SetAnimKitId(0, false);
+ break;
+ case GameObjectActions.PlaySpellVisual:
+ gameObjTarget.SetSpellVisualId((ushort)effectInfo.MiscValueB, m_originalCasterGUID);
+ break;
+ case GameObjectActions.StopSpellVisual:
+ gameObjTarget.SetSpellVisualId(0);
+ break;
+ case GameObjectActions.None:
+ Log.outFatal(LogFilter.Spells, $"Spell {m_spellInfo.Id} has action type NONE in effect {effectInfo.EffectIndex}");
+ break;
+ default:
+ Log.outFatal(LogFilter.Spells, $"Spell {m_spellInfo.Id} has unhandled action {action} in effect {effectInfo.EffectIndex}");
+ break;
+ }
}
[SpellEffectHandler(SpellEffectName.ApplyGlyph)]