mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-30 21:15:33 +00:00
[General] Implement ClientScriptSettings packet, part 1
For starters, the new packet can set which client scripts have all of their variables synchronized between players. The previous hardcoded list of IDs for synchronized scripts has been removed.
This commit is contained in:
parent
25e27ccb95
commit
3acfbad55d
12 changed files with 153 additions and 76 deletions
|
@ -153,11 +153,21 @@ void WorldstateFunctions::UseActorCollisionForPlacedObjects(bool useActorCollisi
|
||||||
writeWorldstate.useActorCollisionForPlacedObjects = useActorCollision;
|
writeWorldstate.useActorCollisionForPlacedObjects = useActorCollision;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WorldstateFunctions::AddSynchronizedClientScriptId(const char *scriptId) noexcept
|
||||||
|
{
|
||||||
|
writeWorldstate.synchronizedClientScriptIds.push_back(scriptId);
|
||||||
|
}
|
||||||
|
|
||||||
void WorldstateFunctions::AddEnforcedCollisionRefId(const char *refId) noexcept
|
void WorldstateFunctions::AddEnforcedCollisionRefId(const char *refId) noexcept
|
||||||
{
|
{
|
||||||
writeWorldstate.enforcedCollisionRefIds.push_back(refId);
|
writeWorldstate.enforcedCollisionRefIds.push_back(refId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WorldstateFunctions::ClearSynchronizedClientScriptIds() noexcept
|
||||||
|
{
|
||||||
|
writeWorldstate.synchronizedClientScriptIds.clear();
|
||||||
|
}
|
||||||
|
|
||||||
void WorldstateFunctions::ClearEnforcedCollisionRefIds() noexcept
|
void WorldstateFunctions::ClearEnforcedCollisionRefIds() noexcept
|
||||||
{
|
{
|
||||||
writeWorldstate.enforcedCollisionRefIds.clear();
|
writeWorldstate.enforcedCollisionRefIds.clear();
|
||||||
|
@ -196,6 +206,22 @@ void WorldstateFunctions::LoadMapTileImageFile(int cellX, int cellY, const char*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WorldstateFunctions::SendClientScriptSettings(unsigned short pid, bool sendToOtherPlayers, bool skipAttachedPlayer) noexcept
|
||||||
|
{
|
||||||
|
Player *player;
|
||||||
|
GET_PLAYER(pid, player, );
|
||||||
|
|
||||||
|
writeWorldstate.guid = player->guid;
|
||||||
|
|
||||||
|
mwmp::WorldstatePacket *packet = mwmp::Networking::get().getWorldstatePacketController()->GetPacket(ID_CLIENT_SCRIPT_SETTINGS);
|
||||||
|
packet->setWorldstate(&writeWorldstate);
|
||||||
|
|
||||||
|
if (!skipAttachedPlayer)
|
||||||
|
packet->Send(false);
|
||||||
|
if (sendToOtherPlayers)
|
||||||
|
packet->Send(true);
|
||||||
|
}
|
||||||
|
|
||||||
void WorldstateFunctions::SendWorldMap(unsigned short pid, bool sendToOtherPlayers, bool skipAttachedPlayer) noexcept
|
void WorldstateFunctions::SendWorldMap(unsigned short pid, bool sendToOtherPlayers, bool skipAttachedPlayer) noexcept
|
||||||
{
|
{
|
||||||
Player *player;
|
Player *player;
|
||||||
|
|
|
@ -43,12 +43,16 @@
|
||||||
{"SetPlacedObjectCollisionState", WorldstateFunctions::SetPlacedObjectCollisionState},\
|
{"SetPlacedObjectCollisionState", WorldstateFunctions::SetPlacedObjectCollisionState},\
|
||||||
{"UseActorCollisionForPlacedObjects", WorldstateFunctions::UseActorCollisionForPlacedObjects},\
|
{"UseActorCollisionForPlacedObjects", WorldstateFunctions::UseActorCollisionForPlacedObjects},\
|
||||||
\
|
\
|
||||||
|
{"AddSynchronizedClientScriptId", WorldstateFunctions::AddSynchronizedClientScriptId},\
|
||||||
{"AddEnforcedCollisionRefId", WorldstateFunctions::AddEnforcedCollisionRefId},\
|
{"AddEnforcedCollisionRefId", WorldstateFunctions::AddEnforcedCollisionRefId},\
|
||||||
|
\
|
||||||
|
{"ClearSynchronizedClientScriptIds", WorldstateFunctions::ClearSynchronizedClientScriptIds},\
|
||||||
{"ClearEnforcedCollisionRefIds", WorldstateFunctions::ClearEnforcedCollisionRefIds},\
|
{"ClearEnforcedCollisionRefIds", WorldstateFunctions::ClearEnforcedCollisionRefIds},\
|
||||||
\
|
\
|
||||||
{"SaveMapTileImageFile", WorldstateFunctions::SaveMapTileImageFile},\
|
{"SaveMapTileImageFile", WorldstateFunctions::SaveMapTileImageFile},\
|
||||||
{"LoadMapTileImageFile", WorldstateFunctions::LoadMapTileImageFile},\
|
{"LoadMapTileImageFile", WorldstateFunctions::LoadMapTileImageFile},\
|
||||||
\
|
\
|
||||||
|
{"SendClientScriptSettings", WorldstateFunctions::SendClientScriptSettings},\
|
||||||
{"SendWorldMap", WorldstateFunctions::SendWorldMap},\
|
{"SendWorldMap", WorldstateFunctions::SendWorldMap},\
|
||||||
{"SendWorldTime", WorldstateFunctions::SendWorldTime},\
|
{"SendWorldTime", WorldstateFunctions::SendWorldTime},\
|
||||||
{"SendWorldWeather", WorldstateFunctions::SendWorldWeather},\
|
{"SendWorldWeather", WorldstateFunctions::SendWorldWeather},\
|
||||||
|
@ -293,6 +297,15 @@ public:
|
||||||
*/
|
*/
|
||||||
static void UseActorCollisionForPlacedObjects(bool useActorCollision) noexcept;
|
static void UseActorCollisionForPlacedObjects(bool useActorCollision) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Add an ID to the list of script IDs whose variables should all be synchronized
|
||||||
|
* across players.
|
||||||
|
*
|
||||||
|
* \param scriptId The ID.
|
||||||
|
* \return void
|
||||||
|
*/
|
||||||
|
static void AddSynchronizedClientScriptId(const char* scriptId) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Add a refId to the list of refIds for which collision should be enforced
|
* \brief Add a refId to the list of refIds for which collision should be enforced
|
||||||
* irrespective of other settings.
|
* irrespective of other settings.
|
||||||
|
@ -303,7 +316,15 @@ public:
|
||||||
static void AddEnforcedCollisionRefId(const char* refId) noexcept;
|
static void AddEnforcedCollisionRefId(const char* refId) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Clear the list of refIdsd for which collision should be enforced irrespective
|
* \brief Clear the list of script IDs whose variables should all be synchronized
|
||||||
|
* across players.
|
||||||
|
*
|
||||||
|
* \return void
|
||||||
|
*/
|
||||||
|
static void ClearSynchronizedClientScriptIds() noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Clear the list of refIds for which collision should be enforced irrespective
|
||||||
* of other settings.
|
* of other settings.
|
||||||
*
|
*
|
||||||
* \return void
|
* \return void
|
||||||
|
@ -331,6 +352,19 @@ public:
|
||||||
*/
|
*/
|
||||||
static void LoadMapTileImageFile(int cellX, int cellY, const char* filePath) noexcept;
|
static void LoadMapTileImageFile(int cellX, int cellY, const char* filePath) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Send a ClientScriptSettings packet with the current client script settings in
|
||||||
|
* the write-only worldstate.
|
||||||
|
*
|
||||||
|
* \param pid The player ID attached to the packet.
|
||||||
|
* \param sendToOtherPlayers Whether this packet should be sent to players other than the
|
||||||
|
* player attached to the packet (false by default).
|
||||||
|
* \param skipAttachedPlayer Whether the packet should skip being sent to the player attached
|
||||||
|
* to the packet (false by default).
|
||||||
|
* \return void
|
||||||
|
*/
|
||||||
|
static void SendClientScriptSettings(unsigned short pid, bool sendToOtherPlayers, bool skipAttachedPlayer) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Send a WorldRegionAuthority packet establishing a certain player as the only one who
|
* \brief Send a WorldRegionAuthority packet establishing a certain player as the only one who
|
||||||
* should process certain region-specific events (such as weather changes).
|
* should process certain region-specific events (such as weather changes).
|
||||||
|
|
|
@ -134,8 +134,8 @@ add_openmw_dir (mwmp/processors/object BaseObjectProcessor
|
||||||
ProcessorScriptGlobalFloat
|
ProcessorScriptGlobalFloat
|
||||||
)
|
)
|
||||||
|
|
||||||
add_openmw_dir (mwmp/processors/worldstate ProcessorCellCreate ProcessorCellReset ProcessorRecordDynamic
|
add_openmw_dir (mwmp/processors/worldstate ProcessorCellCreate ProcessorCellReset ProcessorClientScriptSettings
|
||||||
ProcessorWorldCollisionOverride ProcessorWorldMap ProcessorWorldRegionAuthority ProcessorWorldTime
|
ProcessorRecordDynamic ProcessorWorldCollisionOverride ProcessorWorldMap ProcessorWorldRegionAuthority ProcessorWorldTime
|
||||||
ProcessorWorldWeather
|
ProcessorWorldWeather
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#include <components/openmw-mp/Utils.hpp>
|
||||||
#include <components/openmw-mp/TimedLog.hpp>
|
#include <components/openmw-mp/TimedLog.hpp>
|
||||||
#include <components/openmw-mp/Version.hpp>
|
#include <components/openmw-mp/Version.hpp>
|
||||||
|
|
||||||
|
@ -231,79 +232,12 @@ CellController *Main::getCellController() const
|
||||||
return mCellController;
|
return mCellController;
|
||||||
}
|
}
|
||||||
|
|
||||||
// When sending packets with ingame script values, certain packets
|
|
||||||
// should be ignored because of their potential for spam
|
|
||||||
bool Main::isValidPacketScript(std::string script)
|
bool Main::isValidPacketScript(std::string script)
|
||||||
{
|
{
|
||||||
static const int validPacketScriptsCount = 21;
|
mwmp::BaseWorldstate *worldstate = get().getNetworking()->getWorldstate();
|
||||||
static const std::string validPacketScripts[validPacketScriptsCount] = {
|
|
||||||
// Ghostgate buttons
|
|
||||||
"GG_OpenGate1", // coc Ghostgate
|
|
||||||
"GG_OpenGate2",
|
|
||||||
// Dwemer ruin cranks
|
|
||||||
"Arkn_doors", // coe 0, -2
|
|
||||||
"nchuleftingthWrong1", // coc "Nchuleftingth, Test of Pattern"
|
|
||||||
"nchuleftingthWrong2",
|
|
||||||
"nchulfetingthRight",
|
|
||||||
"Akula_innerdoors", // coc "Akulakhan's Chamber"
|
|
||||||
"Dagoth_doors", // coe 2, 8
|
|
||||||
// Sotha Sil levers
|
|
||||||
"SothaLever1", // coc "Sotha Sil, Outer Flooded Halls"
|
|
||||||
"SothaLever2",
|
|
||||||
"SothaLever3",
|
|
||||||
"SothaLever4",
|
|
||||||
"SothaLever5",
|
|
||||||
"SothaLever6",
|
|
||||||
"SothaLever7",
|
|
||||||
"SothaLever8",
|
|
||||||
"SothaLever9",
|
|
||||||
"SothaLever10",
|
|
||||||
"SothaLever11",
|
|
||||||
"SothaOilLever", // coc "Sotha Sil, Dome of Udok"
|
|
||||||
// Generic state script
|
|
||||||
"LocalState"
|
|
||||||
};
|
|
||||||
|
|
||||||
static const int invalidPacketScriptsCount = 17;
|
if (Utils::vectorContains(worldstate->synchronizedClientScriptIds, script))
|
||||||
static const std::string invalidPacketScripts[invalidPacketScriptsCount] = {
|
return true;
|
||||||
// Spammy shorts
|
|
||||||
"OutsideBanner",
|
|
||||||
"sleeperScript",
|
|
||||||
"dreamer_talkerEnable",
|
|
||||||
"drenSlaveOwners",
|
|
||||||
"ahnassiScript",
|
|
||||||
"hlormarScript",
|
|
||||||
// Spammy floats
|
|
||||||
"Float",
|
|
||||||
"SignRotate",
|
|
||||||
"FaluraScript",
|
|
||||||
"jsaddhaScript",
|
|
||||||
// Spammy globals
|
|
||||||
"wraithguardScript",
|
|
||||||
// Spammy globals leading to crashes
|
|
||||||
"LegionUniform",
|
|
||||||
"OrdinatorUniform",
|
|
||||||
"LorkhanHeart",
|
|
||||||
"ouch_keening",
|
|
||||||
"ouch_sunder",
|
|
||||||
"ouch_wraithguard"
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto &validPacketScript : validPacketScripts)
|
|
||||||
{
|
|
||||||
if (Misc::StringUtils::ciEqual(script, validPacketScript))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
/* Switch over to this when using a blacklist system
|
|
||||||
for (int i = 0; i < invalidPacketScriptsCount; i++)
|
|
||||||
{
|
|
||||||
if (Misc::StringUtils::ciEqual(script, invalidPacketScripts[i]))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,6 +91,7 @@
|
||||||
#include "WorldstateProcessor.hpp"
|
#include "WorldstateProcessor.hpp"
|
||||||
#include "worldstate/ProcessorCellCreate.hpp"
|
#include "worldstate/ProcessorCellCreate.hpp"
|
||||||
#include "worldstate/ProcessorCellReset.hpp"
|
#include "worldstate/ProcessorCellReset.hpp"
|
||||||
|
#include "worldstate/ProcessorClientScriptSettings.hpp"
|
||||||
#include "worldstate/ProcessorRecordDynamic.hpp"
|
#include "worldstate/ProcessorRecordDynamic.hpp"
|
||||||
#include "worldstate/ProcessorWorldCollisionOverride.hpp"
|
#include "worldstate/ProcessorWorldCollisionOverride.hpp"
|
||||||
#include "worldstate/ProcessorWorldMap.hpp"
|
#include "worldstate/ProcessorWorldMap.hpp"
|
||||||
|
@ -189,6 +190,7 @@ void ProcessorInitializer()
|
||||||
|
|
||||||
WorldstateProcessor::AddProcessor(new ProcessorCellCreate());
|
WorldstateProcessor::AddProcessor(new ProcessorCellCreate());
|
||||||
WorldstateProcessor::AddProcessor(new ProcessorCellReset());
|
WorldstateProcessor::AddProcessor(new ProcessorCellReset());
|
||||||
|
WorldstateProcessor::AddProcessor(new ProcessorClientScriptSettings());
|
||||||
WorldstateProcessor::AddProcessor(new ProcessorRecordDynamic());
|
WorldstateProcessor::AddProcessor(new ProcessorRecordDynamic());
|
||||||
WorldstateProcessor::AddProcessor(new ProcessorWorldCollisionOverride());
|
WorldstateProcessor::AddProcessor(new ProcessorWorldCollisionOverride());
|
||||||
WorldstateProcessor::AddProcessor(new ProcessorWorldMap());
|
WorldstateProcessor::AddProcessor(new ProcessorWorldMap());
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
#ifndef OPENMW_PROCESSORCLIENTSCRIPTSETTINGS_HPP
|
||||||
|
#define OPENMW_PROCESSORCLIENTSCRIPTSETTINGS_HPP
|
||||||
|
|
||||||
|
|
||||||
|
#include <apps/openmw/mwbase/world.hpp>
|
||||||
|
#include <apps/openmw/mwbase/environment.hpp>
|
||||||
|
#include "../WorldstateProcessor.hpp"
|
||||||
|
|
||||||
|
namespace mwmp
|
||||||
|
{
|
||||||
|
class ProcessorClientScriptSettings : public WorldstateProcessor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ProcessorClientScriptSettings()
|
||||||
|
{
|
||||||
|
BPP_INIT(ID_CLIENT_SCRIPT_SETTINGS)
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Do(WorldstatePacket &packet, Worldstate &worldstate)
|
||||||
|
{
|
||||||
|
// Placeholder
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif //OPENMW_PROCESSORCLIENTSCRIPTSETTINGS_HPP
|
|
@ -208,8 +208,8 @@ add_component_dir (openmw-mp/Packets/Object
|
||||||
add_component_dir (openmw-mp/Packets/Worldstate
|
add_component_dir (openmw-mp/Packets/Worldstate
|
||||||
WorldstatePacket
|
WorldstatePacket
|
||||||
|
|
||||||
PacketCellCreate PacketCellReset PacketRecordDynamic PacketWorldCollisionOverride PacketWorldMap
|
PacketCellCreate PacketCellReset PacketClientScriptSettings PacketRecordDynamic PacketWorldCollisionOverride
|
||||||
PacketWorldRegionAuthority PacketWorldTime PacketWorldWeather
|
PacketWorldMap PacketWorldRegionAuthority PacketWorldTime PacketWorldWeather
|
||||||
)
|
)
|
||||||
|
|
||||||
add_component_dir (fallback
|
add_component_dir (fallback
|
||||||
|
|
|
@ -307,6 +307,7 @@ namespace mwmp
|
||||||
RakNet::RakNetGUID guid;
|
RakNet::RakNetGUID guid;
|
||||||
|
|
||||||
mwmp::Time time;
|
mwmp::Time time;
|
||||||
|
std::vector<std::string> synchronizedClientScriptIds;
|
||||||
|
|
||||||
bool hasPlayerCollision;
|
bool hasPlayerCollision;
|
||||||
bool hasActorCollision;
|
bool hasActorCollision;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include "../Packets/Worldstate/PacketCellCreate.hpp"
|
#include "../Packets/Worldstate/PacketCellCreate.hpp"
|
||||||
#include "../Packets/Worldstate/PacketCellReset.hpp"
|
#include "../Packets/Worldstate/PacketCellReset.hpp"
|
||||||
|
#include "../Packets/Worldstate/PacketClientScriptSettings.hpp"
|
||||||
#include "../Packets/Worldstate/PacketRecordDynamic.hpp"
|
#include "../Packets/Worldstate/PacketRecordDynamic.hpp"
|
||||||
#include "../Packets/Worldstate/PacketWorldCollisionOverride.hpp"
|
#include "../Packets/Worldstate/PacketWorldCollisionOverride.hpp"
|
||||||
#include "../Packets/Worldstate/PacketWorldMap.hpp"
|
#include "../Packets/Worldstate/PacketWorldMap.hpp"
|
||||||
|
@ -21,6 +22,7 @@ mwmp::WorldstatePacketController::WorldstatePacketController(RakNet::RakPeerInte
|
||||||
{
|
{
|
||||||
AddPacket<PacketCellCreate>(&packets, peer);
|
AddPacket<PacketCellCreate>(&packets, peer);
|
||||||
AddPacket<PacketCellReset>(&packets, peer);
|
AddPacket<PacketCellReset>(&packets, peer);
|
||||||
|
AddPacket<PacketClientScriptSettings>(&packets, peer);
|
||||||
AddPacket<PacketRecordDynamic>(&packets, peer);
|
AddPacket<PacketRecordDynamic>(&packets, peer);
|
||||||
AddPacket<PacketWorldCollisionOverride>(&packets, peer);
|
AddPacket<PacketWorldCollisionOverride>(&packets, peer);
|
||||||
AddPacket<PacketWorldMap>(&packets, peer);
|
AddPacket<PacketWorldMap>(&packets, peer);
|
||||||
|
|
|
@ -99,7 +99,7 @@ enum GameMessages
|
||||||
ID_GAME_SETTINGS,
|
ID_GAME_SETTINGS,
|
||||||
ID_GAME_PREINIT,
|
ID_GAME_PREINIT,
|
||||||
|
|
||||||
ID_CELL_CREATE,
|
ID_CLIENT_SCRIPT_SETTINGS,
|
||||||
ID_CELL_RESET,
|
ID_CELL_RESET,
|
||||||
ID_RECORD_DYNAMIC,
|
ID_RECORD_DYNAMIC,
|
||||||
ID_WORLD_COLLISION_OVERRIDE,
|
ID_WORLD_COLLISION_OVERRIDE,
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
#include "PacketClientScriptSettings.hpp"
|
||||||
|
#include <components/openmw-mp/NetworkMessages.hpp>
|
||||||
|
|
||||||
|
using namespace mwmp;
|
||||||
|
|
||||||
|
PacketClientScriptSettings::PacketClientScriptSettings(RakNet::RakPeerInterface *peer) : WorldstatePacket(peer)
|
||||||
|
{
|
||||||
|
packetID = ID_CLIENT_SCRIPT_SETTINGS;
|
||||||
|
orderChannel = CHANNEL_WORLDSTATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PacketClientScriptSettings::Packet(RakNet::BitStream *bs, bool send)
|
||||||
|
{
|
||||||
|
WorldstatePacket::Packet(bs, send);
|
||||||
|
|
||||||
|
uint32_t clientScriptsCount;
|
||||||
|
|
||||||
|
if (send)
|
||||||
|
clientScriptsCount = static_cast<uint32_t>(worldstate->synchronizedClientScriptIds.size());
|
||||||
|
|
||||||
|
RW(clientScriptsCount, send);
|
||||||
|
|
||||||
|
if (!send)
|
||||||
|
{
|
||||||
|
worldstate->synchronizedClientScriptIds.clear();
|
||||||
|
worldstate->synchronizedClientScriptIds.resize(clientScriptsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &&clientScriptId : worldstate->synchronizedClientScriptIds)
|
||||||
|
{
|
||||||
|
RW(clientScriptId, send);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
#ifndef OPENMW_PACKETCLIENTSCRIPTSETTINGS_HPP
|
||||||
|
#define OPENMW_PACKETCLIENTSCRIPTSETTINGS_HPP
|
||||||
|
|
||||||
|
#include <components/openmw-mp/Packets/Worldstate/WorldstatePacket.hpp>
|
||||||
|
|
||||||
|
namespace mwmp
|
||||||
|
{
|
||||||
|
class PacketClientScriptSettings : public WorldstatePacket
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PacketClientScriptSettings(RakNet::RakPeerInterface *peer);
|
||||||
|
|
||||||
|
virtual void Packet(RakNet::BitStream *bs, bool send);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //OPENMW_PACKETCLIENTSCRIPTSETTINGS_HPP
|
Loading…
Reference in a new issue