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 @@ + + + +