fix(Core/Spells): Implement SPELL_EFFECT_ACTIVATE_OBJECT (#11648)
Co-authored-by: jackpoz <giacomopoz@gmail.com>
This commit is contained in:
@@ -0,0 +1,56 @@
|
|||||||
|
--
|
||||||
|
ALTER TABLE `gameobject_template_addon`
|
||||||
|
ADD COLUMN `artkit0` INT NOT NULL DEFAULT 0 AFTER `maxgold`,
|
||||||
|
ADD COLUMN `artkit1` INT NOT NULL DEFAULT 0 AFTER `artkit0`,
|
||||||
|
ADD COLUMN `artkit2` INT NOT NULL DEFAULT 0 AFTER `artkit1`,
|
||||||
|
ADD COLUMN `artkit3` INT NOT NULL DEFAULT 0 AFTER `artkit2`;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `gameobjectartkit_dbc`;
|
||||||
|
|
||||||
|
CREATE TABLE `gameobjectartkit_dbc`
|
||||||
|
(
|
||||||
|
`ID` INT NOT NULL DEFAULT 0,
|
||||||
|
`Texture_1` INT NOT NULL DEFAULT 0,
|
||||||
|
`Texture_2` INT NOT NULL DEFAULT 0,
|
||||||
|
`Texture_3` INT NOT NULL DEFAULT 0,
|
||||||
|
`Attach_Model_1` INT NOT NULL DEFAULT 0,
|
||||||
|
`Attach_Model_2` INT NOT NULL DEFAULT 0,
|
||||||
|
`Attach_Model_3` INT NOT NULL DEFAULT 0,
|
||||||
|
`Attach_Model_4` INT NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (`ID`)
|
||||||
|
) ENGINE=MYISAM DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
-- Note: All of these should be targetable by spells 46904 and 46903, but conditions are only set for Stormwind (damn Horde fanatics)
|
||||||
|
UPDATE `gameobject_template_addon` SET `artkit0` = 121, `artkit1` = 122 WHERE `entry` IN (
|
||||||
|
188352, -- Flame of Shattrath
|
||||||
|
188129, -- Flame of Silvermoon
|
||||||
|
188128, -- Flame of the Exodar
|
||||||
|
181567, -- Flame of the Wetlands
|
||||||
|
181566, -- Flame of Hillsbrad
|
||||||
|
181565, -- Flame of Westfall
|
||||||
|
181564, -- Flame of Silverpine
|
||||||
|
181563, -- Flame of Darkshore
|
||||||
|
181562, -- Flame of Stonetalon
|
||||||
|
181561, -- Flame of Ashenvale
|
||||||
|
181560, -- Flame of the Barrens
|
||||||
|
181349, -- Flame of the Scholomance
|
||||||
|
181348, -- Flame of Stratholme
|
||||||
|
181347, -- Flame of Blackrock Spire
|
||||||
|
181346, -- Flame of Dire Maul
|
||||||
|
181345, -- Flame of the Hinterlands
|
||||||
|
181344, -- Flame of the Blasted Lands
|
||||||
|
181343, -- Flame of Un'Goro
|
||||||
|
181342, -- Flame of Azshara
|
||||||
|
181341, -- Flame of Searing Gorge
|
||||||
|
181340, -- Flame of Winterspring
|
||||||
|
181339, -- Flame of Silithus
|
||||||
|
181338, -- Flame of the Plaguelands
|
||||||
|
181337, -- Flame of Thunder Bluff
|
||||||
|
181336, -- Flame of Orgrimmar
|
||||||
|
181335, -- Flame of the Undercity
|
||||||
|
181334, -- Flame of Darnassus
|
||||||
|
181333, -- Flame of Ironforge
|
||||||
|
181332 -- Flame of Stormwind
|
||||||
|
);
|
||||||
|
|
||||||
|
DELETE FROM `spell_script_names` WHERE `ScriptName`= "spell_banging_the_gong";
|
||||||
@@ -76,6 +76,8 @@ static FactionTeamMap sFactionTeamMap;
|
|||||||
DBCStorage <FactionEntry> sFactionStore(FactionEntryfmt);
|
DBCStorage <FactionEntry> sFactionStore(FactionEntryfmt);
|
||||||
DBCStorage <FactionTemplateEntry> sFactionTemplateStore(FactionTemplateEntryfmt);
|
DBCStorage <FactionTemplateEntry> sFactionTemplateStore(FactionTemplateEntryfmt);
|
||||||
|
|
||||||
|
DBCStorage <GameObjectArtKitEntry> sGameObjectArtKitStore(GameObjectArtKitfmt);
|
||||||
|
|
||||||
DBCStorage <GameObjectDisplayInfoEntry> sGameObjectDisplayInfoStore(GameObjectDisplayInfofmt);
|
DBCStorage <GameObjectDisplayInfoEntry> sGameObjectDisplayInfoStore(GameObjectDisplayInfofmt);
|
||||||
DBCStorage <GemPropertiesEntry> sGemPropertiesStore(GemPropertiesEntryfmt);
|
DBCStorage <GemPropertiesEntry> sGemPropertiesStore(GemPropertiesEntryfmt);
|
||||||
DBCStorage <GlyphPropertiesEntry> sGlyphPropertiesStore(GlyphPropertiesfmt);
|
DBCStorage <GlyphPropertiesEntry> sGlyphPropertiesStore(GlyphPropertiesfmt);
|
||||||
@@ -294,6 +296,7 @@ void LoadDBCStores(const std::string& dataPath)
|
|||||||
LOAD_DBC(sEmotesTextStore, "EmotesText.dbc", "emotestext_dbc");
|
LOAD_DBC(sEmotesTextStore, "EmotesText.dbc", "emotestext_dbc");
|
||||||
LOAD_DBC(sFactionStore, "Faction.dbc", "faction_dbc");
|
LOAD_DBC(sFactionStore, "Faction.dbc", "faction_dbc");
|
||||||
LOAD_DBC(sFactionTemplateStore, "FactionTemplate.dbc", "factiontemplate_dbc");
|
LOAD_DBC(sFactionTemplateStore, "FactionTemplate.dbc", "factiontemplate_dbc");
|
||||||
|
LOAD_DBC(sGameObjectArtKitStore, "GameObjectArtKit.dbc", "gameobjectartkit_dbc");
|
||||||
LOAD_DBC(sGameObjectDisplayInfoStore, "GameObjectDisplayInfo.dbc", "gameobjectdisplayinfo_dbc");
|
LOAD_DBC(sGameObjectDisplayInfoStore, "GameObjectDisplayInfo.dbc", "gameobjectdisplayinfo_dbc");
|
||||||
LOAD_DBC(sGemPropertiesStore, "GemProperties.dbc", "gemproperties_dbc");
|
LOAD_DBC(sGemPropertiesStore, "GemProperties.dbc", "gemproperties_dbc");
|
||||||
LOAD_DBC(sGlyphPropertiesStore, "GlyphProperties.dbc", "glyphproperties_dbc");
|
LOAD_DBC(sGlyphPropertiesStore, "GlyphProperties.dbc", "glyphproperties_dbc");
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ extern DBCStorage <EmotesEntry> sEmotesStore;
|
|||||||
extern DBCStorage <EmotesTextEntry> sEmotesTextStore;
|
extern DBCStorage <EmotesTextEntry> sEmotesTextStore;
|
||||||
extern DBCStorage <FactionEntry> sFactionStore;
|
extern DBCStorage <FactionEntry> sFactionStore;
|
||||||
extern DBCStorage <FactionTemplateEntry> sFactionTemplateStore;
|
extern DBCStorage <FactionTemplateEntry> sFactionTemplateStore;
|
||||||
|
extern DBCStorage <GameObjectArtKitEntry> sGameObjectArtKitStore;
|
||||||
extern DBCStorage <GameObjectDisplayInfoEntry> sGameObjectDisplayInfoStore;
|
extern DBCStorage <GameObjectDisplayInfoEntry> sGameObjectDisplayInfoStore;
|
||||||
extern DBCStorage <GemPropertiesEntry> sGemPropertiesStore;
|
extern DBCStorage <GemPropertiesEntry> sGemPropertiesStore;
|
||||||
extern DBCStorage <GlyphPropertiesEntry> sGlyphPropertiesStore;
|
extern DBCStorage <GlyphPropertiesEntry> sGlyphPropertiesStore;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#include "Object.h"
|
#include "Object.h"
|
||||||
#include "SharedDefines.h"
|
#include "SharedDefines.h"
|
||||||
#include "Unit.h"
|
#include "Unit.h"
|
||||||
|
#include <array>
|
||||||
|
|
||||||
class GameObjectAI;
|
class GameObjectAI;
|
||||||
class Transport;
|
class Transport;
|
||||||
@@ -676,6 +677,7 @@ struct GameObjectTemplateAddon
|
|||||||
uint32 flags;
|
uint32 flags;
|
||||||
uint32 mingold;
|
uint32 mingold;
|
||||||
uint32 maxgold;
|
uint32 maxgold;
|
||||||
|
std::array<uint32, 4> artKits = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Benchmarked: Faster than std::map (insert/find)
|
// Benchmarked: Faster than std::map (insert/find)
|
||||||
@@ -736,6 +738,35 @@ enum GOState
|
|||||||
|
|
||||||
#define MAX_GO_STATE 3
|
#define MAX_GO_STATE 3
|
||||||
|
|
||||||
|
enum class GameObjectActions : uint32
|
||||||
|
{
|
||||||
|
// Name from client executable // Comments
|
||||||
|
None, // -NONE-
|
||||||
|
AnimateCustom0, // Animate Custom0
|
||||||
|
AnimateCustom1, // Animate Custom1
|
||||||
|
AnimateCustom2, // Animate Custom2
|
||||||
|
AnimateCustom3, // Animate Custom3
|
||||||
|
Disturb, // Disturb // Triggers trap
|
||||||
|
Unlock, // Unlock // Resets GO_FLAG_LOCKED
|
||||||
|
Lock, // Lock // Sets GO_FLAG_LOCKED
|
||||||
|
Open, // Open // Sets GO_STATE_ACTIVE
|
||||||
|
OpenAndUnlock, // Open + Unlock // Sets GO_STATE_ACTIVE and resets GO_FLAG_LOCKED
|
||||||
|
Close, // Close // Sets GO_STATE_READY
|
||||||
|
ToggleOpen, // Toggle Open
|
||||||
|
Destroy, // Destroy // Sets GO_STATE_DESTROYED
|
||||||
|
Rebuild, // Rebuild // Resets from GO_STATE_DESTROYED
|
||||||
|
Creation, // Creation
|
||||||
|
Despawn, // Despawn
|
||||||
|
MakeInert, // Make Inert // Disables interactions
|
||||||
|
MakeActive, // Make Active // Enables interactions
|
||||||
|
CloseAndLock, // Close + Lock // Sets GO_STATE_READY and sets GO_FLAG_LOCKED
|
||||||
|
UseArtKit0, // Use ArtKit0 // 46904: 121
|
||||||
|
UseArtKit1, // Use ArtKit1 // 36639: 81, 46903: 122
|
||||||
|
UseArtKit2, // Use ArtKit2
|
||||||
|
UseArtKit3, // Use ArtKit3
|
||||||
|
SetTapList, // Set Tap List
|
||||||
|
};
|
||||||
|
|
||||||
// from `gameobject`
|
// from `gameobject`
|
||||||
struct GameObjectData
|
struct GameObjectData
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7183,8 +7183,8 @@ void ObjectMgr::LoadGameObjectTemplateAddons()
|
|||||||
{
|
{
|
||||||
uint32 oldMSTime = getMSTime();
|
uint32 oldMSTime = getMSTime();
|
||||||
|
|
||||||
// 0 1 2 3 4
|
// 0 1 2 3 4 5 6 7 8
|
||||||
QueryResult result = WorldDatabase.Query("SELECT entry, faction, flags, mingold, maxgold FROM gameobject_template_addon");
|
QueryResult result = WorldDatabase.Query("SELECT entry, faction, flags, mingold, maxgold, artkit0, artkit1, artkit2, artkit3 FROM gameobject_template_addon");
|
||||||
|
|
||||||
if (!result)
|
if (!result)
|
||||||
{
|
{
|
||||||
@@ -7215,6 +7215,21 @@ void ObjectMgr::LoadGameObjectTemplateAddons()
|
|||||||
gameObjectAddon.mingold = fields[3].Get<uint32>();
|
gameObjectAddon.mingold = fields[3].Get<uint32>();
|
||||||
gameObjectAddon.maxgold = fields[4].Get<uint32>();
|
gameObjectAddon.maxgold = fields[4].Get<uint32>();
|
||||||
|
|
||||||
|
for (uint32 i = 0; i < gameObjectAddon.artKits.size(); i++)
|
||||||
|
{
|
||||||
|
uint32 artKitID = fields[5 + i].Get<uint32>();
|
||||||
|
if (!artKitID)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!sGameObjectArtKitStore.LookupEntry(artKitID))
|
||||||
|
{
|
||||||
|
LOG_ERROR("sql.sql", "GameObject (Entry: {}) has invalid `artkit{}` {} defined, set to zero instead.", entry, i, artKitID);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
gameObjectAddon.artKits[i] = artKitID;
|
||||||
|
}
|
||||||
|
|
||||||
// checks
|
// checks
|
||||||
if (gameObjectAddon.faction && !sFactionTemplateStore.LookupEntry(gameObjectAddon.faction))
|
if (gameObjectAddon.faction && !sFactionTemplateStore.LookupEntry(gameObjectAddon.faction))
|
||||||
LOG_ERROR("sql.sql",
|
LOG_ERROR("sql.sql",
|
||||||
|
|||||||
@@ -4263,7 +4263,7 @@ void Spell::EffectSummonPlayer(SpellEffIndex /*effIndex*/)
|
|||||||
player->GetSession()->SendPacket(&data);
|
player->GetSession()->SendPacket(&data);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spell::EffectActivateObject(SpellEffIndex /*effIndex*/)
|
void Spell::EffectActivateObject(SpellEffIndex effIndex)
|
||||||
{
|
{
|
||||||
if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET)
|
if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET)
|
||||||
return;
|
return;
|
||||||
@@ -4271,17 +4271,74 @@ void Spell::EffectActivateObject(SpellEffIndex /*effIndex*/)
|
|||||||
if (!gameObjTarget)
|
if (!gameObjTarget)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Player* player = m_caster->GetTypeId() == TYPEID_PLAYER ? m_caster->ToPlayer() : m_caster->GetCharmerOrOwnerPlayerOrPlayerItself();
|
GameObjectActions action = GameObjectActions(m_spellInfo->Effects[effIndex].MiscValue);
|
||||||
gameObjTarget->Use(player ? player : m_caster);
|
switch (action)
|
||||||
|
{
|
||||||
|
case GameObjectActions::AnimateCustom0:
|
||||||
|
case GameObjectActions::AnimateCustom1:
|
||||||
|
case GameObjectActions::AnimateCustom2:
|
||||||
|
case GameObjectActions::AnimateCustom3:
|
||||||
|
gameObjTarget->SendCustomAnim(uint32(action) - uint32(GameObjectActions::AnimateCustom0));
|
||||||
|
break;
|
||||||
|
case GameObjectActions::Disturb: // What's the difference with Open?
|
||||||
|
case GameObjectActions::Open:
|
||||||
|
if (Unit* unitCaster = m_caster->ToUnit())
|
||||||
|
gameObjTarget->Use(unitCaster);
|
||||||
|
break;
|
||||||
|
case GameObjectActions::OpenAndUnlock:
|
||||||
|
if (Unit* unitCaster = m_caster->ToUnit())
|
||||||
|
gameObjTarget->UseDoorOrButton(0, false, unitCaster);
|
||||||
|
[[fallthrough]];
|
||||||
|
case GameObjectActions::Unlock:
|
||||||
|
case GameObjectActions::Lock:
|
||||||
|
gameObjTarget->ApplyModFlag(GAMEOBJECT_FLAGS, GO_FLAG_LOCKED, action == GameObjectActions::Lock);
|
||||||
|
break;
|
||||||
|
case GameObjectActions::Close:
|
||||||
|
case GameObjectActions::Rebuild:
|
||||||
|
gameObjTarget->ResetDoorOrButton();
|
||||||
|
break;
|
||||||
|
case GameObjectActions::Despawn:
|
||||||
|
gameObjTarget->DespawnOrUnsummon();
|
||||||
|
break;
|
||||||
|
case GameObjectActions::MakeInert:
|
||||||
|
case GameObjectActions::MakeActive:
|
||||||
|
gameObjTarget->ApplyModFlag(GAMEOBJECT_FLAGS, GO_FLAG_NOT_SELECTABLE, action == GameObjectActions::MakeInert);
|
||||||
|
break;
|
||||||
|
case GameObjectActions::CloseAndLock:
|
||||||
|
gameObjTarget->ResetDoorOrButton();
|
||||||
|
gameObjTarget->SetFlag(GAMEOBJECT_FLAGS, GO_FLAG_LOCKED);
|
||||||
|
break;
|
||||||
|
case GameObjectActions::Destroy:
|
||||||
|
if (Unit* unitCaster = m_caster->ToUnit())
|
||||||
|
gameObjTarget->UseDoorOrButton(0, true, unitCaster);
|
||||||
|
break;
|
||||||
|
case GameObjectActions::UseArtKit0:
|
||||||
|
case GameObjectActions::UseArtKit1:
|
||||||
|
case GameObjectActions::UseArtKit2:
|
||||||
|
case GameObjectActions::UseArtKit3:
|
||||||
|
{
|
||||||
|
GameObjectTemplateAddon const* templateAddon = gameObjTarget->GetTemplateAddon();
|
||||||
|
|
||||||
//ScriptInfo activateCommand;
|
uint32 artKitIndex = uint32(action) - uint32(GameObjectActions::UseArtKit0);
|
||||||
//activateCommand.command = SCRIPT_COMMAND_ACTIVATE_OBJECT;
|
|
||||||
|
|
||||||
// int32 unk = m_spellInfo->Effects[effIndex].MiscValue; // This is set for EffectActivateObject spells; needs research
|
uint32 artKitValue = 0;
|
||||||
|
if (templateAddon)
|
||||||
|
artKitValue = templateAddon->artKits[artKitIndex];
|
||||||
|
|
||||||
// xinef: pass player to allow gossip scripts to work
|
if (artKitValue == 0)
|
||||||
//
|
LOG_ERROR("sql.sql", "GameObject {} hit by spell {} needs `artkit{}` in `gameobject_template_addon`", gameObjTarget->GetEntry(), m_spellInfo->Id, artKitIndex);
|
||||||
//gameObjTarget->GetMap()->ScriptCommandStart(activateCommand, 0, player ? player : m_caster, gameObjTarget);
|
else
|
||||||
|
gameObjTarget->SetGoArtKit(artKitValue);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GameObjectActions::None:
|
||||||
|
LOG_FATAL("spell", "Spell {} has action type NONE in effect {}", m_spellInfo->Id, int32(effIndex));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_ERROR("spell", "Spell {} has unhandled action {} in effect {}", m_spellInfo->Id, int32(action), int32(effIndex));
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Spell::EffectApplyGlyph(SpellEffIndex effIndex)
|
void Spell::EffectApplyGlyph(SpellEffIndex effIndex)
|
||||||
|
|||||||
@@ -783,37 +783,9 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class spell_banging_the_gong : public SpellScriptLoader
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
spell_banging_the_gong() : SpellScriptLoader("spell_banging_the_gong") { }
|
|
||||||
|
|
||||||
class spell_banging_the_gong_SpellScript : public SpellScript
|
|
||||||
{
|
|
||||||
PrepareSpellScript(spell_banging_the_gong_SpellScript);
|
|
||||||
|
|
||||||
void Activate(SpellEffIndex index)
|
|
||||||
{
|
|
||||||
PreventHitDefaultEffect(index);
|
|
||||||
GetHitGObj()->SendCustomAnim(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Register() override
|
|
||||||
{
|
|
||||||
OnEffectHitTarget += SpellEffectFn(spell_banging_the_gong_SpellScript::Activate, EFFECT_1, SPELL_EFFECT_ACTIVATE_OBJECT);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
SpellScript* GetSpellScript() const override
|
|
||||||
{
|
|
||||||
return new spell_banging_the_gong_SpellScript();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void AddSC_zulaman()
|
void AddSC_zulaman()
|
||||||
{
|
{
|
||||||
new npc_forest_frog();
|
new npc_forest_frog();
|
||||||
new npc_zulaman_hostage();
|
new npc_zulaman_hostage();
|
||||||
new npc_harrison_jones();
|
new npc_harrison_jones();
|
||||||
new spell_banging_the_gong();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -979,6 +979,13 @@ struct FactionTemplateEntry
|
|||||||
[[nodiscard]] bool IsContestedGuardFaction() const { return (factionFlags & FACTION_TEMPLATE_FLAG_ATTACK_PVP_ACTIVE_PLAYERS) != 0; }
|
[[nodiscard]] bool IsContestedGuardFaction() const { return (factionFlags & FACTION_TEMPLATE_FLAG_ATTACK_PVP_ACTIVE_PLAYERS) != 0; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct GameObjectArtKitEntry
|
||||||
|
{
|
||||||
|
uint32 ID; // 0
|
||||||
|
//char* TextureVariation[3] // 1-3 m_textureVariations[3]
|
||||||
|
//char* AttachModel[4] // 4-8 m_attachModels[4]
|
||||||
|
};
|
||||||
|
|
||||||
struct GameObjectDisplayInfoEntry
|
struct GameObjectDisplayInfoEntry
|
||||||
{
|
{
|
||||||
uint32 Displayid; // 0 m_ID
|
uint32 Displayid; // 0 m_ID
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ char constexpr EmotesEntryfmt[] = "nxxiiix";
|
|||||||
char constexpr EmotesTextEntryfmt[] = "nxixxxxxxxxxxxxxxxx";
|
char constexpr EmotesTextEntryfmt[] = "nxixxxxxxxxxxxxxxxx";
|
||||||
char constexpr FactionEntryfmt[] = "niiiiiiiiiiiiiiiiiiffixssssssssssssssssxxxxxxxxxxxxxxxxxx";
|
char constexpr FactionEntryfmt[] = "niiiiiiiiiiiiiiiiiiffixssssssssssssssssxxxxxxxxxxxxxxxxxx";
|
||||||
char constexpr FactionTemplateEntryfmt[] = "niiiiiiiiiiiii";
|
char constexpr FactionTemplateEntryfmt[] = "niiiiiiiiiiiii";
|
||||||
|
char constexpr GameObjectArtKitfmt[] = "nxxxxxxx";
|
||||||
char constexpr GameObjectDisplayInfofmt[] = "nsxxxxxxxxxxffffffx";
|
char constexpr GameObjectDisplayInfofmt[] = "nsxxxxxxxxxxffffffx";
|
||||||
char constexpr GemPropertiesEntryfmt[] = "nixxi";
|
char constexpr GemPropertiesEntryfmt[] = "nixxi";
|
||||||
char constexpr GlyphPropertiesfmt[] = "niii";
|
char constexpr GlyphPropertiesfmt[] = "niii";
|
||||||
|
|||||||
Reference in New Issue
Block a user