From e4112bfe6c26ae40be5b85d8c0ce8c5a47aa22b4 Mon Sep 17 00:00:00 2001
From: kokekanon <114332266+kokekanon@users.noreply.github.com>
Date: Tue, 16 Apr 2024 11:49:28 -0400
Subject: [PATCH] feat: [game_outfit] Shader (#9)
---
data/XML/shaders.xml | 9 ++
data/talkactions/scripts/reload.lua | 3 +
schema.sql | 9 ++
src/const.h | 1 +
src/creature.h | 1 +
src/enums.h | 2 +
src/game.cpp | 25 ++++-
src/game.h | 5 +-
src/iologindata.cpp | 43 +++++++-
src/luagame.cpp | 16 +++
src/luaplayer.cpp | 92 ++++++++++++++++
src/luascript.cpp | 10 ++
src/luascript.h | 2 +
src/player.cpp | 164 ++++++++++++++++++++++++++++
src/player.h | 19 +++-
src/protocolgame.cpp | 32 +++++-
src/shaders.cpp | 52 +++++++++
src/shaders.h | 30 +++++
vc17/theforgottenserver.vcxproj | 2 +
19 files changed, 508 insertions(+), 9 deletions(-)
create mode 100644 data/XML/shaders.xml
create mode 100644 src/shaders.cpp
create mode 100644 src/shaders.h
diff --git a/data/XML/shaders.xml b/data/XML/shaders.xml
new file mode 100644
index 0000000..38dbb14
--- /dev/null
+++ b/data/XML/shaders.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/data/talkactions/scripts/reload.lua b/data/talkactions/scripts/reload.lua
index 7c79e67..18af896 100644
--- a/data/talkactions/scripts/reload.lua
+++ b/data/talkactions/scripts/reload.lua
@@ -38,6 +38,9 @@ local reloadTypes = {
["aura"] = RELOAD_TYPE_AURA,
["auras"] = RELOAD_TYPE_AURA,
+ ["shader"] = RELOAD_TYPE_SHADERS,
+ ["shaders"] = RELOAD_TYPE_SHADERS,
+
["move"] = RELOAD_TYPE_MOVEMENTS,
["movement"] = RELOAD_TYPE_MOVEMENTS,
["movements"] = RELOAD_TYPE_MOVEMENTS,
diff --git a/schema.sql b/schema.sql
index 2036084..a771296 100644
--- a/schema.sql
+++ b/schema.sql
@@ -36,6 +36,8 @@ CREATE TABLE IF NOT EXISTS `players` (
`randomizeaura` tinyint NOT NULL DEFAULT '0',
`currenteffect` smallint UNSIGNED NOT NULL DEFAULT '0',
`randomizeeffect` tinyint NOT NULL DEFAULT '0',
+ `currentshader` smallint UNSIGNED NOT NULL DEFAULT '0',
+ `randomizeshader` tinyint NOT NULL DEFAULT '0';
`direction` tinyint unsigned NOT NULL DEFAULT '2',
`maglevel` int NOT NULL DEFAULT '0',
`mana` int NOT NULL DEFAULT '0',
@@ -385,6 +387,13 @@ CREATE TABLE IF NOT EXISTS `player_auras` (
FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
+CREATE TABLE IF NOT EXISTS `player_shaders` (
+ `player_id` int(11) NOT NULL DEFAULT 0,
+ `shader_id` smallint(5) UNSIGNED NOT NULL DEFAULT 0
+ PRIMARY KEY (`player_id`, `shader_id`),
+ FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
+
CREATE TABLE IF NOT EXISTS `server_config` (
`config` varchar(50) NOT NULL,
`value` varchar(256) NOT NULL DEFAULT '',
diff --git a/src/const.h b/src/const.h
index a2497da..1ffade7 100644
--- a/src/const.h
+++ b/src/const.h
@@ -480,6 +480,7 @@ enum ReloadTypes_t : uint8_t
RELOAD_TYPE_WINGS,
RELOAD_TYPE_AURAS,
RELOAD_TYPE_EFFECTS,
+ RELOAD_TYPE_SHADERS,
RELOAD_TYPE_MOVEMENTS,
RELOAD_TYPE_NPCS,
RELOAD_TYPE_QUESTS,
diff --git a/src/creature.h b/src/creature.h
index d960811..f41cf77 100644
--- a/src/creature.h
+++ b/src/creature.h
@@ -415,6 +415,7 @@ class Creature : virtual public Thing
uint16_t currentWing;
uint16_t currentAura;
uint16_t currentEffect;
+ uint16_t currentShader;
Position lastPosition;
LightInfo internalLight;
diff --git a/src/enums.h b/src/enums.h
index d15be46..ea2f178 100644
--- a/src/enums.h
+++ b/src/enums.h
@@ -506,6 +506,8 @@ struct Outfit_t
uint16_t lookWing = 0;
uint16_t lookAura = 0;
uint16_t lookEffect = 0;
+ uint16_t lookShader = 0;
+
uint8_t lookHead = 0;
uint8_t lookBody = 0;
uint8_t lookLegs = 0;
diff --git a/src/game.cpp b/src/game.cpp
index 529a63a..bea4da6 100644
--- a/src/game.cpp
+++ b/src/game.cpp
@@ -79,6 +79,7 @@ void Game::setGameState(GameState_t newState)
wings.loadFromXml();
auras.loadFromXml();
effects.loadFromXml();
+ shaders.loadFromXml();
raids.loadFromXml();
raids.startup();
@@ -3526,7 +3527,29 @@ void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit, bool randomize
player->detachEffectById(player->getCurrentAura());
player->wasAuraed = false;
}
- // @
+ // @
+ /// shaders
+ if (outfit.lookShader != 0) {
+ Shader* shader = shaders.getShaderByID(outfit.lookShader);
+ if (!shader) {
+ return;
+ }
+
+ if (!player->hasShader(shader)) {
+ return;
+ }
+
+ player->setCurrentShader(shader->id);
+ player->sendShader(player, shader->name);
+
+
+ } else {
+ if (player->isShadered()) {
+ player->disshader();
+ }
+ player->sendShader(player, "Outfit - Default");
+ player->wasShadered = false;
+ }
if (player->canWear(outfit.lookType, outfit.lookAddons)) {
player->defaultOutfit = outfit;
diff --git a/src/game.h b/src/game.h
index 1a95c2d..87984fb 100644
--- a/src/game.h
+++ b/src/game.h
@@ -14,6 +14,7 @@
#include "wings.h"
#include "auras.h"
#include "effects.h"
+#include "shaders.h"
#include "npc.h"
#include "player.h"
#include "position.h"
@@ -490,8 +491,10 @@ class Game
Raids raids;
Mounts mounts;
Wings wings;
- Effects effects;
Auras auras;
+ Effects effects;
+ Shaders shaders;
+
std::forward_list- toDecayItems;
diff --git a/src/iologindata.cpp b/src/iologindata.cpp
index f1d5844..35fa8d4 100644
--- a/src/iologindata.cpp
+++ b/src/iologindata.cpp
@@ -229,7 +229,7 @@ bool IOLoginData::loadPlayerById(Player* player, uint32_t id)
return loadPlayer(
player,
db.storeQuery(fmt::format(
- "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `currentmount`, `randomizemount`, `currentwing`, `randomizewing`,`currenteffect`, `randomizeeffect`,`currentaura`, `randomizeaura`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `direction` FROM `players` WHERE `id` = {:d}",
+ "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `currentmount`, `randomizemount`, `currentwing`, `randomizewing`,`currenteffect`, `randomizeeffect`,`currentaura`, `randomizeaura`,`currentshader`, `randomizeshader`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `direction` FROM `players` WHERE `id` = {:d}",
id)));
}
@@ -239,7 +239,7 @@ bool IOLoginData::loadPlayerByName(Player* player, std::string_view name)
return loadPlayer(
player,
db.storeQuery(fmt::format(
- "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `currentmount`, `randomizemount`, `currentwing`, `randomizewing`,`currenteffect`, `randomizeeffect`,`currentaura`, `randomizeaura`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `direction` FROM `players` WHERE `name` = {:s}",
+ "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `currentmount`, `randomizemount`, `currentwing`, `randomizewing`,`currenteffect`, `randomizeeffect`,`currentaura`, `randomizeaura`,`currentshader`, `randomizeshader`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `direction` FROM `players` WHERE `name` = {:s}",
db.escapeString(name))));
}
@@ -364,11 +364,13 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result)
player->currentWing = result->getNumber("currentwing");
player->currentEffect = result->getNumber("currenteffect");
player->currentAura = result->getNumber("currentaura");
+ player->currentShader = result->getNumber("currentshader");
player->direction = static_cast(result->getNumber("direction"));
player->randomizeMount = result->getNumber("randomizemount") != 0;
player->randomizeWing = result->getNumber("randomizewing") != 0;
player->randomizeAura = result->getNumber("randomizeaura") != 0;
player->randomizeEffect = result->getNumber("randomizeeffect") != 0;
+ player->randomizeShader = result->getNumber("randomizeshader") != 0;
if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) {
const time_t skullSeconds = result->getNumber("skulltime") - time(nullptr);
if (skullSeconds > 0) {
@@ -647,6 +649,24 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result)
}
// @--
+ // load Shaders
+ if ((result = db.storeQuery(
+ fmt::format("SELECT `shader_id` FROM `player_shaders` WHERE `player_id` = {:d}", player->getGUID())))) {
+ do {
+ player->tameShader(result->getNumber("shader_id"));
+ } while (result->next());
+ }
+
+ auto currentShaderID = player->getCurrentShader();
+
+ if (currentShaderID && currentShaderID != 0) {
+ Shader* shader = g_game.shaders.getShaderByID(currentShaderID);
+
+ if (shader && shader->name != "Outfit - Default") {
+ player->setShader(shader->name);
+ }
+ }
+
player->updateBaseSpeed();
player->updateInventoryWeight();
player->updateItemsLight(true);
@@ -760,6 +780,8 @@ bool IOLoginData::savePlayer(Player* player)
query << "`randomizeeffect` = " << player->randomizeEffect << ",";
query << "`currentaura` = " << static_cast(player->currentAura) << ',';
query << "`randomizeaura` = " << player->randomizeAura << ",";
+ query << "`currentshader` = " << static_cast(player->currentShader) << ',';
+ query << "`randomizeshader` = " << player->randomizeShader << ",";
query << "`maglevel` = " << player->magLevel << ',';
query << "`mana` = " << player->mana << ',';
query << "`manamax` = " << player->manaMax << ',';
@@ -1029,6 +1051,23 @@ bool IOLoginData::savePlayer(Player* player)
}
// --@
+ // save shaders
+ if (!db.executeQuery(fmt::format("DELETE FROM `player_shaders` WHERE `player_id` = {:d}", player->getGUID()))) {
+ return false;
+ }
+
+ DBInsert shaderQuery("INSERT INTO `player_shaders` (`player_id`, `shader_id`) VALUES ");
+
+ for (const auto& it : player->shaders) {
+ if (!shaderQuery.addRow(fmt::format("{:d}, {:d}", player->getGUID(), it))) {
+ return false;
+ }
+ }
+
+ if (!shaderQuery.execute()) {
+ return false;
+ }
+
// End the transaction
return transaction.commit();
}
diff --git a/src/luagame.cpp b/src/luagame.cpp
index e2f9165..b980214 100644
--- a/src/luagame.cpp
+++ b/src/luagame.cpp
@@ -268,6 +268,21 @@ int luaGameGetWings(lua_State* L)
return 1;
}
+// shaders
+int luaGameGetShaders(lua_State* L)
+{
+ // Game.getShaders()
+ const auto& shaders = g_game.shaders.getShaders();
+ lua_createtable(L, shaders.size(), 0);
+
+ int index = 0;
+ for (const auto& shader : shaders) {
+ pushShader(L, &shader);
+ lua_rawseti(L, -2, ++index);
+ }
+
+ return 1;
+}
int luaGameGetAuras(lua_State* L)
{
@@ -738,6 +753,7 @@ void LuaScriptInterface::registerGame()
registerMethod("Game", "getWings", luaGameGetWings);
registerMethod("Game", "getEffects", luaGameGetEffects);
registerMethod("Game", "getAuras", luaGameGetAuras);
+ registerMethod("Game", "getShaders", luaGameGetShaders);
registerMethod("Game", "getGameState", luaGameGetGameState);
registerMethod("Game", "setGameState", luaGameSetGameState);
diff --git a/src/luaplayer.cpp b/src/luaplayer.cpp
index 2f5ed24..19cc4a5 100644
--- a/src/luaplayer.cpp
+++ b/src/luaplayer.cpp
@@ -14,6 +14,7 @@
#include "wings.h"
#include "auras.h"
#include "effects.h"
+#include "shaders.h"
extern Chat* g_chat;
extern Game g_game;
@@ -1869,6 +1870,93 @@ int luaPlayerToggleEffect(lua_State* L)
}
// @
+int luaPlayerAddShader(lua_State* L)
+{
+ // player:addShader(shaderId or shaderName)
+ Player* player = getUserdata(L, 1);
+ if (!player) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ uint16_t shaderId;
+ if (isInteger(L, 2)) {
+ shaderId = getInteger(L, 2);
+ } else {
+ Shader* shader = g_game.shaders.getShaderByName(getString(L, 2));
+ if (!shader) {
+ lua_pushnil(L);
+ return 1;
+ }
+ shaderId = shader->id;
+ }
+
+ pushBoolean(L, player->tameShader(shaderId));
+ return 1;
+}
+
+int luaPlayerRemoveShader(lua_State* L)
+{
+ // player:removeShader(shaderId or shaderName)
+ Player* player = getUserdata(L, 1);
+ if (!player) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ uint16_t shaderId;
+ if (isInteger(L, 2)) {
+ shaderId = getInteger(L, 2);
+ } else {
+ Shader* shader = g_game.shaders.getShaderByName(getString(L, 2));
+ if (!shader) {
+ lua_pushnil(L);
+ return 1;
+ }
+ shaderId = shader->id;
+ }
+
+ pushBoolean(L, player->untameShader(shaderId));
+ return 1;
+}
+
+int luaPlayerHasShader(lua_State* L)
+{
+ // player:hasShader(shaderId or shaderName)
+ const Player* player = getUserdata(L, 1);
+ if (!player) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ Shader* shader = nullptr;
+ if (isInteger(L, 2)) {
+ shader = g_game.shaders.getShaderByID(getInteger(L, 2));
+ } else {
+ shader = g_game.shaders.getShaderByName(getString(L, 2));
+ }
+
+ if (shader) {
+ pushBoolean(L, player->hasShader(shader));
+ } else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
+int luaPlayerToggleShader(lua_State* L)
+{
+ // player:toggleShader(shader)
+ Player* player = getUserdata(L, 1);
+ if (!player) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ bool shader = getBoolean(L, 2);
+ pushBoolean(L, player->toggleShader(shader));
+ return 1;
+}
int luaPlayerGetPremiumEndsAt(lua_State* L)
{
@@ -2661,6 +2749,10 @@ void LuaScriptInterface::registerPlayer()
registerMethod("Player", "hasEffect", luaPlayerHasEffect);
registerMethod("Player", "toggleEffect", luaPlayerToggleEffect);
// @
+ registerMethod("Player", "addShader", luaPlayerAddShader);
+ registerMethod("Player", "removeShader", luaPlayerRemoveShader);
+ registerMethod("Player", "hasShader", luaPlayerHasShader);
+ registerMethod("Player", "toggleShader", luaPlayerToggleShader);
registerMethod("Player", "getPremiumEndsAt", luaPlayerGetPremiumEndsAt);
registerMethod("Player", "setPremiumEndsAt", luaPlayerSetPremiumEndsAt);
diff --git a/src/luascript.cpp b/src/luascript.cpp
index 62c349d..fecbbc7 100644
--- a/src/luascript.cpp
+++ b/src/luascript.cpp
@@ -1007,6 +1007,7 @@ void Lua::pushOutfit(lua_State* L, const Outfit_t& outfit)
setField(L, "lookWing", outfit.lookWing);
setField(L, "lookAura", outfit.lookAura);
setField(L, "lookEffect", outfit.lookEffect);
+ setField(L, "lookShader", outfit.lookShader);
setField(L, "lookHead", outfit.lookHead);
setField(L, "lookBody", outfit.lookBody);
setField(L, "lookLegs", outfit.lookLegs);
@@ -1043,6 +1044,14 @@ void Lua::pushWing(lua_State* L, const Wing* wing)
setField(L, "id", wing->id);
setField(L, "premium", wing->premium);
}
+void Lua::pushShader(lua_State* L, const Shader* shader)
+{
+ lua_createtable(L, 0, 5);
+ setField(L, "name", shader->name);
+ setField(L, "id", shader->id);
+ setField(L, "premium", shader->premium);
+}
+
void Lua::pushAura(lua_State* L, const Aura* aura)
{
lua_createtable(L, 0, 5);
@@ -1977,6 +1986,7 @@ void LuaScriptInterface::registerFunctions()
registerEnum(RELOAD_TYPE_ITEMS);
registerEnum(RELOAD_TYPE_MONSTERS);
registerEnum(RELOAD_TYPE_MOUNTS);
+ registerEnum(RELOAD_TYPE_SHADERS);
registerEnum(RELOAD_TYPE_WINGS);
registerEnum(RELOAD_TYPE_AURAS);
registerEnum(RELOAD_TYPE_EFFECTS);
diff --git a/src/luascript.h b/src/luascript.h
index 43a6759..7682631 100644
--- a/src/luascript.h
+++ b/src/luascript.h
@@ -119,6 +119,7 @@ struct Wing;
struct Aura;
struct Effect;
struct Outfit;
+struct Shader;
struct Position;
using Combat_ptr = std::shared_ptr;
@@ -782,6 +783,7 @@ void pushMount(lua_State* L, const Mount* mount);
void pushWing(lua_State* L, const Wing* wing);
void pushAura(lua_State* L, const Aura* aura);
void pushEffect(lua_State* L, const Effect* effect);
+void pushShader(lua_State* L, const Shader* shader);
void pushLoot(lua_State* L, const std::vector& lootList);
void pushReflect(lua_State* L, const Reflect& reflect);
diff --git a/src/player.cpp b/src/player.cpp
index be3d3bd..cb63d16 100644
--- a/src/player.cpp
+++ b/src/player.cpp
@@ -4599,6 +4599,170 @@ bool Player::hasEffects() const
}
void Player::diseffect() { defaultOutfit.lookEffect = 0; }
+
+uint16_t Player::getRandomShader() const
+{
+ std::vector shadersId;
+ for (const Shader& shader : g_game.shaders.getShaders()) {
+ if (hasShader(&shader)) {
+ shadersId.push_back(shader.id);
+ }
+ }
+
+ return shadersId[uniform_random(0, shadersId.size() - 1)];
+}
+
+
+uint16_t Player::getCurrentShader() const { return currentShader; }
+
+
+void Player::setCurrentShader(uint16_t shaderId) { currentShader = shaderId; }
+
+bool Player::toggleShader(bool shader)
+{
+ if ((OTSYS_TIME() - lastToggleShader) < 3000 && !wasShadered) {
+ sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED);
+ return false;
+ }
+
+ if (shader) {
+ if (isShadered()) {
+ return false;
+ }
+
+ if (!group->access && tile->hasFlag(TILESTATE_PROTECTIONZONE)) {
+ sendCancelMessage(RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE);
+ return false;
+ }
+
+ const Outfit* playerOutfit = Outfits::getInstance().getOutfitByLookType(defaultOutfit.lookType);
+ if (!playerOutfit) {
+ return false;
+ }
+
+ uint16_t currentShaderId = getCurrentShader();
+ if (currentShaderId == 0) {
+ sendOutfitWindow();
+ return false;
+ }
+
+ Shader* currentShader = g_game.shaders.getShaderByID(currentShaderId);
+ if (!currentShader) {
+ return false;
+ }
+
+ if (!hasShader(currentShader)) {
+ setCurrentShader(0);
+ sendOutfitWindow();
+ return false;
+ }
+
+ if (currentShader->premium && !isPremium()) {
+ sendCancelMessage(RETURNVALUE_YOUNEEDPREMIUMACCOUNT);
+ return false;
+ }
+
+ if (hasCondition(CONDITION_OUTFIT)) {
+ sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
+ return false;
+ }
+
+ defaultOutfit.lookShader = currentShader->id;
+
+ } else {
+ if (!isShadered()) {
+ return false;
+ }
+
+ disshader();
+ }
+
+ g_game.internalCreatureChangeOutfit(this, defaultOutfit);
+ lastToggleShader = OTSYS_TIME();
+ return true;
+}
+
+bool Player::tameShader(uint16_t shaderId)
+{
+ if (!g_game.shaders.getShaderByID(shaderId)) {
+ return false;
+ }
+
+ Shader* shader = g_game.shaders.getShaderByID(shaderId);
+ if (hasShader(shader)) {
+ return false;
+ }
+
+ shaders.insert(shaderId);
+ return true;
+}
+
+bool Player::untameShader(uint16_t shaderId)
+{
+ if (!g_game.shaders.getShaderByID(shaderId)) {
+ return false;
+ }
+
+ Shader* shader = g_game.shaders.getShaderByID(shaderId);
+ if (!hasShader(shader)) {
+ return false;
+ }
+
+ shaders.erase(shaderId);
+
+ if (getCurrentShader() == shaderId) {
+ if (isShadered()) {
+ disshader();
+ g_game.internalCreatureChangeOutfit(this, defaultOutfit);
+ }
+
+ setCurrentShader(0);
+ }
+
+ return true;
+}
+
+bool Player::hasShader(const Shader* shader) const
+{
+ if (isAccessPlayer()) {
+ return true;
+ }
+
+ if (shader->premium && !isPremium()) {
+ return false;
+ }
+
+ return shaders.find(shader->id) != shaders.end();
+}
+
+bool Player::hasShaders() const
+{
+ for (const Shader& shader : g_game.shaders.getShaders()) {
+ if (hasShader(&shader)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void Player::disshader() { defaultOutfit.lookShader = 0; }
+
+std::string Player::getCurrentShader_NAME() const
+{
+
+ uint16_t currentShaderId = getCurrentShader();
+
+ Shader* currentShader = g_game.shaders.getShaderByID(static_cast(currentShaderId));
+
+ if (currentShader != nullptr) {
+
+ return currentShader->name;
+ } else {
+
+ return "Outfit - Default";
+ }
+}
+
/*bool Player::addOfflineTrainingTries(skills_t skill, uint64_t tries)
{
if (tries == 0 || skill == SKILL_LEVEL) {
diff --git a/src/player.h b/src/player.h
index 79377d2..27c18ef 100644
--- a/src/player.h
+++ b/src/player.h
@@ -162,7 +162,20 @@ class Player final : public Creature, public Cylinder
bool hasEffect(const Effect* effect) const;
bool hasEffects() const;
void diseffect();
- // -- @
+ // -- @
+ // -- @ Shader
+ uint16_t getRandomShader() const;
+ uint16_t getCurrentShader() const;
+ void setCurrentShader(uint16_t shaderId);
+ bool isShadered() const { return defaultOutfit.lookShader != 0; }
+ bool toggleShader(bool shader);
+ bool tameShader(uint16_t shaderId);
+ bool untameShader(uint16_t shaderId);
+ bool hasShader(const Shader* shader) const;
+ bool hasShaders() const;
+ void disshader();
+ std::string getCurrentShader_NAME() const;
+ // -- @
void sendFYIBox(std::string_view message)
{
if (client) {
@@ -1109,6 +1122,7 @@ class Player final : public Creature, public Cylinder
std::unordered_set wings;
std::unordered_set auras;
std::unordered_set effects;
+ std::unordered_set shaders;
GuildWarVector guildWarVector;
@@ -1144,6 +1158,7 @@ class Player final : public Creature, public Cylinder
int64_t lastToggleWing = 0;
int64_t lastToggleEffect = 0;
int64_t lastToggleAura = 0;
+ int64_t lastToggleShader = 0;
int64_t lastPing;
int64_t lastPong;
@@ -1217,6 +1232,7 @@ class Player final : public Creature, public Cylinder
bool wasWinged = false;
bool wasAuraed = false;
bool wasEffected = false;
+ bool wasShadered = false;
bool pzLocked = false;
bool isConnecting = false;
@@ -1225,6 +1241,7 @@ class Player final : public Creature, public Cylinder
bool randomizeWing = false;
bool randomizeAura = false;
bool randomizeEffect = false;
+ bool randomizeShader = false;
bool inventoryAbilities[CONST_SLOT_LAST + 1] = {};
diff --git a/src/protocolgame.cpp b/src/protocolgame.cpp
index de58308..4bd3435 100644
--- a/src/protocolgame.cpp
+++ b/src/protocolgame.cpp
@@ -1073,6 +1073,13 @@ void ProtocolGame::parseSetOutfit(NetworkMessage& msg)
newOutfit.lookWing = isOTC ? msg.get() : 0;
newOutfit.lookAura = isOTC ? msg.get() : 0;
newOutfit.lookEffect = isOTC ? msg.get() : 0;
+
+ std::string_view shaderName = isOTC ? msg.getString() : "";
+ Shader* shader = nullptr;
+ if (!shaderName.empty()) {
+ shader = g_game.shaders.getShaderByName(shaderName);
+ newOutfit.lookShader = shader ? shader->id : 0;
+ }
g_dispatcher.addTask([=, playerID = player->getID()]() { g_game.playerChangeOutfit(playerID, newOutfit); });
}
@@ -2401,7 +2408,10 @@ void ProtocolGame::sendOutfitWindow()
if (currentEffect) {
currentOutfit.lookEffect = currentEffect->id;
}
-
+ Shader* currentShader = g_game.shaders.getShaderByID(player->getCurrentShader());
+ if (currentShader) {
+ currentOutfit.lookShader = currentShader->id;
+ }
/*bool mounted;
if (player->wasMounted) {
mounted = currentOutfit.lookMount != 0;
@@ -2464,7 +2474,7 @@ void ProtocolGame::sendOutfitWindow()
msg.addString(wing->name);
}
- // auras
+ // auras
std::vector auras;
for (const Aura& aura : g_game.auras.getAuras()) {
if (player->hasAura(&aura)) {
@@ -2478,8 +2488,7 @@ void ProtocolGame::sendOutfitWindow()
msg.addString(aura->name);
}
-
- // effects
+ // effects
std::vector effects;
for (const Effect& effect : g_game.effects.getEffects()) {
if (player->hasEffect(&effect)) {
@@ -2492,7 +2501,19 @@ void ProtocolGame::sendOutfitWindow()
msg.add(effect->id);
msg.addString(effect->name);
}
+ // shader
+ std::vector shaders;
+ for (const Shader& shader : g_game.shaders.getShaders()) {
+ if (player->hasShader(&shader)) {
+ shaders.push_back(&shader);
+ }
+ }
+ msg.addByte(shaders.size());
+ for (const Shader* shader : shaders) {
+ msg.add(shader->id);
+ msg.addString(shader->name);
+ }
}
writeToOutputBuffer(msg);
@@ -2656,6 +2677,9 @@ void ProtocolGame::AddOutfit(NetworkMessage& msg, const Outfit_t& outfit)
msg.add(outfit.lookWing);
msg.add(outfit.lookAura);
msg.add(outfit.lookEffect);
+
+ Shader* shader = g_game.shaders.getShaderByID(outfit.lookShader);
+ msg.addString(shader ? shader->name : "");
}
}
diff --git a/src/shaders.cpp b/src/shaders.cpp
new file mode 100644
index 0000000..fe1deda
--- /dev/null
+++ b/src/shaders.cpp
@@ -0,0 +1,52 @@
+#include "otpch.h"
+
+#include "shaders.h"
+
+#include "pugicast.h"
+#include "tools.h"
+
+bool Shaders::reload()
+{
+ shaders.clear();
+ return loadFromXml();
+}
+
+bool Shaders::loadFromXml()
+{
+ pugi::xml_document doc;
+ pugi::xml_parse_result result = doc.load_file("data/XML/shaders.xml");
+ if (!result) {
+ printXMLError("Error - Shaders::loadFromXml", "data/XML/shaders.xml", result);
+ return false;
+ }
+
+ for (auto shaderNode : doc.child("shaders").children()) {
+ shaders.emplace_back(
+ static_cast(pugi::cast(shaderNode.attribute("id").value())),
+ shaderNode.attribute("name").as_string(),
+ shaderNode.attribute("premium").as_bool()
+ );
+ }
+ shaders.shrink_to_fit();
+ return true;
+}
+
+Shader* Shaders::getShaderByID(uint8_t id)
+{
+ auto it = std::find_if(shaders.begin(), shaders.end(), [id](const Shader& shader) {
+ return shader.id == id;
+ });
+
+ return it != shaders.end() ? &*it : nullptr;
+}
+
+Shader* Shaders::getShaderByName(const std::string_view &name)
+{
+ for (auto& it : shaders) {
+ if (caseInsensitiveEqual(name, it.name)) {
+ return ⁢
+ }
+ }
+ return nullptr;
+ }
+
diff --git a/src/shaders.h b/src/shaders.h
new file mode 100644
index 0000000..590ab75
--- /dev/null
+++ b/src/shaders.h
@@ -0,0 +1,30 @@
+#ifndef FS_SHADERS_H
+#define FS_SHADERS_H
+
+struct Shader
+{
+ Shader(uint8_t id, std::string name, bool premium) :
+ name(std::move(name)), id(id), premium(premium) {}
+
+ uint8_t id;
+ std::string name;
+ bool premium;
+};
+
+class Shaders
+{
+ public:
+ bool reload();
+ bool loadFromXml();
+ Shader* getShaderByID(uint8_t id);
+ Shader* getShaderByName(const std::string_view &name);
+
+ const std::vector& getShaders() const {
+ return shaders;
+ }
+
+ private:
+ std::vector shaders;
+};
+
+#endif
diff --git a/vc17/theforgottenserver.vcxproj b/vc17/theforgottenserver.vcxproj
index 7075553..41bd665 100644
--- a/vc17/theforgottenserver.vcxproj
+++ b/vc17/theforgottenserver.vcxproj
@@ -239,6 +239,7 @@
+
@@ -329,6 +330,7 @@
+