diff --git a/README.md b/README.md
index b024649..da6a43f 100644
--- a/README.md
+++ b/README.md
@@ -16,3 +16,69 @@ If you need help, please visit the [support forum on OTLand](https://otland.net/
### Issues
We use the [issue tracker on GitHub](https://github.com/otland/forgottenserver/issues). Keep in mind that everyone who is watching the repository gets notified by e-mail when there is activity, so be thoughtful and avoid writing comments that aren't meaningful for an issue (e.g. "+1"). If you'd like for an issue to be fixed faster, you should either fix it yourself and submit a pull request, or place a bounty on the issue.
+
+Client:
+```lua
+g_game.enableFeature(GameWingsAurasEffectsShader)
+g_game.enableFeature(GameFormatCreatureName)
+g_game.enableFeature(GameCreatureShader)
+g_game.enableFeature(GameCreatureAttachedEffect)
+g_game.enableFeature(GameItemShader)
+g_game.enableFeature(GameItemTooltipV8);
+```
+DATA BASE
+```sql
+ALTER TABLE `players`
+ADD COLUMN `currentwing` smallint UNSIGNED NOT NULL DEFAULT '0',
+ADD COLUMN `randomizewing` tinyint NOT NULL DEFAULT '0',
+ADD COLUMN `currentaura` smallint UNSIGNED NOT NULL DEFAULT '0',
+ADD COLUMN `randomizeaura` tinyint NOT NULL DEFAULT '0',
+ADD COLUMN `currenteffect` smallint UNSIGNED NOT NULL DEFAULT '0',
+ADD COLUMN `randomizeeffect` tinyint NOT NULL DEFAULT '0',
+ADD COLUMN `currentshader` smallint UNSIGNED NOT NULL DEFAULT '0',
+ADD COLUMN `randomizeshader` tinyint NOT NULL DEFAULT '0';
+```
+
+
+```sql
+ALTER TABLE `players`
+ADD COLUMN `currentwing` smallint UNSIGNED NOT NULL DEFAULT '0',
+ADD COLUMN `randomizewing` tinyint NOT NULL DEFAULT '0',
+ADD COLUMN `currentaura` smallint UNSIGNED NOT NULL DEFAULT '0',
+ADD COLUMN `randomizeaura` tinyint NOT NULL DEFAULT '0',
+ADD COLUMN `currenteffect` smallint UNSIGNED NOT NULL DEFAULT '0',
+ADD COLUMN `randomizeeffect` tinyint NOT NULL DEFAULT '0',
+ADD COLUMN `currentshader` smallint UNSIGNED NOT NULL DEFAULT '0',
+ADD COLUMN `randomizeshader` tinyint NOT NULL DEFAULT '0';
+```
+```sql
+CREATE TABLE IF NOT EXISTS `player_wings` (
+ `player_id` int NOT NULL DEFAULT '0',
+ `wing_id` smallint unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (`player_id`,`wing_id`),
+ FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
+
+CREATE TABLE IF NOT EXISTS `player_effects` (
+ `player_id` int NOT NULL DEFAULT '0',
+ `effect_id` smallint unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (`player_id`,`effect_id`),
+ FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
+
+CREATE TABLE IF NOT EXISTS `player_auras` (
+ `player_id` int NOT NULL DEFAULT '0',
+ `aura_id` smallint unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (`player_id`,`aura_id`),
+ 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;
+```
+
+![img](https://github.com/Nottinghster/otclient/assets/114332266/782e0fcf-b1cf-451e-b102-d7e7943bd50b)
\ No newline at end of file
diff --git a/data/XML/auras.xml b/data/XML/auras.xml
new file mode 100644
index 0000000..cfd2942
--- /dev/null
+++ b/data/XML/auras.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/data/XML/effects.xml b/data/XML/effects.xml
new file mode 100644
index 0000000..2d7cb79
--- /dev/null
+++ b/data/XML/effects.xml
@@ -0,0 +1,4 @@
+
+
+
+
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/XML/wings.xml b/data/XML/wings.xml
new file mode 100644
index 0000000..fd47520
--- /dev/null
+++ b/data/XML/wings.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/data/monster/monsters/orc.xml b/data/monster/monsters/orc.xml
index 80fc57b..47c523e 100644
--- a/data/monster/monsters/orc.xml
+++ b/data/monster/monsters/orc.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/data/scripts/OTCMEHAH/Attachedeffect.lua b/data/scripts/OTCMEHAH/Attachedeffect.lua
new file mode 100644
index 0000000..e55ce3f
--- /dev/null
+++ b/data/scripts/OTCMEHAH/Attachedeffect.lua
@@ -0,0 +1,92 @@
+local mehah = {
+ talkactions = {
+ attacheffect = "!attacheffect",
+ detachEffect = "!detacheffect",
+ playerSetShader = "!playerSetShader",
+ itemSetShader = "!itemSetShader",
+ mapShader = "!mapShader"
+ },
+}
+
+local events = {}
+
+local function processCommand(player, words, param, type, action)
+ if not player:getGroup():getAccess() or player:getAccountType() < ACCOUNT_TYPE_GOD then
+ player:sendCancelMessage("No tienes acceso a este comando.")
+ return false
+ end
+
+ local params, arg
+ if param:find("\"") then
+ params = { param:match("\"(.+)\",*(.*)") }
+ arg = params[1]
+ print(params)
+ print(arg)
+ pdump(params)
+ else
+ params = param:split(", ")
+ arg = params[1]
+ print("2",params)
+ print("2",arg)
+ end
+
+ if not arg then
+ player:sendCancelMessage("Parámetro inválido. Por favor proporciona un argumento válido.")
+ return false
+ end
+
+ local creature
+ if params[2] and params[2] ~= "" then
+ creature = Player(params[2])
+ if not creature then
+ player:sendCancelMessage("El nombre del jugador proporcionado no es válido.")
+ return false
+ end
+ else
+ creature = player
+ end
+
+ action(creature, arg)
+ return false
+end
+
+local attachEffect = TalkAction(mehah.talkactions.attacheffect)
+function attachEffect.onSay(player, words, param, type)
+ return processCommand(player, words, param, type, function(creature, effect)
+ creature:attachEffectById(tonumber(effect), false)
+ end)
+end
+table.insert(events, attachEffect)
+
+local detachEffect = TalkAction(mehah.talkactions.detachEffect)
+function detachEffect.onSay(player, words, param, type)
+ return processCommand(player, words, param, type, function(creature, effect)
+ creature:detachEffectById(tonumber(effect))
+ end)
+end
+table.insert(events, detachEffect)
+
+local setShader = TalkAction(mehah.talkactions.playerSetShader)
+function setShader.onSay(player, words, param, type)
+ return processCommand(player, words, param, type, function(creature, shader)
+ creature:setShader(shader)
+ end)
+end
+table.insert(events, setShader)
+
+local mapShader = TalkAction(mehah.talkactions.mapShader)
+function mapShader.onSay(player, words, param, type)
+ return processCommand(player, words, param, type, function(creature, shader)
+ if creature:getMapShader() ~= shader then
+ creature:setMapShader(shader, true)
+ end
+ end)
+end
+table.insert(events, mapShader)
+
+for _, event in ipairs(events) do
+ event:accountType(ACCOUNT_TYPE_GOD)
+ event:access(true)
+ event:separator(" ")
+ event:register()
+end
diff --git a/data/talkactions/scripts/reload.lua b/data/talkactions/scripts/reload.lua
index f11e9d5..1566867 100644
--- a/data/talkactions/scripts/reload.lua
+++ b/data/talkactions/scripts/reload.lua
@@ -28,6 +28,18 @@ local reloadTypes = {
["mount"] = RELOAD_TYPE_MOUNTS,
["mounts"] = RELOAD_TYPE_MOUNTS,
+
+ ["wing"] = RELOAD_TYPE_WINGS,
+ ["wings"] = RELOAD_TYPE_WINGS,
+
+ ["effect"] = RELOAD_TYPE_EFFECT,
+ ["effects"] = RELOAD_TYPE_EFFECT,
+
+ ["aura"] = RELOAD_TYPE_AURA,
+ ["auras"] = RELOAD_TYPE_AURA,
+
+ ["shader"] = RELOAD_TYPE_SHADERS,
+ ["shaders"] = RELOAD_TYPE_SHADERS,
["move"] = RELOAD_TYPE_MOVEMENTS,
["movement"] = RELOAD_TYPE_MOVEMENTS,
diff --git a/schema.sql b/schema.sql
index a5718cc..29f93ab 100644
--- a/schema.sql
+++ b/schema.sql
@@ -53,6 +53,14 @@ CREATE TABLE IF NOT EXISTS `players` (
`offlinetraining_time` smallint unsigned NOT NULL DEFAULT '43200',
`offlinetraining_skill` int NOT NULL DEFAULT '-1',
`stamina` smallint unsigned NOT NULL DEFAULT '2520',
+ `currentwing` smallint UNSIGNED NOT NULL DEFAULT '0',
+ `randomizewing` tinyint NOT NULL DEFAULT '0',
+ `currentaura` smallint UNSIGNED NOT NULL DEFAULT '0',
+ `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';
`skill_fist` int unsigned NOT NULL DEFAULT 10,
`skill_fist_tries` bigint unsigned NOT NULL DEFAULT 0,
`skill_club` int unsigned NOT NULL DEFAULT 10,
@@ -341,6 +349,34 @@ CREATE TABLE IF NOT EXISTS `server_config` (
PRIMARY KEY `config` (`config`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
+CREATE TABLE IF NOT EXISTS `player_wings` (
+ `player_id` int NOT NULL DEFAULT '0',
+ `wing_id` smallint unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (`player_id`,`wing_id`),
+ FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
+
+CREATE TABLE IF NOT EXISTS `player_effects` (
+ `player_id` int NOT NULL DEFAULT '0',
+ `effect_id` smallint unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (`player_id`,`effect_id`),
+ FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
+
+CREATE TABLE IF NOT EXISTS `player_auras` (
+ `player_id` int NOT NULL DEFAULT '0',
+ `aura_id` smallint unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (`player_id`,`aura_id`),
+ 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 `tile_store` (
`house_id` int NOT NULL,
`data` longblob NOT NULL,
diff --git a/src/auras.cpp b/src/auras.cpp
new file mode 100644
index 0000000..f642cb9
--- /dev/null
+++ b/src/auras.cpp
@@ -0,0 +1,64 @@
+// Copyright 2023 The Forgotten Server Authors. All rights reserved.
+// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file.
+
+#include "otpch.h"
+
+#include "auras.h"
+
+#include "pugicast.h"
+#include "tools.h"
+
+bool Auras::reload()
+{
+ auras.clear();
+ return loadFromXml();
+}
+
+bool Auras::loadFromXml()
+{
+ pugi::xml_document doc;
+ pugi::xml_parse_result result = doc.load_file("data/XML/auras.xml");
+ if (!result) {
+ printXMLError("Error - Auras::loadFromXml", "data/XML/auras.xml", result);
+ return false;
+ }
+
+ for (auto auraNode : doc.child("auras").children()) {
+ uint16_t nodeId = pugi::cast(auraNode.attribute("id").value());
+ if (nodeId == 0 || nodeId > std::numeric_limits::max()) {
+ std::cout << "[Notice - Auras::loadFromXml] Aura id "" << nodeId << "" is not within 1 and 65535 range"
+ << std::endl;
+ continue;
+ }
+
+ if (getAuraByID(nodeId)) {
+ std::cout << "[Notice - Auras::loadFromXml] Duplicate aura with id: " << nodeId << std::endl;
+ continue;
+ }
+
+ auras.emplace_back(
+ static_cast(nodeId),
+ auraNode.attribute("name").as_string(), pugi::cast(auraNode.attribute("speed").value()),
+ auraNode.attribute("premium").as_bool());
+ }
+ auras.shrink_to_fit();
+ return true;
+}
+
+Aura* Auras::getAuraByID(uint16_t id)
+{
+ auto it = std::find_if(auras.begin(), auras.end(), [id](const Aura& aura) { return aura.id == id; });
+
+ return it != auras.end() ? &*it : nullptr;
+}
+
+Aura* Auras::getAuraByName(std::string_view name)
+{
+ for (auto& it : auras) {
+ if (strcasecmp(name.data(), it.name.c_str()) == 0) {
+ return ⁢
+ }
+ }
+
+ return nullptr;
+}
diff --git a/src/auras.h b/src/auras.h
new file mode 100644
index 0000000..6fc32cb
--- /dev/null
+++ b/src/auras.h
@@ -0,0 +1,35 @@
+// Copyright 2023 The Forgotten Server Authors. All rights reserved.
+// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file.
+
+#ifndef FS_AURAS_H
+#define FS_AURAS_H
+
+struct Aura
+{
+ Aura(uint16_t id, std::string_view name, int32_t speed, bool premium) :
+ name{name}, speed{speed}, id{id}, premium{premium}
+ {}
+
+ std::string name;
+ int32_t speed;
+
+ uint16_t id;
+ bool premium;
+};
+
+class Auras
+{
+public:
+ bool reload();
+ bool loadFromXml();
+ Aura* getAuraByID(uint16_t id);
+ Aura* getAuraByName(std::string_view name);
+
+
+ const std::vector& getAuras() const { return auras; }
+
+private:
+ std::vector auras;
+};
+
+#endif // FS_AURAS_H
diff --git a/src/configmanager.cpp b/src/configmanager.cpp
index fdc9195..00c5ed4 100644
--- a/src/configmanager.cpp
+++ b/src/configmanager.cpp
@@ -267,7 +267,7 @@ bool ConfigManager::load()
integer[EXP_FROM_PLAYERS_LEVEL_RANGE] = getGlobalNumber(L, "expFromPlayersLevelRange", 75);
integer[CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES] = getGlobalNumber(L, "checkExpiredMarketOffersEachMinutes", 60);
integer[MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER] = getGlobalNumber(L, "maxMarketOffersAtATimePerPlayer", 100);
- integer[MAX_PACKETS_PER_SECOND] = getGlobalNumber(L, "maxPacketsPerSecond", 25);
+ integer[MAX_PACKETS_PER_SECOND] = getGlobalNumber(L, "maxPacketsPerSecond", 50);
integer[SERVER_SAVE_NOTIFY_DURATION] = getGlobalNumber(L, "serverSaveNotifyDuration", 5);
integer[YELL_MINIMUM_LEVEL] = getGlobalNumber(L, "yellMinimumLevel", 2);
integer[VIP_FREE_LIMIT] = getGlobalNumber(L, "vipFreeLimit", 20);
diff --git a/src/const.h b/src/const.h
index 135e0ad..d1f9b1c 100644
--- a/src/const.h
+++ b/src/const.h
@@ -4,7 +4,7 @@
#ifndef FS_CONST_H_0A49B5996F074465BF44B90F4F780E8B
#define FS_CONST_H_0A49B5996F074465BF44B90F4F780E8B
-static constexpr int32_t NETWORKMESSAGE_MAXSIZE = 24590;
+static constexpr int32_t NETWORKMESSAGE_MAXSIZE = 65500;
enum MagicEffectClasses : uint8_t {
CONST_ME_NONE,
@@ -544,6 +544,10 @@ enum ReloadTypes_t : uint8_t {
RELOAD_TYPE_ITEMS,
RELOAD_TYPE_MONSTERS,
RELOAD_TYPE_MOUNTS,
+ 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.cpp b/src/creature.cpp
index dfe0fa9..8829859 100644
--- a/src/creature.cpp
+++ b/src/creature.cpp
@@ -1627,3 +1627,21 @@ bool Creature::getPathTo(const Position& targetPos, std::vector& dirL
fpp.maxTargetDist = maxTargetDist;
return getPathTo(targetPos, dirList, fpp);
}
+
+void Creature::attachEffectById(uint16_t id)
+{
+ auto it = std::find(attachedEffectList.begin(), attachedEffectList.end(), id);
+ if (it != attachedEffectList.end()) return;
+
+ attachedEffectList.push_back(id);
+ g_game.sendAttachedEffect(this, id);
+}
+void Creature::detachEffectById(uint16_t id)
+{
+ auto it = std::find(attachedEffectList.begin(), attachedEffectList.end(), id);
+ if (it == attachedEffectList.end()) return;
+
+ attachedEffectList.erase(it);
+ g_game.sendDetachEffect(this, id);
+}
+
diff --git a/src/creature.h b/src/creature.h
index 3df113f..f2af768 100644
--- a/src/creature.h
+++ b/src/creature.h
@@ -453,6 +453,9 @@ class Creature : virtual public Thing
bool getPathTo(const Position& targetPos, std::vector& dirList, const FindPathParams& fpp) const;
bool getPathTo(const Position& targetPos, std::vector& dirList, int32_t minTargetDist, int32_t maxTargetDist, bool fullPathSearch = true, bool clearSight = true, int32_t maxSearchDist = 0) const;
+ std::string getShader() const { return shader; }
+ void setShader(const std::string& shaderName) { shader = shaderName; }
+
void incrementReferenceCounter() {
++referenceCounter;
}
@@ -461,6 +464,10 @@ class Creature : virtual public Thing
delete this;
}
}
+ void attachEffectById(uint16_t id);
+ void detachEffectById(uint16_t id);
+ const std::vector getAttachedEffectList() const { return attachedEffectList; }
+
protected:
virtual bool useCacheMap() const {
@@ -481,6 +488,7 @@ class Creature : virtual public Thing
using CountMap = std::map;
CountMap damageMap;
+ std::string shader;
std::list summons;
CreatureEventList eventsList;
@@ -511,12 +519,17 @@ class Creature : virtual public Thing
Outfit_t currentOutfit;
Outfit_t defaultOutfit;
+ uint16_t currentWing;
+ uint16_t currentAura;
+ uint16_t currentEffect;
+ uint16_t currentShader;
Position lastPosition;
LightInfo internalLight;
Direction direction = DIRECTION_SOUTH;
Skulls_t skull = SKULL_NONE;
+ std::vector attachedEffectList;
bool localMapCache[mapWalkHeight][mapWalkWidth] = {{ false }};
bool isInternalRemoved = false;
diff --git a/src/effects.cpp b/src/effects.cpp
new file mode 100644
index 0000000..d70ce95
--- /dev/null
+++ b/src/effects.cpp
@@ -0,0 +1,64 @@
+// Copyright 2023 The Forgotten Server Authors. All rights reserved.
+// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file.
+
+#include "otpch.h"
+
+#include "effects.h"
+
+#include "pugicast.h"
+#include "tools.h"
+
+bool Effects::reload()
+{
+ effects.clear();
+ return loadFromXml();
+}
+
+bool Effects::loadFromXml()
+{
+ pugi::xml_document doc;
+ pugi::xml_parse_result result = doc.load_file("data/XML/effects.xml");
+ if (!result) {
+ printXMLError("Error - Effects::loadFromXml", "data/XML/effects.xml", result);
+ return false;
+ }
+
+ for (auto effectNode : doc.child("effects").children()) {
+ uint16_t nodeId = pugi::cast(effectNode.attribute("id").value());
+ if (nodeId == 0 || nodeId > std::numeric_limits::max()) {
+ std::cout << "[Notice - Effects::loadFromXml] Effect id "" << nodeId << "" is not within 1 and 65535 range"
+ << std::endl;
+ continue;
+ }
+
+ if (getEffectByID(nodeId)) {
+ std::cout << "[Notice - Effects::loadFromXml] Duplicate effect with id: " << nodeId << std::endl;
+ continue;
+ }
+
+ effects.emplace_back(
+ static_cast(nodeId),
+ effectNode.attribute("name").as_string(), pugi::cast(effectNode.attribute("speed").value()),
+ effectNode.attribute("premium").as_bool());
+ }
+ effects.shrink_to_fit();
+ return true;
+}
+
+Effect* Effects::getEffectByID(uint16_t id)
+{
+ auto it = std::find_if(effects.begin(), effects.end(), [id](const Effect& effect) { return effect.id == id; });
+
+ return it != effects.end() ? &*it : nullptr;
+}
+
+Effect* Effects::getEffectByName(std::string_view name)
+{
+ for (auto& it : effects) {
+ if (strcasecmp(name.data(), it.name.c_str()) == 0) {
+ return ⁢
+ }
+ }
+
+ return nullptr;
+}
diff --git a/src/effects.h b/src/effects.h
new file mode 100644
index 0000000..c971ff0
--- /dev/null
+++ b/src/effects.h
@@ -0,0 +1,35 @@
+// Copyright 2023 The Forgotten Server Authors. All rights reserved.
+// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file.
+
+#ifndef FS_EFFECTS_H
+#define FS_EFFECTS_H
+
+struct Effect
+{
+ Effect(uint16_t id, std::string_view name, int32_t speed, bool premium) :
+ name{name}, speed{speed}, id{id}, premium{premium}
+ {}
+
+ std::string name;
+ int32_t speed;
+
+ uint16_t id;
+ bool premium;
+};
+
+class Effects
+{
+public:
+ bool reload();
+ bool loadFromXml();
+ Effect* getEffectByID(uint16_t id);
+ Effect* getEffectByName(std::string_view name);
+
+
+ const std::vector& getEffects() const { return effects; }
+
+private:
+ std::vector effects;
+};
+
+#endif // FS_EFFECTS_H
\ No newline at end of file
diff --git a/src/enums.h b/src/enums.h
index 7dc99ef..0b7f351 100644
--- a/src/enums.h
+++ b/src/enums.h
@@ -491,6 +491,10 @@ struct Outfit_t {
uint8_t lookLegs = 0;
uint8_t lookFeet = 0;
uint8_t lookAddons = 0;
+ uint16_t lookWing = 0;
+ uint16_t lookAura = 0;
+ uint16_t lookEffect = 0;
+ uint16_t lookShader = 0;
};
struct LightInfo {
diff --git a/src/game.cpp b/src/game.cpp
index ef17fcc..74e1544 100644
--- a/src/game.cpp
+++ b/src/game.cpp
@@ -109,6 +109,11 @@ void Game::setGameState(GameState_t newState)
quests.loadFromXml();
mounts.loadFromXml();
+ wings.loadFromXml();
+ auras.loadFromXml();
+ effects.loadFromXml();
+ shaders.loadFromXml();
+
loadMotdNum();
loadPlayersRecord();
loadAccountStorageValues();
@@ -3374,6 +3379,98 @@ void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit)
} else if (player->isMounted()) {
player->dismount();
}
+ // @ wings
+ if (outfit.lookWing != 0) {
+ Wing* wing = wings.getWingByID(outfit.lookWing);
+ if (!wing) {
+ return;
+ }
+
+ if (!player->hasWing(wing)) {
+ return;
+ }
+
+ player->detachEffectById(player->getCurrentWing());
+ player->setCurrentWing(wing->id);
+ player->attachEffectById(wing->id);
+ }
+ else {
+ if (player->isWinged()) {
+ player->diswing();
+ }
+ player->detachEffectById(player->getCurrentWing());
+ player->wasWinged = false;
+ }
+ // @
+ // @ Effect
+ if (outfit.lookEffect != 0) {
+ Effect* effect = effects.getEffectByID(outfit.lookEffect);
+ if (!effect) {
+ return;
+ }
+
+ if (!player->hasEffect(effect)) {
+ return;
+ }
+
+ player->detachEffectById(player->getCurrentEffect());
+ player->setCurrentEffect(effect->id);
+ player->attachEffectById(effect->id);
+ }
+ else {
+ if (player->isEffected()) {
+ player->diseffect();
+ }
+ player->detachEffectById(player->getCurrentEffect());
+ player->wasEffected = false;
+ }
+ // @
+ // @ Aura
+ if (outfit.lookAura != 0) {
+ Aura* aura = auras.getAuraByID(outfit.lookAura);
+ if (!aura) {
+ return;
+ }
+
+ if (!player->hasAura(aura)) {
+ return;
+ }
+
+ player->detachEffectById(player->getCurrentAura());
+ player->setCurrentAura(aura->id);
+ player->attachEffectById(aura->id);
+ }
+ else {
+ if (player->isAuraed()) {
+ player->disaura();
+ }
+ 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;
@@ -5788,3 +5885,92 @@ bool Game::reload(ReloadTypes_t reloadType)
}
return true;
}
+
+void Game::sendAttachedEffect(const Creature* creature, uint16_t effectId)
+{
+ SpectatorVec spectators;
+ map.getSpectators(spectators, creature->getPosition(), false, true, 8, 8, 6, 6);
+ for (Creature* spectator : spectators) {
+ if (Player* spectatorPlayer = spectator->getPlayer()) {
+ spectatorPlayer->sendAttachedEffect(creature, effectId);
+
+ }
+ else {
+ spectator->attachEffectById(effectId);
+ }
+ }
+}
+
+void Game::sendDetachEffect(const Creature* creature, uint16_t effectId)
+{
+ SpectatorVec spectators;
+ map.getSpectators(spectators, creature->getPosition(), false, true, 8, 8, 6, 6);
+ for (Creature* spectator : spectators) {
+ if (Player* spectatorPlayer = spectator->getPlayer()) {
+ spectatorPlayer->sendDetachEffect(creature, effectId);
+
+ }
+ else {
+ spectator->detachEffectById(effectId);
+ }
+ }
+}
+
+void Game::updateCreatureShader(const Creature* creature)
+{
+ SpectatorVec spectators;
+ map.getSpectators(spectators, creature->getPosition(), false, true, 8, 8, 6, 6);
+ for (Creature* spectator : spectators) {
+ if (Player* spectatorPlayer = spectator->getPlayer()) {
+ spectatorPlayer->sendShader(creature, creature->getShader());
+
+ }
+ else {
+ spectator->setShader(creature->getShader());
+ }
+ }
+}
+
+void Game::refreshItem(const Item* item)
+{
+ if (!item || !item->getParent()) return;
+
+ const auto parent = item->getParent();
+
+ if (const auto creature = parent->getCreature()) {
+ if (const auto player = creature->getPlayer()) {
+ int32_t index = creature->getPlayer()->getThingIndex(item);
+ if (index > -1) player->sendInventoryItem(static_cast(index), item);
+ }
+
+ return;
+ }
+
+ if (const auto container = parent->getContainer()) {
+ int32_t index = container->getThingIndex(item);
+ if (index > -1 && index <= std::numeric_limits::max()) {
+ SpectatorVec spectators;
+ g_game.map.getSpectators(spectators, container->getPosition(), false, true, 2, 2, 2, 2);
+
+ // send to client
+ for (auto spectator : spectators) {
+ spectator->getPlayer()->sendUpdateContainerItem(container, static_cast(index), item);
+ }
+ }
+
+ return;
+ }
+
+ if (const auto tile = parent->getTile()) {
+ SpectatorVec spectators;
+ g_game.map.getSpectators(spectators, tile->getPosition(), true, true);
+
+ // send to client
+ for (Creature* spectator : spectators) {
+ if (Player* tmpPlayer = spectator->getPlayer()) {
+ tmpPlayer->sendUpdateTileItem(tile, tile->getPosition(), item);
+ }
+ }
+ return;
+ }
+}
diff --git a/src/game.h b/src/game.h
index 5b03768..4b4f8c9 100644
--- a/src/game.h
+++ b/src/game.h
@@ -8,6 +8,10 @@
#include "combat.h"
#include "groups.h"
#include "map.h"
+#include "wings.h"
+#include "auras.h"
+#include "effects.h"
+#include "shaders.h"
#include "position.h"
#include "item.h"
#include "container.h"
@@ -440,6 +444,12 @@ class Game
void addDistanceEffect(const Position& fromPos, const Position& toPos, uint8_t effect);
static void addDistanceEffect(const SpectatorVec& spectators, const Position& fromPos, const Position& toPos, uint8_t effect);
+ void sendAttachedEffect(const Creature* creature, uint16_t effectId);
+ void sendDetachEffect(const Creature* creature, uint16_t effectId);
+ void updateCreatureShader(const Creature* creature);
+ void refreshItem(const Item* item);
+
+
void setAccountStorageValue(const uint32_t accountId, const uint32_t key, const int32_t value);
int32_t getAccountStorageValue(const uint32_t accountId, const uint32_t key) const;
void loadAccountStorageValues();
@@ -494,6 +504,10 @@ class Game
Mounts mounts;
Raids raids;
Quests quests;
+ Wings wings;
+ Auras auras;
+ Effects effects;
+ Shaders shaders;
std::forward_list- toDecayItems;
diff --git a/src/iologindata.cpp b/src/iologindata.cpp
index d9e982e..d26069b 100644
--- a/src/iologindata.cpp
+++ b/src/iologindata.cpp
@@ -195,13 +195,13 @@ bool IOLoginData::preloadPlayer(Player* player, const std::string& name)
bool IOLoginData::loadPlayerById(Player* player, uint32_t id)
{
Database& db = Database::getInstance();
- 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`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `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)));
+ 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`, `currentwing`, `randomizewing`,`currenteffect`, `randomizeeffect`,`currentaura`, `randomizeaura`,`currentshader`, `randomizeshader`,`posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `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)));
}
bool IOLoginData::loadPlayerByName(Player* player, const std::string& name)
{
Database& db = Database::getInstance();
- 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`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `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))));
+ 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`,`currentwing`, `randomizewing`,`currenteffect`, `randomizeeffect`,`currentaura`, `randomizeaura`,`currentshader`, `randomizeshader`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `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))));
}
static GuildWarVector getWarList(uint32_t guildId)
@@ -317,7 +317,15 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result)
player->defaultOutfit.lookFeet = result->getNumber("lookfeet");
player->defaultOutfit.lookAddons = result->getNumber("lookaddons");
player->currentOutfit = player->defaultOutfit;
+ 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->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);
@@ -544,6 +552,67 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result)
player->addVIPInternal(result->getNumber("player_id"));
} while (result->next());
}
+ // load Wings
+ if ((result = db.storeQuery(
+ fmt::format("SELECT `wing_id` FROM `player_wings` WHERE `player_id` = {:d}", player->getGUID())))) {
+ do {
+ player->tameWing(result->getNumber("wing_id"));
+ } while (result->next());
+ }
+ // load auras
+ if ((result = db.storeQuery(
+ fmt::format("SELECT `aura_id` FROM `player_auras` WHERE `player_id` = {:d}", player->getGUID())))) {
+ do {
+ player->tameAura(result->getNumber("aura_id"));
+ } while (result->next());
+ }
+ // load effects
+ if ((result = db.storeQuery(
+ fmt::format("SELECT `effect_id` FROM `player_effects` WHERE `player_id` = {:d}", player->getGUID())))) {
+ do {
+ player->tameEffect(result->getNumber("effect_id"));
+ } while (result->next());
+ }
+
+ // @-- wings
+ uint16_t currentWing = player->getCurrentWing();
+
+ if (currentWing > 0) {
+ player->attachEffectById(currentWing);
+ }
+ // @--
+ // @-- Aura
+ uint16_t currentAura = player->getCurrentAura();
+
+ if (currentAura > 0) {
+ player->attachEffectById(currentAura);
+ }
+ // @--
+ // @-- Effects
+ uint16_t currentEffect = player->getCurrentEffect();
+
+ if (currentEffect > 0) {
+ player->attachEffectById(currentEffect);
+ }
+ // @--
+
+ // 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();
@@ -651,6 +720,14 @@ bool IOLoginData::savePlayer(Player* player)
query << "`looklegs` = " << static_cast(player->defaultOutfit.lookLegs) << ',';
query << "`looktype` = " << player->defaultOutfit.lookType << ',';
query << "`lookaddons` = " << static_cast(player->defaultOutfit.lookAddons) << ',';
+ query << "`currentwing` = " << static_cast(player->currentWing) << ',';
+ query << "`randomizewing` = " << player->randomizeWing << ",";
+ query << "`currenteffect` = " << static_cast(player->currentEffect) << ',';
+ 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 << ',';
@@ -833,7 +910,73 @@ bool IOLoginData::savePlayer(Player* player)
if (!storageQuery.execute()) {
return false;
}
+ // --@ save wings
+ if (!db.executeQuery(fmt::format("DELETE FROM `player_wings` WHERE `player_id` = {:d}", player->getGUID()))) {
+ return false;
+ }
+
+ DBInsert wingQuery("INSERT INTO `player_wings` (`player_id`, `wing_id`) VALUES ");
+
+ for (const auto& it : player->wings) {
+ if (!wingQuery.addRow(fmt::format("{:d}, {:d}", player->getGUID(), it))) {
+ return false;
+ }
+ }
+ if (!wingQuery.execute()) {
+ return false;
+ }
+ // --@
+
+ // --@ save effects
+ if (!db.executeQuery(fmt::format("DELETE FROM `player_effects` WHERE `player_id` = {:d}", player->getGUID()))) {
+ return false;
+ }
+
+ DBInsert effectQuery("INSERT INTO `player_effects` (`player_id`, `effects_id`) VALUES ");
+
+ for (const auto& it : player->effects) {
+ if (!effectQuery.addRow(fmt::format("{:d}, {:d}", player->getGUID(), it))) {
+ return false;
+ }
+ }
+ if (!effectQuery.execute()) {
+ return false;
+ }
+ // --@
+
+ // --@ save aura
+ if (!db.executeQuery(fmt::format("DELETE FROM `player_auras` WHERE `player_id` = {:d}", player->getGUID()))) {
+ return false;
+ }
+
+ DBInsert auraQuery("INSERT INTO `player_auras` (`player_id`, `aura_id`) VALUES ");
+
+ for (const auto& it : player->auras) {
+ if (!auraQuery.addRow(fmt::format("{:d}, {:d}", player->getGUID(), it))) {
+ return false;
+ }
+ }
+ if (!auraQuery.execute()) {
+ return false;
+ }
+ // --@
+
+ // 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/item.h b/src/item.h
index dbbe214..b91a464 100644
--- a/src/item.h
+++ b/src/item.h
@@ -226,6 +226,14 @@ class ItemAttributes
void set(const T& v) {
value = v;
}
+ const std::string& getString() const
+ {
+ if (value.type() == typeid(std::string)) {
+ return boost::get(value);
+ }
+
+ return emptyString;
+ }
template
const T& get();
@@ -1034,6 +1042,26 @@ class Item : virtual public Thing
bool isRemoved() const override {
return !parent || parent->isRemoved();
}
+ void setShader(const std::string& shaderName)
+ {
+ if (shaderName.empty()) {
+ removeCustomAttribute("shader");
+ return;
+ }
+
+ ItemAttributes::CustomAttribute val;
+ val.set(shaderName);
+
+ std::string key = "shader";
+ setCustomAttribute(key, val);
+ }
+
+ bool hasShader() const { return const_cast
- (this)->getCustomAttribute("shader") != nullptr; }
+ std::string getShader() const
+ {
+ auto shader = const_cast
- (this)->getCustomAttribute("shader");
+ return shader ? shader->getString() : "";
+ }
protected:
Cylinder* parent = nullptr;
diff --git a/src/luascript.cpp b/src/luascript.cpp
index f46b1a8..a84b6e4 100644
--- a/src/luascript.cpp
+++ b/src/luascript.cpp
@@ -943,6 +943,10 @@ void LuaScriptInterface::pushOutfit(lua_State* L, const Outfit_t& outfit)
setField(L, "lookBody", outfit.lookBody);
setField(L, "lookLegs", outfit.lookLegs);
setField(L, "lookFeet", outfit.lookFeet);
+ setField(L, "lookWing", outfit.lookWing);
+ setField(L, "lookAura", outfit.lookAura);
+ setField(L, "lookEffect", outfit.lookEffect);
+ setField(L, "lookShader", outfit.lookShader);
setField(L, "lookAddons", outfit.lookAddons);
setField(L, "lookMount", outfit.lookMount);
}
@@ -978,6 +982,38 @@ void LuaScriptInterface::pushLoot(lua_State* L, const std::vector& lo
lua_rawseti(L, -2, ++index);
}
}
+void LuaScriptInterface::pushWing(lua_State* L, const Wing* wing)
+{
+ lua_createtable(L, 0, 5);
+ setField(L, "name", wing->name);
+ setField(L, "speed", wing->speed);
+ setField(L, "id", wing->id);
+ setField(L, "premium", wing->premium);
+}
+void LuaScriptInterface::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 LuaScriptInterface::pushAura(lua_State* L, const Aura* aura)
+{
+ lua_createtable(L, 0, 5);
+ setField(L, "name", aura->name);
+ setField(L, "speed", aura->speed);
+ setField(L, "id", aura->id);
+ setField(L, "premium", aura->premium);
+}
+void LuaScriptInterface::pushEffect(lua_State* L, const Effect* effect)
+{
+ lua_createtable(L, 0, 5);
+ setField(L, "name", effect->name);
+ setField(L, "speed", effect->speed);
+ setField(L, "id", effect->id);
+ setField(L, "premium", effect->premium);
+}
#define registerEnum(value) { std::string enumName = #value; registerGlobalVariable(enumName.substr(enumName.find_last_of(':') + 1), value); }
#define registerEnumIn(tableName, value) { std::string enumName = #value; registerVariable(tableName, enumName.substr(enumName.find_last_of(':') + 1), value); }
@@ -1883,6 +1919,10 @@ 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);
registerEnum(RELOAD_TYPE_MOVEMENTS)
registerEnum(RELOAD_TYPE_NPCS)
registerEnum(RELOAD_TYPE_QUESTS)
@@ -2049,6 +2089,10 @@ void LuaScriptInterface::registerFunctions()
registerMethod("Game", "getAccountStorageValue", LuaScriptInterface::luaGameGetAccountStorageValue);
registerMethod("Game", "setAccountStorageValue", LuaScriptInterface::luaGameSetAccountStorageValue);
registerMethod("Game", "saveAccountStorageValues", LuaScriptInterface::luaGameSaveAccountStorageValues);
+ registerMethod("Game", "getWings", LuaScriptInterface::luaGameGetWings);
+ registerMethod("Game", "getEffects", LuaScriptInterface::luaGameGetEffects);
+ registerMethod("Game", "getAuras", LuaScriptInterface::luaGameGetAuras);
+ registerMethod("Game", "getShaders", LuaScriptInterface::luaGameGetShaders);
// Variant
registerClass("Variant", "", LuaScriptInterface::luaVariantCreate);
@@ -2227,6 +2271,9 @@ void LuaScriptInterface::registerFunctions()
registerMethod("Item", "setStoreItem", LuaScriptInterface::luaItemSetStoreItem);
registerMethod("Item", "isStoreItem", LuaScriptInterface::luaItemIsStoreItem);
+ registerMethod("Item", "setShader", LuaScriptInterface::luaItemSetShader);
+ registerMethod("Item", "getShader", LuaScriptInterface::luaItemGetShader);
+ registerMethod("Item", "hasShader", LuaScriptInterface::luaItemHasShader);
// Container
registerClass("Container", "Item", LuaScriptInterface::luaContainerCreate);
@@ -2336,6 +2383,11 @@ void LuaScriptInterface::registerFunctions()
registerMethod("Creature", "getZone", LuaScriptInterface::luaCreatureGetZone);
+ registerMethod("Creature", "attachEffectById", LuaScriptInterface::luaCreatureAttachEffectById);
+ registerMethod("Creature", "detachEffectById", LuaScriptInterface::luaCreatureDetachEffectById);
+ registerMethod("Creature", "getShader", LuaScriptInterface::luaCreatureGetShader);
+ registerMethod("Creature", "setShader", LuaScriptInterface::luaCreatureSetShader);
+
// Player
registerClass("Player", "Creature", LuaScriptInterface::luaPlayerCreate);
registerMetaMethod("Player", "__eq", LuaScriptInterface::luaUserdataCompare);
@@ -2508,7 +2560,30 @@ void LuaScriptInterface::registerFunctions()
registerMethod("Player", "getFightMode", LuaScriptInterface::luaPlayerGetFightMode);
registerMethod("Player", "getStoreInbox", LuaScriptInterface::luaPlayerGetStoreInbox);
-
+ // @ wings
+ registerMethod("Player", "addWing", LuaScriptInterface::luaPlayerAddWing);
+ registerMethod("Player", "removeWing", LuaScriptInterface::luaPlayerRemoveWing);
+ registerMethod("Player", "hasWing", LuaScriptInterface::luaPlayerHasWing);
+ registerMethod("Player", "toggleWing", LuaScriptInterface::luaPlayerToggleWing);
+ // @
+ // @ auras
+ registerMethod("Player", "addAura", LuaScriptInterface::luaPlayerAddAura);
+ registerMethod("Player", "removeAura", LuaScriptInterface::luaPlayerRemoveAura);
+ registerMethod("Player", "hasAura", LuaScriptInterface::luaPlayerHasAura);
+ registerMethod("Player", "toggleAura", LuaScriptInterface::luaPlayerToggleAura);
+ // @
+ // @ effects
+ registerMethod("Player", "addEffect", LuaScriptInterface::luaPlayerAddEffect);
+ registerMethod("Player", "removeEffect", LuaScriptInterface::luaPlayerRemoveEffect);
+ registerMethod("Player", "hasEffect", LuaScriptInterface::luaPlayerHasEffect);
+ registerMethod("Player", "toggleEffect", LuaScriptInterface::luaPlayerToggleEffect);
+ // @
+ registerMethod("Player", "addShader", LuaScriptInterface::luaPlayerAddShader);
+ registerMethod("Player", "removeShader", LuaScriptInterface::luaPlayerRemoveShader);
+ registerMethod("Player", "hasShader", LuaScriptInterface::luaPlayerHasShader);
+ registerMethod("Player", "toggleShader", LuaScriptInterface::luaPlayerToggleShader);
+ registerMethod("Player", "getMapShader", LuaScriptInterface::luaPlayerGetMapShader);
+ registerMethod("Player", "setMapShader", LuaScriptInterface::luaPlayerSetMapShader);
// Monster
registerClass("Monster", "Creature", LuaScriptInterface::luaMonsterCreate);
registerMetaMethod("Monster", "__eq", LuaScriptInterface::luaUserdataCompare);
@@ -4492,6 +4567,19 @@ int LuaScriptInterface::luaGameCreateMonster(lua_State* L)
if (g_game.placeCreature(monster, position, extended, force)) {
pushUserdata(L, monster);
setMetatable(L, -1, "Monster");
+ if (monster->rayosEffect() != 0) {
+ monster->attachEffectById(monster->rayosEffect());
+ }
+ if (monster->wignsEffect() != 0) {
+ monster->attachEffectById(monster->wignsEffect());
+ }
+ if (monster->auraEffect() != 0) {
+ monster->attachEffectById(monster->auraEffect());
+ }
+ if (monster->shaderEffect() != "") {
+ monster->setShader(monster->shaderEffect());
+ g_game.updateCreatureShader(monster);
+ }
} else {
delete monster;
lua_pushnil(L);
@@ -4671,7 +4759,65 @@ int LuaScriptInterface::luaGameSaveAccountStorageValues(lua_State* L)
return 1;
}
+int LuaScriptInterface::luaGameGetWings(lua_State* L)
+{
+ // Game.getWings()
+ const auto& wings = g_game.wings.getWings();
+ lua_createtable(L, wings.size(), 0);
+
+ int index = 0;
+ for (const auto& wing : wings) {
+ pushWing(L, &wing);
+ lua_rawseti(L, -2, ++index);
+ }
+
+ return 1;
+}
+// shaders
+int LuaScriptInterface::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 LuaScriptInterface::luaGameGetAuras(lua_State* L)
+{
+ // Game.getAuras()
+ const auto& auras = g_game.auras.getAuras();
+ lua_createtable(L, auras.size(), 0);
+
+ int index = 0;
+ for (const auto& aura : auras) {
+ pushAura(L, &aura);
+ lua_rawseti(L, -2, ++index);
+ }
+
+ return 1;
+}
+
+int LuaScriptInterface::luaGameGetEffects(lua_State* L)
+{
+ // Game.getEffects()
+ const auto& effects = g_game.effects.getEffects();
+ lua_createtable(L, effects.size(), 0);
+
+ int index = 0;
+ for (const auto& effect : effects) {
+ pushEffect(L, &effect);
+ lua_rawseti(L, -2, ++index);
+ }
+
+ return 1;
+}
// Variant
int LuaScriptInterface::luaVariantCreate(lua_State* L)
{
@@ -6821,6 +6967,7 @@ int LuaScriptInterface::luaItemIsLoadedFromMap(lua_State* L)
return 1;
}
+
int LuaScriptInterface::luaItemSetStoreItem(lua_State* L)
{
// item:setStoreItem(storeItem)
@@ -6845,6 +6992,50 @@ int LuaScriptInterface::luaItemIsStoreItem(lua_State* L)
}
return 1;
}
+int LuaScriptInterface::luaItemHasShader(lua_State* L)
+{
+ // item:getShader()
+ const auto* item = getUserdata(L, 1);
+ if (item) {
+ pushBoolean(L, item->hasShader());
+ }
+ else {
+ lua_pushnil(L);
+ }
+
+ return 1;
+}
+
+int LuaScriptInterface::luaItemGetShader(lua_State* L)
+{
+ // item:getShader()
+ const auto* item = getUserdata(L, 1);
+ if (item) {
+ pushString(L, item->getShader());
+ }
+ else {
+ lua_pushnil(L);
+ }
+
+ return 1;
+}
+
+int LuaScriptInterface::luaItemSetShader(lua_State* L)
+{
+ // item:setShader(shaderName)
+ auto* item = getUserdata
- (L, 1);
+ if (!item) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ item->setShader(getString(L, 2));
+ g_game.refreshItem(item);
+
+ pushBoolean(L, true);
+ return 1;
+}
+
// Container
int LuaScriptInterface::luaContainerCreate(lua_State* L)
@@ -8130,6 +8321,71 @@ int LuaScriptInterface::luaCreatureGetZone(lua_State* L)
}
return 1;
}
+int LuaScriptInterface:: luaCreatureAttachEffectById(lua_State* L)
+{
+ // creature:attachEffectById(effectId, [temporary])
+ Creature* creature = getUserdata(L, 1);
+ if (!creature) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ uint16_t id = getNumber(L, 2);
+ bool temp = getBoolean(L, 3, false);
+
+ if (temp)
+ g_game.sendAttachedEffect(creature, id);
+ else
+ creature->attachEffectById(id);
+
+ return 1;
+}
+
+int LuaScriptInterface::luaCreatureDetachEffectById(lua_State* L)
+{
+ // creature:detachEffectById(effectId)
+ Creature* creature = getUserdata(L, 1);
+ if (!creature) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ uint16_t id = getNumber(L, 2);
+ creature->detachEffectById(id);
+
+ return 1;
+}
+
+int LuaScriptInterface::luaCreatureGetShader(lua_State* L)
+{
+ // creature:getShader()
+ const auto* creature = getUserdata(L, 1);
+ if (creature) {
+ pushString(L, creature->getShader());
+ }
+ else {
+ lua_pushnil(L);
+ }
+
+ return 1;
+}
+
+int LuaScriptInterface::luaCreatureSetShader(lua_State* L)
+{
+ // creature:setShader(shaderName)
+ auto* creature = getUserdata(L, 1);
+ if (!creature) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ creature->setShader(getString(L, 2));
+ g_game.updateCreatureShader(creature);
+
+ pushBoolean(L, true);
+ return 1;
+}
+
// Player
int LuaScriptInterface::luaPlayerCreate(lua_State* L)
@@ -10274,7 +10530,415 @@ int LuaScriptInterface::luaPlayerGetStoreInbox(lua_State* L)
setMetatable(L, -1, "Container");
return 1;
}
+// @ wings
+
+int LuaScriptInterface::luaPlayerAddWing(lua_State* L)
+{
+ // player:addWing(wingId or wingName)
+ Player* player = getUserdata(L, 1);
+ if (!player) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ uint16_t wingId;
+ if (isNumber(L, 2)) {
+ wingId = getNumber(L, 2);
+ }
+ else {
+ Wing* wing = g_game.wings.getWingByName(getString(L, 2));
+ if (!wing) {
+ lua_pushnil(L);
+ return 1;
+ }
+ wingId = wing->id;
+ }
+
+ pushBoolean(L, player->tameWing(wingId));
+ return 1;
+}
+
+int LuaScriptInterface::luaPlayerRemoveWing(lua_State* L)
+{
+ // player:removeWing(wingId or wingName)
+ Player* player = getUserdata(L, 1);
+ if (!player) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ uint16_t wingId;
+ if (isNumber(L, 2)) {
+ wingId = getNumber(L, 2);
+ }
+ else {
+ Wing* wing = g_game.wings.getWingByName(getString(L, 2));
+ if (!wing) {
+ lua_pushnil(L);
+ return 1;
+ }
+ wingId = wing->id;
+ }
+
+ pushBoolean(L, player->untameWing(wingId));
+ return 1;
+}
+
+int LuaScriptInterface::luaPlayerHasWing(lua_State* L)
+{
+ // player:hasWing(wingId or wingName)
+ const Player* player = getUserdata(L, 1);
+ if (!player) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ Wing* wing = nullptr;
+ if (isNumber(L, 2)) {
+ wing = g_game.wings.getWingByID(getNumber(L, 2));
+ }
+ else {
+ wing = g_game.wings.getWingByName(getString(L, 2));
+ }
+
+ if (wing) {
+ pushBoolean(L, player->hasWing(wing));
+ }
+ else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
+int LuaScriptInterface::luaPlayerToggleWing(lua_State* L)
+{
+ // player:toggleWing(wing)
+ Player* player = getUserdata(L, 1);
+ if (!player) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ bool wing = getBoolean(L, 2);
+ pushBoolean(L, player->toggleWing(wing));
+ return 1;
+}
+
+// @
+// @ auras
+
+int LuaScriptInterface::luaPlayerAddAura(lua_State* L)
+{
+ // player:addAura(auraId or auraName)
+ Player* player = getUserdata(L, 1);
+ if (!player) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ uint16_t auraId;
+ if (isNumber(L, 2)) {
+ auraId = getNumber(L, 2);
+ }
+ else {
+ Aura* aura = g_game.auras.getAuraByName(getString(L, 2));
+ if (!aura) {
+ lua_pushnil(L);
+ return 1;
+ }
+ auraId = aura->id;
+ }
+
+ pushBoolean(L, player->tameAura(auraId));
+ return 1;
+}
+
+int LuaScriptInterface::luaPlayerRemoveAura(lua_State* L)
+{
+ // player:removeAura(auraId or auraName)
+ Player* player = getUserdata(L, 1);
+ if (!player) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ uint16_t auraId;
+ if (isNumber(L, 2)) {
+ auraId = getNumber(L, 2);
+ }
+ else {
+ Aura* aura = g_game.auras.getAuraByName(getString(L, 2));
+ if (!aura) {
+ lua_pushnil(L);
+ return 1;
+ }
+ auraId = aura->id;
+ }
+
+ pushBoolean(L, player->untameAura(auraId));
+ return 1;
+}
+
+int LuaScriptInterface::luaPlayerHasAura(lua_State* L)
+{
+ // player:hasAura(auraId or auraName)
+ const Player* player = getUserdata(L, 1);
+ if (!player) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ Aura* aura = nullptr;
+ if (isNumber(L, 2)) {
+ aura = g_game.auras.getAuraByID(getNumber(L, 2));
+ }
+ else {
+ aura = g_game.auras.getAuraByName(getString(L, 2));
+ }
+
+ if (aura) {
+ pushBoolean(L, player->hasAura(aura));
+ }
+ else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
+int LuaScriptInterface::luaPlayerToggleAura(lua_State* L)
+{
+ // player:toggleAura(aura)
+ Player* player = getUserdata(L, 1);
+ if (!player) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ bool aura = getBoolean(L, 2);
+ pushBoolean(L, player->toggleAura(aura));
+ return 1;
+}
+
+// @
+// @ effects
+
+int LuaScriptInterface::luaPlayerAddEffect(lua_State* L)
+{
+ // player:addEffect(effectId or effectName)
+ Player* player = getUserdata(L, 1);
+ if (!player) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ uint16_t effectId;
+ if (isNumber(L, 2)) {
+ effectId = getNumber(L, 2);
+ }
+ else {
+ Effect* effect = g_game.effects.getEffectByName(getString(L, 2));
+ if (!effect) {
+ lua_pushnil(L);
+ return 1;
+ }
+ effectId = effect->id;
+ }
+
+ pushBoolean(L, player->tameEffect(effectId));
+ return 1;
+}
+
+int LuaScriptInterface::luaPlayerRemoveEffect(lua_State* L)
+{
+ // player:removeEffect(effectId or effectName)
+ Player* player = getUserdata(L, 1);
+ if (!player) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ uint16_t effectId;
+ if (isNumber(L, 2)) {
+ effectId = getNumber(L, 2);
+ }
+ else {
+ Effect* effect = g_game.effects.getEffectByName(getString(L, 2));
+ if (!effect) {
+ lua_pushnil(L);
+ return 1;
+ }
+ effectId = effect->id;
+ }
+
+ pushBoolean(L, player->untameEffect(effectId));
+ return 1;
+}
+
+int LuaScriptInterface::luaPlayerHasEffect(lua_State* L)
+{
+ // player:hasEffect(effectId or effectName)
+ const Player* player = getUserdata(L, 1);
+ if (!player) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ Effect* effect = nullptr;
+ if (isNumber(L, 2)) {
+ effect = g_game.effects.getEffectByID(getNumber(L, 2));
+ }
+ else {
+ effect = g_game.effects.getEffectByName(getString(L, 2));
+ }
+
+ if (effect) {
+ pushBoolean(L, player->hasEffect(effect));
+ }
+ else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
+int LuaScriptInterface::luaPlayerToggleEffect(lua_State* L)
+{
+ // player:toggleEffect(effect)
+ Player* player = getUserdata(L, 1);
+ if (!player) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ bool effect = getBoolean(L, 2);
+ pushBoolean(L, player->toggleEffect(effect));
+ return 1;
+}
+
+// @
+int LuaScriptInterface::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 (isNumber(L, 2)) {
+ shaderId = getNumber(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 LuaScriptInterface::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 (isNumber(L, 2)) {
+ shaderId = getNumber(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 LuaScriptInterface::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 (isNumber(L, 2)) {
+ shader = g_game.shaders.getShaderByID(getNumber(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 LuaScriptInterface::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 LuaScriptInterface::LuaScriptInterface::luaPlayerGetMapShader(lua_State* L)
+{
+ // player:getMapShader()
+ const auto* player = getUserdata(L, 1);
+ if (player) {
+ pushString(L, player->getMapShader());
+ }
+ else {
+ lua_pushnil(L);
+ }
+
+ return 1;
+}
+
+int LuaScriptInterface::luaPlayerSetMapShader(lua_State* L)
+{
+ // player:setMapShader(shaderName, [temporary])
+ auto* player = getUserdata(L, 1);
+ if (!player) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ const auto& shaderName = getString(L, 2);
+ bool temp = getBoolean(L, 3, false);
+
+ if (!temp) player->setMapShader(shaderName);
+
+ player->sendMapShader(shaderName);
+
+ pushBoolean(L, true);
+ return 1;
+}
// Monster
int LuaScriptInterface::luaMonsterCreate(lua_State* L)
{
@@ -10295,6 +10959,20 @@ int LuaScriptInterface::luaMonsterCreate(lua_State* L)
if (monster) {
pushUserdata(L, monster);
setMetatable(L, -1, "Monster");
+
+ if (monster->rayosEffect() != 0) {
+ monster->attachEffectById(monster->rayosEffect());
+ }
+ if (monster->wignsEffect() != 0) {
+ monster->attachEffectById(monster->wignsEffect());
+ }
+ if (monster->auraEffect() != 0) {
+ monster->attachEffectById(monster->auraEffect());
+ }
+ if (monster->shaderEffect() != "") {
+ monster->setShader(monster->shaderEffect());
+ g_game.updateCreatureShader(monster);
+ }
} else {
lua_pushnil(L);
}
diff --git a/src/luascript.h b/src/luascript.h
index fea060b..9817df0 100644
--- a/src/luascript.h
+++ b/src/luascript.h
@@ -86,8 +86,11 @@ class LuaScriptInterface;
class Cylinder;
class Game;
+struct Wing;
+struct Aura;
+struct Effect;
struct LootBlock;
-
+struct Shader;
class ScriptEnvironment
{
public:
@@ -400,6 +403,10 @@ class LuaScriptInterface
static void pushPosition(lua_State* L, const Position& position, int32_t stackpos = 0);
static void pushOutfit(lua_State* L, const Outfit_t& outfit);
static void pushOutfit(lua_State* L, const Outfit* outfit);
+ static void pushWing(lua_State* L, const Wing* wing);
+ static void pushAura(lua_State* L, const Aura* aura);
+ static void pushEffect(lua_State* L, const Effect* effect);
+ static void pushShader(lua_State* L, const Shader* shader);
static void pushLoot(lua_State* L, const std::vector& lootList);
//
@@ -586,6 +593,46 @@ class LuaScriptInterface
static int luaGameSetAccountStorageValue(lua_State* L);
static int luaGameSaveAccountStorageValues(lua_State* L);
+ static int luaGameGetWings(lua_State* L);
+ static int luaGameGetEffects(lua_State* L);
+ static int luaGameGetAuras(lua_State* L);
+ static int luaGameGetShaders(lua_State* L);
+
+ static int luaItemSetShader(lua_State* L);
+ static int luaItemHasShader(lua_State* L);
+ static int luaItemGetShader(lua_State* L);
+
+ static int luaCreatureAttachEffectById(lua_State* L);
+
+ static int luaCreatureDetachEffectById(lua_State* L);
+
+ static int luaCreatureSetShader(lua_State* L);
+ static int luaPlayerAddWing(lua_State* L);
+ static int luaPlayerRemoveWing(lua_State* L);
+ static int luaPlayerHasWing(lua_State* L);
+
+
+ static int luaCreatureGetShader(lua_State* L);
+ static int luaPlayerToggleWin(lua_State* L);
+ static int luaPlayerAddAura(lua_State* L);
+ static int luaPlayerRemoveAura(lua_State* L);
+ static int luaPlayerHasAura(lua_State* L);
+ static int luaPlayerToggleAura(lua_State* L);
+ static int luaPlayerAddEffect(lua_State* L);
+ static int luaPlayerRemoveEffect(lua_State* L);
+
+ static int luaPlayerHasEffect(lua_State* L);
+ static int luaPlayerToggleEffect(lua_State* L);
+ static int luaPlayerAddShader(lua_State* L);
+ static int luaPlayerRemoveShader(lua_State* L);
+ static int luaPlayerHasShader(lua_State* L);
+ static int luaPlayerToggleShader(lua_State* L);
+ static int luaPlayerGetMapShader(lua_State* L);
+ static int luaPlayerSetMapShader(lua_State* L);
+
+ static int luaPlayerToggleWing(lua_State* L);
+
+
// Variant
static int luaVariantCreate(lua_State* L);
diff --git a/src/monster.h b/src/monster.h
index 1d3c126..d7ee887 100644
--- a/src/monster.h
+++ b/src/monster.h
@@ -104,6 +104,13 @@ class Monster final : public Creature
uint32_t getManaCost() const {
return mType->info.manaCost;
}
+ uint16_t auraEffect() const { return mType->info.auraEffect; }
+
+ std::string shaderEffect() const { return mType->info.shaderEffect; }
+
+ uint16_t wignsEffect() const { return mType->info.wignsEffect; }
+
+ uint16_t rayosEffect() const { return mType->info.rayosEffect; }
void setSpawn(Spawn* spawn) {
this->spawn = spawn;
}
diff --git a/src/monsters.cpp b/src/monsters.cpp
index d5ac145..a0eab40 100644
--- a/src/monsters.cpp
+++ b/src/monsters.cpp
@@ -837,7 +837,18 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m
if ((attr = monsterNode.attribute("manacost"))) {
mType->info.manaCost = pugi::cast(attr.value());
}
-
+ if ((attr = monsterNode.attribute("rayosEffect"))) {
+ mType->info.rayosEffect = pugi::cast(attr.value());
+ }
+ if ((attr = monsterNode.attribute("wignsEffect"))) {
+ mType->info.wignsEffect = pugi::cast(attr.value());
+ }
+ if ((attr = monsterNode.attribute("auraEffect"))) {
+ mType->info.auraEffect = pugi::cast(attr.value());
+ }
+ if ((attr = monsterNode.attribute("shaderEffect"))) {
+ mType->info.shaderEffect = (attr.as_string());
+ }
if ((attr = monsterNode.attribute("skull"))) {
mType->info.skull = getSkullType(asLowerCaseString(attr.as_string()));
}
diff --git a/src/monsters.h b/src/monsters.h
index 11fd4dd..3fff165 100644
--- a/src/monsters.h
+++ b/src/monsters.h
@@ -102,6 +102,10 @@ class MonsterType
LightInfo light = {};
uint16_t lookcorpse = 0;
+ uint16_t rayosEffect = 0;
+ uint16_t wignsEffect = 0;
+ std::string shaderEffect = "";
+ uint16_t auraEffect = 0;
uint64_t experience = 0;
diff --git a/src/networkmessage.cpp b/src/networkmessage.cpp
index 914a85a..56bca94 100644
--- a/src/networkmessage.cpp
+++ b/src/networkmessage.cpp
@@ -97,7 +97,9 @@ void NetworkMessage::addItem(uint16_t id, uint8_t count, bool withDescription)
addByte(0xFE); // random phase (0xFF for async)
}
if (withDescription) {
- addString("");
+ addString(""); // g_game.enableFeature(GameItemShader)
+ addString(""); // g_game.enableFeature(GameItemTooltipV8);
+
}
}
@@ -118,7 +120,9 @@ void NetworkMessage::addItem(const Item* item, bool withDescription)
addByte(0xFE); // random phase (0xFF for async)
}
if (withDescription) {
- addString(item->getDescription(0));
+ addString(item->getShader()); // g_game.enableFeature(GameItemShader)
+ addString(item->getDescription(0)); // g_game.enableFeature(GameItemTooltipV8);
+
}
}
diff --git a/src/player.cpp b/src/player.cpp
index 879e8f8..cb8f130 100644
--- a/src/player.cpp
+++ b/src/player.cpp
@@ -4309,6 +4309,612 @@ void Player::dismount()
defaultOutfit.lookMount = 0;
}
+// wings
+
+uint16_t Player::getRandomWing() const
+{
+ std::vector wingsId;
+ for (const Wing& wing : g_game.wings.getWings()) {
+ if (hasWing(&wing)) {
+ wingsId.push_back(wing.id);
+ }
+ }
+
+ return wingsId[uniform_random(0, wingsId.size() - 1)];
+}
+
+uint16_t Player::getCurrentWing() const { return currentWing; }
+
+void Player::setCurrentWing(uint16_t wingId) { currentWing = wingId; }
+
+bool Player::toggleWing(bool wing)
+{
+ if ((OTSYS_TIME() - lastToggleWing) < 3000 && !wasWinged) {
+ sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED);
+ return false;
+ }
+
+ if (wing) {
+ if (isWinged()) {
+ 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 currentWingId = getCurrentWing();
+ if (currentWingId == 0) {
+ sendOutfitWindow();
+ return false;
+ }
+
+ Wing* currentWing = g_game.wings.getWingByID(currentWingId);
+ if (!currentWing) {
+ return false;
+ }
+
+ if (!hasWing(currentWing)) {
+ setCurrentWing(0);
+ sendOutfitWindow();
+ return false;
+ }
+
+ if (currentWing->premium && !isPremium()) {
+ sendCancelMessage(RETURNVALUE_YOUNEEDPREMIUMACCOUNT);
+ return false;
+ }
+
+ if (hasCondition(CONDITION_OUTFIT)) {
+ sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
+ return false;
+ }
+
+ defaultOutfit.lookWing = currentWing->id;
+
+ }
+ else {
+ if (!isWinged()) {
+ return false;
+ }
+
+ diswing();
+ }
+
+ g_game.internalCreatureChangeOutfit(this, defaultOutfit);
+ lastToggleWing = OTSYS_TIME();
+ return true;
+}
+
+bool Player::tameWing(uint16_t wingId)
+{
+ if (!g_game.wings.getWingByID(wingId)) {
+ return false;
+ }
+
+ Wing* wing = g_game.wings.getWingByID(wingId);
+ if (hasWing(wing)) {
+ return false;
+ }
+
+ wings.insert(wingId);
+ return true;
+}
+
+bool Player::untameWing(uint16_t wingId)
+{
+ if (!g_game.wings.getWingByID(wingId)) {
+ return false;
+ }
+
+ Wing* wing = g_game.wings.getWingByID(wingId);
+ if (!hasWing(wing)) {
+ return false;
+ }
+
+ wings.erase(wingId);
+
+ if (getCurrentWing() == wingId) {
+ if (isWinged()) {
+ diswing();
+ g_game.internalCreatureChangeOutfit(this, defaultOutfit);
+ }
+
+ setCurrentWing(0);
+ }
+
+ return true;
+}
+
+bool Player::hasWing(const Wing* wing) const
+{
+ if (isAccessPlayer()) {
+ return true;
+ }
+
+ if (wing->premium && !isPremium()) {
+ return false;
+ }
+
+ return wings.find(wing->id) != wings.end();
+}
+
+bool Player::hasWings() const
+{
+ for (const Wing& wing : g_game.wings.getWings()) {
+ if (hasWing(&wing)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void Player::diswing() { defaultOutfit.lookWing = 0; }
+// auras
+
+uint16_t Player::getRandomAura() const
+{
+ std::vector aurasId;
+ for (const Aura& aura : g_game.auras.getAuras()) {
+ if (hasAura(&aura)) {
+ aurasId.push_back(aura.id);
+ }
+ }
+
+ return aurasId[uniform_random(0, aurasId.size() - 1)];
+}
+
+uint16_t Player::getCurrentAura() const { return currentAura; }
+
+void Player::setCurrentAura(uint16_t auraId) { currentAura = auraId; }
+
+bool Player::toggleAura(bool aura)
+{
+ if ((OTSYS_TIME() - lastToggleAura) < 3000 && !wasAuraed) {
+ sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED);
+ return false;
+ }
+
+ if (aura) {
+ if (isAuraed()) {
+ 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 currentAuraId = getCurrentAura();
+ if (currentAuraId == 0) {
+ sendOutfitWindow();
+ return false;
+ }
+
+ Aura* currentAura = g_game.auras.getAuraByID(currentAuraId);
+ if (!currentAura) {
+ return false;
+ }
+
+ if (!hasAura(currentAura)) {
+ setCurrentAura(0);
+ sendOutfitWindow();
+ return false;
+ }
+
+ if (currentAura->premium && !isPremium()) {
+ sendCancelMessage(RETURNVALUE_YOUNEEDPREMIUMACCOUNT);
+ return false;
+ }
+
+ if (hasCondition(CONDITION_OUTFIT)) {
+ sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
+ return false;
+ }
+
+ defaultOutfit.lookAura = currentAura->id;
+
+ }
+ else {
+ if (!isAuraed()) {
+ return false;
+ }
+
+ disaura();
+ }
+
+ g_game.internalCreatureChangeOutfit(this, defaultOutfit);
+ lastToggleAura = OTSYS_TIME();
+ return true;
+}
+
+bool Player::tameAura(uint16_t auraId)
+{
+ if (!g_game.auras.getAuraByID(auraId)) {
+ return false;
+ }
+
+ Aura* aura = g_game.auras.getAuraByID(auraId);
+ if (hasAura(aura)) {
+ return false;
+ }
+
+ auras.insert(auraId);
+ return true;
+}
+
+bool Player::untameAura(uint16_t auraId)
+{
+ if (!g_game.auras.getAuraByID(auraId)) {
+ return false;
+ }
+
+ Aura* aura = g_game.auras.getAuraByID(auraId);
+ if (!hasAura(aura)) {
+ return false;
+ }
+
+ auras.erase(auraId);
+
+ if (getCurrentAura() == auraId) {
+ if (isAuraed()) {
+ disaura();
+ g_game.internalCreatureChangeOutfit(this, defaultOutfit);
+ }
+
+ setCurrentAura(0);
+ }
+
+ return true;
+}
+
+bool Player::hasAura(const Aura* aura) const
+{
+ if (isAccessPlayer()) {
+ return true;
+ }
+
+ if (aura->premium && !isPremium()) {
+ return false;
+ }
+
+ return auras.find(aura->id) != auras.end();
+}
+
+bool Player::hasAuras() const
+{
+ for (const Aura& aura : g_game.auras.getAuras()) {
+ if (hasAura(&aura)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void Player::disaura() { defaultOutfit.lookAura = 0; }
+// effects
+
+uint16_t Player::getRandomEffect() const
+{
+ std::vector effectsId;
+ for (const Effect& effect : g_game.effects.getEffects()) {
+ if (hasEffect(&effect)) {
+ effectsId.push_back(effect.id);
+ }
+ }
+
+ return effectsId[uniform_random(0, effectsId.size() - 1)];
+}
+
+uint16_t Player::getCurrentEffect() const { return currentEffect; }
+
+void Player::setCurrentEffect(uint16_t effectId) { currentEffect = effectId; }
+
+bool Player::toggleEffect(bool effect)
+{
+ if ((OTSYS_TIME() - lastToggleEffect) < 3000 && !wasEffected) {
+ sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED);
+ return false;
+ }
+
+ if (effect) {
+ if (isEffected()) {
+ 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 currentEffectId = getCurrentEffect();
+ if (currentEffectId == 0) {
+ sendOutfitWindow();
+ return false;
+ }
+
+ Effect* currentEffect = g_game.effects.getEffectByID(currentEffectId);
+ if (!currentEffect) {
+ return false;
+ }
+
+ if (!hasEffect(currentEffect)) {
+ setCurrentEffect(0);
+ sendOutfitWindow();
+ return false;
+ }
+
+ if (currentEffect->premium && !isPremium()) {
+ sendCancelMessage(RETURNVALUE_YOUNEEDPREMIUMACCOUNT);
+ return false;
+ }
+
+ if (hasCondition(CONDITION_OUTFIT)) {
+ sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
+ return false;
+ }
+
+ defaultOutfit.lookEffect = currentEffect->id;
+
+ }
+ else {
+ if (!isEffected()) {
+ return false;
+ }
+
+ diseffect();
+ }
+
+ g_game.internalCreatureChangeOutfit(this, defaultOutfit);
+ lastToggleEffect = OTSYS_TIME();
+ return true;
+}
+
+bool Player::tameEffect(uint16_t effectId)
+{
+ if (!g_game.effects.getEffectByID(effectId)) {
+ return false;
+ }
+
+ Effect* effect = g_game.effects.getEffectByID(effectId);
+ if (hasEffect(effect)) {
+ return false;
+ }
+
+ effects.insert(effectId);
+ return true;
+}
+
+bool Player::untameEffect(uint16_t effectId)
+{
+ if (!g_game.effects.getEffectByID(effectId)) {
+ return false;
+ }
+
+ Effect* effect = g_game.effects.getEffectByID(effectId);
+ if (!hasEffect(effect)) {
+ return false;
+ }
+
+ effects.erase(effectId);
+
+ if (getCurrentEffect() == effectId) {
+ if (isEffected()) {
+ diseffect();
+ g_game.internalCreatureChangeOutfit(this, defaultOutfit);
+ }
+
+ setCurrentEffect(0);
+ }
+
+ return true;
+}
+
+bool Player::hasEffect(const Effect* effect) const
+{
+ if (isAccessPlayer()) {
+ return true;
+ }
+
+ if (effect->premium && !isPremium()) {
+ return false;
+ }
+
+ return effects.find(effect->id) != effects.end();
+}
+
+bool Player::hasEffects() const
+{
+ for (const Effect& effect : g_game.effects.getEffects()) {
+ if (hasEffect(&effect)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+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)
{
diff --git a/src/player.h b/src/player.h
index 0de1ada..f5dbccb 100644
--- a/src/player.h
+++ b/src/player.h
@@ -146,7 +146,55 @@ class Player final : public Creature, public Cylinder
bool untameMount(uint8_t mountId);
bool hasMount(const Mount* mount) const;
void dismount();
-
+ // -- @ wings
+ uint16_t getRandomWing() const;
+ uint16_t getCurrentWing() const;
+ void setCurrentWing(uint16_t wingId);
+ bool isWinged() const { return defaultOutfit.lookWing != 0; }
+ bool toggleWing(bool wing);
+ bool tameWing(uint16_t wingId);
+ bool untameWing(uint16_t wingId);
+ bool hasWing(const Wing* wing) const;
+ bool hasWings() const;
+ void diswing();
+ // -- @
+ // -- @ Auras
+ uint16_t getRandomAura() const;
+ uint16_t getCurrentAura() const;
+ void setCurrentAura(uint16_t auraId);
+ bool isAuraed() const { return defaultOutfit.lookAura != 0; }
+ bool toggleAura(bool aura);
+ bool tameAura(uint16_t auraId);
+ bool untameAura(uint16_t auraId);
+ bool hasAura(const Aura* aura) const;
+ bool hasAuras() const;
+ void disaura();
+ // -- @
+ // -- @ Effect
+ uint16_t getRandomEffect() const;
+ uint16_t getCurrentEffect() const;
+ void setCurrentEffect(uint16_t effectId);
+ bool isEffected() const { return defaultOutfit.lookEffect != 0; }
+ bool toggleEffect(bool effect);
+ bool tameEffect(uint16_t effectId);
+ bool untameEffect(uint16_t effectId);
+ 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(const std::string& message) {
if (client) {
client->sendFYIBox(message);
@@ -860,7 +908,32 @@ class Player final : public Creature, public Cylinder
client->sendItems();
}
}
+ void sendAttachedEffect(const Creature* creature, uint16_t effectId)
+ {
+ if (client) {
+ client->sendAttachedEffect(creature, effectId);
+ }
+ }
+
+ void sendDetachEffect(const Creature* creature, uint16_t effectId)
+ {
+ if (client) {
+ client->sendDetachEffect(creature, effectId);
+ }
+ }
+ void sendShader(const Creature* creature, const std::string& shaderName)
+ {
+ if (client) {
+ client->sendShader(creature, shaderName);
+ }
+ }
+ void sendMapShader(const std::string& shaderName)
+ {
+ if (client) {
+ client->sendMapShader(shaderName);
+ }
+ }
//event methods
void onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem,
const ItemType& oldType, const Item* newItem, const ItemType& newType) override;
@@ -1146,6 +1219,9 @@ class Player final : public Creature, public Cylinder
bool hasLearnedInstantSpell(const std::string& spellName) const;
void updateRegeneration();
+ std::string getMapShader() const { return mapShader; }
+ void setMapShader(const std::string& shaderName) { mapShader = shaderName; }
+ bool isMehah() const { return client ? client->isMehah : false; }
private:
std::forward_list getMuteConditions() const;
@@ -1203,6 +1279,10 @@ class Player final : public Creature, public Cylinder
std::map storageMap;
std::vector outfits;
+ std::unordered_set wings;
+ std::unordered_set auras;
+ std::unordered_set effects;
+ std::unordered_set shaders;
GuildWarVector guildWarVector;
std::list shopItemList;
@@ -1214,6 +1294,7 @@ class Player final : public Creature, public Cylinder
std::string name;
std::string guildNick;
+ std::string mapShader;
Skill skills[SKILL_LAST + 1];
LightInfo itemsLight;
@@ -1236,6 +1317,10 @@ class Player final : public Creature, public Cylinder
int64_t lastPing;
int64_t lastPong;
int64_t nextAction = 0;
+ int64_t lastToggleWing = 0;
+ int64_t lastToggleEffect = 0;
+ int64_t lastToggleAura = 0;
+ int64_t lastToggleShader = 0;
BedItem* bedItem = nullptr;
Guild* guild = nullptr;
@@ -1310,8 +1395,17 @@ class Player final : public Creature, public Cylinder
bool pzLocked = false;
bool isConnecting = false;
bool addAttackSkillPoint = false;
+ bool wasWinged = false;
+ bool wasAuraed = false;
+ bool wasEffected = false;
+ bool wasShadered = false;
+ bool randomizeWing = false;
+ bool randomizeAura = false;
+ bool randomizeEffect = false;
+ bool randomizeShader = false;
bool inventoryAbilities[CONST_SLOT_LAST + 1] = {};
+
static uint32_t playerAutoID;
void updateItemsLight(bool internal = false);
diff --git a/src/protocolgame.cpp b/src/protocolgame.cpp
index a47737c..5b56ff5 100644
--- a/src/protocolgame.cpp
+++ b/src/protocolgame.cpp
@@ -839,6 +839,17 @@ void ProtocolGame::parseSetOutfit(NetworkMessage& msg)
newOutfit.lookFeet = msg.getByte();
newOutfit.lookAddons = msg.getByte();
newOutfit.lookMount = msg.get();
+ newOutfit.lookWing = isMehah ? msg.get() : 0;
+ newOutfit.lookAura = isMehah ? msg.get() : 0;
+ newOutfit.lookEffect = isMehah ? msg.get() : 0;
+
+ std::string_view shaderName = isMehah ? msg.getString() : "";
+ Shader* shader = nullptr;
+ if (!shaderName.empty()) {
+ shader = g_game.shaders.getShaderByName(shaderName);
+ newOutfit.lookShader = shader ? shader->id : 0;
+ }
+
addGameTask(&Game::playerChangeOutfit, player->getID(), newOutfit);
}
@@ -2804,7 +2815,24 @@ void ProtocolGame::sendOutfitWindow()
if (currentMount) {
currentOutfit.lookMount = currentMount->clientId;
}
-
+ Wing* currentWing = g_game.wings.getWingByID(player->getCurrentWing());
+ if (currentWing) {
+ currentOutfit.lookWing = currentWing->id;
+ }
+ // @ -- auras
+ Aura* currentAura = g_game.auras.getAuraByID(player->getCurrentAura());
+ if (currentAura) {
+ currentOutfit.lookAura = currentAura->id;
+ }
+ // @ -- effects
+ Effect* currentEffect = g_game.effects.getEffectByID(player->getCurrentEffect());
+ if (currentEffect) {
+ currentOutfit.lookEffect = currentEffect->id;
+ }
+ Shader* currentShader = g_game.shaders.getShaderByID(player->getCurrentShader());
+ if (currentShader) {
+ currentOutfit.lookShader = currentShader->id;
+ }
AddOutfit(msg, currentOutfit);
std::vector protocolOutfits;
@@ -2845,7 +2873,62 @@ void ProtocolGame::sendOutfitWindow()
msg.add(mount->clientId);
msg.addString(mount->name);
}
+ if (isMehah) {
+ // wings
+ std::vector wings;
+ for (const Wing& wing : g_game.wings.getWings()) {
+ if (player->hasWing(&wing)) {
+ wings.push_back(&wing);
+ }
+ }
+
+ msg.addByte(wings.size());
+ for (const Wing* wing : wings) {
+ msg.add(wing->id);
+ msg.addString(wing->name);
+ }
+
+ // auras
+ std::vector auras;
+ for (const Aura& aura : g_game.auras.getAuras()) {
+ if (player->hasAura(&aura)) {
+ auras.push_back(&aura);
+ }
+ }
+
+ msg.addByte(auras.size());
+ for (const Aura* aura : auras) {
+ msg.add(aura->id);
+ msg.addString(aura->name);
+ }
+ // effects
+ std::vector effects;
+ for (const Effect& effect : g_game.effects.getEffects()) {
+ if (player->hasEffect(&effect)) {
+ effects.push_back(&effect);
+ }
+ }
+
+ msg.addByte(effects.size());
+ for (const Effect* effect : effects) {
+ 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);
}
@@ -3005,6 +3088,11 @@ void ProtocolGame::AddCreature(NetworkMessage& msg, const Creature* creature, bo
}
msg.addByte(player->canWalkthroughEx(creature) ? 0x00 : 0x01);
+ if (isMehah) {
+ msg.addString(creature->getShader());
+ msg.addByte(static_cast(creature->getAttachedEffectList().size()));
+ for (const uint16_t id : creature->getAttachedEffectList()) msg.add(id);
+ }
}
void ProtocolGame::AddPlayerStats(NetworkMessage& msg)
@@ -3081,6 +3169,14 @@ void ProtocolGame::AddOutfit(NetworkMessage& msg, const Outfit_t& outfit)
}
msg.add(outfit.lookMount);
+ if (isMehah) {
+ 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 : "");
+ }
}
void ProtocolGame::AddWorldLight(NetworkMessage& msg, LightInfo lightInfo)
@@ -3234,3 +3330,48 @@ void ProtocolGame::parseExtendedOpcode(NetworkMessage& msg)
// process additional opcodes via lua script event
addGameTask(&Game::parsePlayerExtendedOpcode, player->getID(), opcode, buffer);
}
+
+void ProtocolGame::sendAttachedEffect(const Creature* creature, uint16_t effectId)
+{
+ if (!isMehah) return;
+ NetworkMessage playermsg;
+ playermsg.reset();
+ playermsg.addByte(0x34);
+ playermsg.add(creature->getID());
+ playermsg.add(effectId);
+ writeToOutputBuffer(playermsg);
+}
+
+void ProtocolGame::sendDetachEffect(const Creature* creature, uint16_t effectId)
+{
+ if (!isMehah) return;
+
+ NetworkMessage playermsg;
+ playermsg.reset();
+ playermsg.addByte(0x35);
+ playermsg.add(creature->getID());
+ playermsg.add(effectId);
+ writeToOutputBuffer(playermsg);
+}
+void ProtocolGame::sendShader(const Creature* creature, const std::string& shaderName)
+{
+ if (!isMehah) return;
+
+ NetworkMessage playermsg;
+ playermsg.reset();
+ playermsg.addByte(0x36);
+ playermsg.add(creature->getID());
+ playermsg.addString(shaderName);
+ writeToOutputBuffer(playermsg);
+}
+
+void ProtocolGame::sendMapShader(const std::string& shaderName)
+{
+ if (!isMehah) return;
+
+ NetworkMessage playermsg;
+ playermsg.reset();
+ playermsg.addByte(0x37);
+ playermsg.addString(shaderName);
+ writeToOutputBuffer(playermsg);
+}
diff --git a/src/protocolgame.h b/src/protocolgame.h
index fc2d9f9..c282e42 100644
--- a/src/protocolgame.h
+++ b/src/protocolgame.h
@@ -288,6 +288,10 @@ class ProtocolGame final : public Protocol
//otclient
void parseExtendedOpcode(NetworkMessage& msg);
+ void sendAttachedEffect(const Creature* creature, uint16_t effectId);
+ void sendDetachEffect(const Creature* creature, uint16_t effectId);
+ void sendShader(const Creature* creature, const std::string& shaderName);
+ void sendMapShader(const std::string& shaderName);
friend class Player;
diff --git a/src/shaders.cpp b/src/shaders.cpp
new file mode 100644
index 0000000..8abf639
--- /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 (strcasecmp(name.data(), it.name.c_str()) == 0) {
+ 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/src/spawn.cpp b/src/spawn.cpp
index c74203a..0f0cfbc 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -332,6 +332,20 @@ bool Spawn::spawnMonster(uint32_t spawnId, MonsterType* mType, const Position& p
monster->setMasterPos(pos);
monster->incrementReferenceCounter();
+ if (monster->rayosEffect() != 0) {
+ monster->attachEffectById(monster->rayosEffect());
+ }
+ if (monster->wignsEffect() != 0) {
+ monster->attachEffectById(monster->wignsEffect());
+ }
+ if (monster->auraEffect() != 0) {
+ monster->attachEffectById(monster->auraEffect());
+ }
+ if (monster->shaderEffect() != "") {
+ monster->setShader(monster->shaderEffect());
+ g_game.updateCreatureShader(monster);
+ }
+
spawnedMap.insert({spawnId, monster});
spawnMap[spawnId].lastSpawn = OTSYS_TIME();
return true;
diff --git a/src/wings.cpp b/src/wings.cpp
new file mode 100644
index 0000000..0b074eb
--- /dev/null
+++ b/src/wings.cpp
@@ -0,0 +1,65 @@
+// Copyright 2023 The Forgotten Server Authors. All rights reserved.
+// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file.
+
+#include "otpch.h"
+
+#include "wings.h"
+
+#include "pugicast.h"
+#include "tools.h"
+
+bool Wings::reload()
+{
+ wings.clear();
+ return loadFromXml();
+}
+
+bool Wings::loadFromXml()
+{
+ pugi::xml_document doc;
+ pugi::xml_parse_result result = doc.load_file("data/XML/wings.xml");
+ if (!result) {
+ printXMLError("Error - Wings::loadFromXml", "data/XML/wings.xml", result);
+ return false;
+ }
+
+ for (auto wingNode : doc.child("wings").children()) {
+ uint16_t nodeId = pugi::cast(wingNode.attribute("id").value());
+ if (nodeId == 0 || nodeId > std::numeric_limits::max()) {
+ std::cout << "[Notice - Wings::loadFromXml] Wing id \"" << nodeId << "\" is not within 1 and 65535 range"
+ << std::endl;
+ continue;
+ }
+
+ if (getWingByID(nodeId)) {
+ std::cout << "[Notice - Wings::loadFromXml] Duplicate wing with id: " << nodeId << std::endl;
+ continue;
+ }
+
+ wings.emplace_back(
+ static_cast(nodeId),
+ wingNode.attribute("name").as_string(), pugi::cast(wingNode.attribute("speed").value()),
+ wingNode.attribute("premium").as_bool());
+ }
+ wings.shrink_to_fit();
+ return true;
+}
+
+Wing* Wings::getWingByID(uint16_t id)
+{
+ auto it = std::find_if(wings.begin(), wings.end(), [id](const Wing& wing) { return wing.id == id; });
+
+ return it != wings.end() ? &*it : nullptr;
+}
+
+Wing* Wings::getWingByName(std::string_view name)
+{
+ for (auto& it : wings) {
+ if (strcasecmp(name.data(), it.name.c_str()) == 0) {
+ return ⁢
+ }
+ }
+
+ return nullptr;
+}
+
diff --git a/src/wings.h b/src/wings.h
new file mode 100644
index 0000000..12f15bf
--- /dev/null
+++ b/src/wings.h
@@ -0,0 +1,35 @@
+// Copyright 2023 The Forgotten Server Authors. All rights reserved.
+// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file.
+
+#ifndef FS_WINGS_H
+#define FS_WINGS_H
+
+struct Wing
+{
+ Wing(uint16_t id, std::string_view name, int32_t speed, bool premium) :
+ name{name}, speed{speed}, id{id}, premium{premium}
+ {}
+
+ std::string name;
+ int32_t speed;
+
+ uint16_t id;
+ bool premium;
+};
+
+class Wings
+{
+public:
+ bool reload();
+ bool loadFromXml();
+ Wing* getWingByID(uint16_t id);
+ Wing* getWingByName(std::string_view name);
+
+
+ const std::vector& getWings() const { return wings; }
+
+private:
+ std::vector wings;
+};
+
+#endif // FS_WINGS_H
diff --git a/vc17/theforgottenserver.vcxproj b/vc17/theforgottenserver.vcxproj
index 7eb3284..d8e0cef 100644
--- a/vc17/theforgottenserver.vcxproj
+++ b/vc17/theforgottenserver.vcxproj
@@ -200,6 +200,10 @@
+
+
+
+
@@ -290,6 +294,10 @@
+
+
+
+