From 8dfe3b790a84766749e53c5eb576438e6b3b413a Mon Sep 17 00:00:00 2001 From: kokekanon Date: Sat, 13 Apr 2024 15:24:01 -0400 Subject: [PATCH 1/2] feat: [Attach Effect] Wings --- data/XML/wings.xml | 6 ++ data/talkactions/scripts/reload.lua | 5 +- schema.sql | 13 ++- src/const.h | 1 + src/creature.h | 1 + src/enums.h | 1 + src/game.cpp | 35 +++++-- src/game.h | 2 + src/iologindata.cpp | 39 +++++++- src/luagame.cpp | 18 +++- src/luaplayer.cpp | 104 +++++++++++++++++++- src/luascript.cpp | 11 +++ src/luascript.h | 2 + src/player.cpp | 147 ++++++++++++++++++++++++++++ src/player.h | 19 ++++ src/protocolgame.cpp | 23 ++++- src/wings.cpp | 65 ++++++++++++ src/wings.h | 35 +++++++ vc17/theforgottenserver.vcxproj | 2 + 19 files changed, 505 insertions(+), 24 deletions(-) create mode 100644 data/XML/wings.xml create mode 100644 src/wings.cpp create mode 100644 src/wings.h diff --git a/data/XML/wings.xml b/data/XML/wings.xml new file mode 100644 index 0000000..65d67a8 --- /dev/null +++ b/data/XML/wings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/data/talkactions/scripts/reload.lua b/data/talkactions/scripts/reload.lua index 818f498..19e1714 100644 --- a/data/talkactions/scripts/reload.lua +++ b/data/talkactions/scripts/reload.lua @@ -28,7 +28,10 @@ local reloadTypes = { ["mount"] = RELOAD_TYPE_MOUNTS, ["mounts"] = RELOAD_TYPE_MOUNTS, - + + ["wing"] = RELOAD_TYPE_WINGS, + ["wings"] = RELOAD_TYPE_WINGS, + ["move"] = RELOAD_TYPE_MOVEMENTS, ["movement"] = RELOAD_TYPE_MOVEMENTS, ["movements"] = RELOAD_TYPE_MOVEMENTS, diff --git a/schema.sql b/schema.sql index 405a132..d6197f6 100644 --- a/schema.sql +++ b/schema.sql @@ -30,9 +30,9 @@ CREATE TABLE IF NOT EXISTS `players` ( `lookaddons` int NOT NULL DEFAULT '0', `currentmount` smallint UNSIGNED NOT NULL DEFAULT '0', `randomizemount` tinyint NOT NULL DEFAULT '0', + `currentwing` smallint UNSIGNED NOT NULL DEFAULT '0', + `randomizewing` tinyint NOT NULL DEFAULT '0', `direction` tinyint unsigned NOT NULL DEFAULT '2', - `currentmount` smallint unsigned NOT NULL DEFAULT '0', - `randomizemount` tinyint NOT NULL DEFAULT '0', `maglevel` int NOT NULL DEFAULT '0', `mana` int NOT NULL DEFAULT '0', `manamax` int NOT NULL DEFAULT '0', @@ -360,6 +360,15 @@ CREATE TABLE `player_mounts` ( `mount_id` smallint UNSIGNED NOT NULL DEFAULT '0' ) ENGINE=InnoDB DEFAULT CHARSET=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 `server_config` ( `config` varchar(50) NOT NULL, `value` varchar(256) NOT NULL DEFAULT '', diff --git a/src/const.h b/src/const.h index f3fe12c..1c1e247 100644 --- a/src/const.h +++ b/src/const.h @@ -477,6 +477,7 @@ enum ReloadTypes_t : uint8_t RELOAD_TYPE_ITEMS, RELOAD_TYPE_MONSTERS, RELOAD_TYPE_MOUNTS, + RELOAD_TYPE_WINGS, RELOAD_TYPE_MOVEMENTS, RELOAD_TYPE_NPCS, RELOAD_TYPE_QUESTS, diff --git a/src/creature.h b/src/creature.h index feabfc2..b2779d6 100644 --- a/src/creature.h +++ b/src/creature.h @@ -413,6 +413,7 @@ class Creature : virtual public Thing Outfit_t currentOutfit; Outfit_t defaultOutfit; uint16_t currentMount; + uint16_t currentWing; Position lastPosition; LightInfo internalLight; diff --git a/src/enums.h b/src/enums.h index 072c90b..fd7a0b0 100644 --- a/src/enums.h +++ b/src/enums.h @@ -503,6 +503,7 @@ struct Outfit_t uint16_t lookType = 0; uint16_t lookTypeEx = 0; uint16_t lookMount = 0; + uint16_t lookWing = 0; uint8_t lookHead = 0; uint8_t lookBody = 0; uint8_t lookLegs = 0; diff --git a/src/game.cpp b/src/game.cpp index 4c19147..f9d8323 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -76,6 +76,7 @@ void Game::setGameState(GameState_t newState) map.spawns.startup(); mounts.loadFromXml(); + wings.loadFromXml(); raids.loadFromXml(); raids.startup(); @@ -2014,7 +2015,6 @@ void Game::playerSetTyping(uint32_t playerId, bool typing) } } - void Game::playerReceivePing(uint32_t playerId) { Player* player = getPlayerByID(playerId); @@ -3426,6 +3426,7 @@ void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit, bool randomize const Outfit* playerOutfit = Outfits::getInstance().getOutfitByLookType(outfit.lookType); if (!playerOutfit) { outfit.lookMount = 0; + // outfit.lookWing = 0; } if (outfit.lookMount != 0) { @@ -3456,6 +3457,28 @@ void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit, bool randomize player->wasMounted = false; } + /// 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; + } + if (player->canWear(outfit.lookType, outfit.lookAddons)) { player->defaultOutfit = outfit; @@ -5363,10 +5386,6 @@ bool Game::reload(ReloadTypes_t reloadType) return true; } - - - - void Game::sendAttachedEffect(const Creature* creature, uint16_t effectId) { SpectatorVec spectators; @@ -5412,7 +5431,6 @@ void Game::updateCreatureShader(const Creature* creature) } } - void Game::refreshItem(const Item* item) { if (!item || !item->getParent()) return; @@ -5428,7 +5446,7 @@ void Game::refreshItem(const Item* item) return; } -if (const auto container = parent->getContainer()) { + if (const auto container = parent->getContainer()) { int32_t index = container->getThingIndex(item); if (index > -1 && index <= std::numeric_limits::max()) { SpectatorVec spectators; @@ -5465,6 +5483,5 @@ void Game::sendPlayerToolsTips(uint32_t playerId, uint16_t itemID) } const ItemType& itemType = Item::items.getItemIdByClientId(itemID); - g_events->eventPlayeronToolsTips(player, itemType.id); - + g_events->eventPlayeronToolsTips(player, itemType.id); } diff --git a/src/game.h b/src/game.h index 1d70552..07f965e 100644 --- a/src/game.h +++ b/src/game.h @@ -11,6 +11,7 @@ #include "item.h" #include "map.h" #include "mounts.h" +#include "wings.h" #include "npc.h" #include "player.h" #include "position.h" @@ -486,6 +487,7 @@ class Game Map map; Raids raids; Mounts mounts; + Wings wings; std::forward_list toDecayItems; diff --git a/src/iologindata.cpp b/src/iologindata.cpp index bcddbf4..b3b8653 100644 --- a/src/iologindata.cpp +++ b/src/iologindata.cpp @@ -229,7 +229,7 @@ bool IOLoginData::loadPlayerById(Player* player, uint32_t id) return loadPlayer( player, db.storeQuery(fmt::format( - "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `currentmount`, `randomizemount`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `direction` FROM `players` WHERE `id` = {:d}", + "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `currentmount`, `randomizemount`, `currentwing`, `randomizewing`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `direction` FROM `players` WHERE `id` = {:d}", id))); } @@ -239,7 +239,7 @@ bool IOLoginData::loadPlayerByName(Player* player, std::string_view name) return loadPlayer( player, db.storeQuery(fmt::format( - "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `currentmount`, `randomizemount`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `direction` FROM `players` WHERE `name` = {:s}", + "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `currentmount`, `randomizemount`, `currentwing`, `randomizewing`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `direction` FROM `players` WHERE `name` = {:s}", db.escapeString(name)))); } @@ -361,9 +361,10 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) player->defaultOutfit.lookAddons = result->getNumber("lookaddons"); player->currentOutfit = player->defaultOutfit; player->currentMount = result->getNumber("currentmount"); + player->currentWing = result->getNumber("currentwing"); player->direction = static_cast(result->getNumber("direction")); player->randomizeMount = result->getNumber("randomizemount") != 0; - + player->randomizeWing = result->getNumber("randomizewing") != 0; if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { const time_t skullSeconds = result->getNumber("skulltime") - time(nullptr); if (skullSeconds > 0) { @@ -598,6 +599,19 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) } 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()); + } + + uint16_t currentWing = player->getCurrentWing(); + if (currentWing > 0) { + player->attachEffectById(currentWing); + } + player->updateBaseSpeed(); player->updateInventoryWeight(); player->updateItemsLight(true); @@ -705,6 +719,8 @@ bool IOLoginData::savePlayer(Player* player) query << "`lookaddons` = " << static_cast(player->defaultOutfit.lookAddons) << ','; query << "`currentmount` = " << static_cast(player->currentMount) << ','; query << "`randomizemount` = " << player->randomizeMount << ","; + query << "`currentwing` = " << static_cast(player->currentWing) << ','; + query << "`randomizewing` = " << player->randomizeWing << ","; query << "`maglevel` = " << player->magLevel << ','; query << "`mana` = " << player->mana << ','; query << "`manamax` = " << player->manaMax << ','; @@ -923,6 +939,23 @@ bool IOLoginData::savePlayer(Player* player) 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; + } + // End the transaction return transaction.commit(); } diff --git a/src/luagame.cpp b/src/luagame.cpp index e6b49a5..41bf420 100644 --- a/src/luagame.cpp +++ b/src/luagame.cpp @@ -254,6 +254,21 @@ int luaGameGetMounts(lua_State* L) return 1; } +int 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; +} + int luaGameGetVocations(lua_State* L) { // Game.getVocations() @@ -436,7 +451,7 @@ int luaGameCreateMonster(lua_State* L) monster->setShader(monster->shaderEffect()); g_game.updateCreatureShader(monster); } - + } else { delete monster; lua_pushnil(L); @@ -690,6 +705,7 @@ void LuaScriptInterface::registerGame() registerMethod("Game", "getHouses", luaGameGetHouses); registerMethod("Game", "getOutfits", luaGameGetOutfits); registerMethod("Game", "getMounts", luaGameGetMounts); + registerMethod("Game", "getWings", luaGameGetWings); registerMethod("Game", "getGameState", luaGameGetGameState); registerMethod("Game", "setGameState", luaGameSetGameState); diff --git a/src/luaplayer.cpp b/src/luaplayer.cpp index 265cac1..dd6f060 100644 --- a/src/luaplayer.cpp +++ b/src/luaplayer.cpp @@ -11,6 +11,7 @@ #include "player.h" #include "spells.h" #include "vocation.h" +#include "wings.h" extern Chat* g_chat; extern Game g_game; @@ -1593,6 +1594,97 @@ int luaPlayerToggleMount(lua_State* L) return 1; } +// @ wings + +int 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 (isInteger(L, 2)) { + wingId = getInteger(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 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 (isInteger(L, 2)) { + wingId = getInteger(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 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 (isInteger(L, 2)) { + wing = g_game.wings.getWingByID(getInteger(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 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; +} + +// @ int luaPlayerGetPremiumEndsAt(lua_State* L) { // player:getPremiumEndsAt() @@ -2168,8 +2260,6 @@ int luaPlayerHasDebugAssertSent(lua_State* L) return 1; } - - int luaPlayerGetMapShader(lua_State* L) { // player:getMapShader() @@ -2203,8 +2293,6 @@ int luaPlayerSetMapShader(lua_State* L) return 1; } - - // OfflinePlayer int luaOfflinePlayerCreate(lua_State* L) { @@ -2370,6 +2458,13 @@ void LuaScriptInterface::registerPlayer() registerMethod("Player", "hasMount", luaPlayerHasMount); registerMethod("Player", "toggleMount", luaPlayerToggleMount); + // @ wings + registerMethod("Player", "addWing", luaPlayerAddWing); + registerMethod("Player", "removeWing", luaPlayerRemoveWing); + registerMethod("Player", "hasWing", luaPlayerHasWing); + registerMethod("Player", "toggleWing", luaPlayerToggleWing); + // @ + registerMethod("Player", "getPremiumEndsAt", luaPlayerGetPremiumEndsAt); registerMethod("Player", "setPremiumEndsAt", luaPlayerSetPremiumEndsAt); @@ -2420,7 +2515,6 @@ void LuaScriptInterface::registerPlayer() registerMethod("Player", "getMapShader", luaPlayerGetMapShader); registerMethod("Player", "setMapShader", luaPlayerSetMapShader); - // OfflinePlayer registerClass("OfflinePlayer", "Player", luaOfflinePlayerCreate); registerMetaMethod("OfflinePlayer", "__gc", luaOfflinePlayerRemove); diff --git a/src/luascript.cpp b/src/luascript.cpp index 5623cec..680fea9 100644 --- a/src/luascript.cpp +++ b/src/luascript.cpp @@ -1004,6 +1004,7 @@ void Lua::pushOutfit(lua_State* L, const Outfit_t& outfit) setField(L, "lookType", outfit.lookType); setField(L, "lookTypeEx", outfit.lookTypeEx); setField(L, "lookMount", outfit.lookMount); + setField(L, "lookWing", outfit.lookWing); setField(L, "lookHead", outfit.lookHead); setField(L, "lookBody", outfit.lookBody); setField(L, "lookLegs", outfit.lookLegs); @@ -1032,6 +1033,15 @@ void Lua::pushMount(lua_State* L, const Mount* mount) setField(L, "premium", mount->premium); } +void Lua::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 Lua::pushLoot(lua_State* L, const std::vector& lootList) { lua_createtable(L, lootList.size(), 0); @@ -1948,6 +1958,7 @@ void LuaScriptInterface::registerFunctions() registerEnum(RELOAD_TYPE_ITEMS); registerEnum(RELOAD_TYPE_MONSTERS); registerEnum(RELOAD_TYPE_MOUNTS); + registerEnum(RELOAD_TYPE_WINGS); registerEnum(RELOAD_TYPE_MOVEMENTS); registerEnum(RELOAD_TYPE_NPCS); registerEnum(RELOAD_TYPE_QUESTS); diff --git a/src/luascript.h b/src/luascript.h index dbe3548..1bcbb0b 100644 --- a/src/luascript.h +++ b/src/luascript.h @@ -115,6 +115,7 @@ struct Group; struct LootBlock; struct ModalWindow; struct Mount; +struct Wing; struct Outfit; struct Position; @@ -776,6 +777,7 @@ void pushPosition(lua_State* L, const Position& position, int32_t stackpos = 0); void pushOutfit(lua_State* L, const Outfit_t& outfit); void pushOutfit(lua_State* L, const Outfit* outfit); void pushMount(lua_State* L, const Mount* mount); +void pushWing(lua_State* L, const Wing* wing); void pushLoot(lua_State* L, const std::vector& lootList); void pushReflect(lua_State* L, const Reflect& reflect); diff --git a/src/player.cpp b/src/player.cpp index 1cb154a..ae4eab2 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -4161,6 +4161,153 @@ 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; } + /*bool Player::addOfflineTrainingTries(skills_t skill, uint64_t tries) { if (tries == 0 || skill == SKILL_LEVEL) { diff --git a/src/player.h b/src/player.h index 2cde76b..fc8399f 100644 --- a/src/player.h +++ b/src/player.h @@ -128,6 +128,17 @@ class Player final : public Creature, public Cylinder bool hasMounts() const; void dismount(); + 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(); + void sendFYIBox(std::string_view message) { if (client) { @@ -1072,6 +1083,8 @@ class Player final : public Creature, public Cylinder std::unordered_map outfits; std::unordered_set mounts; + std::unordered_set wings; + GuildWarVector guildWarVector; std::list shopItemList; @@ -1103,6 +1116,8 @@ class Player final : public Creature, public Cylinder int64_t skullTicks = 0; int64_t lastWalkthroughAttempt = 0; int64_t lastToggleMount = 0; + int64_t lastToggleWing = 0; + int64_t lastPing; int64_t lastPong; int64_t nextAction = 0; @@ -1172,10 +1187,14 @@ class Player final : public Creature, public Cylinder bool secureMode = false; bool ghostMode = false; bool wasMounted = false; + bool wasWinged = false; + bool pzLocked = false; bool isConnecting = false; bool addAttackSkillPoint = false; bool randomizeMount = false; + bool randomizeWing = false; + bool inventoryAbilities[CONST_SLOT_LAST + 1] = {}; void updateItemsLight(bool internal = false); diff --git a/src/protocolgame.cpp b/src/protocolgame.cpp index 54297ad..a289646 100644 --- a/src/protocolgame.cpp +++ b/src/protocolgame.cpp @@ -1070,6 +1070,7 @@ void ProtocolGame::parseSetOutfit(NetworkMessage& msg) newOutfit.lookFeet = msg.getByte(); newOutfit.lookAddons = msg.getByte(); newOutfit.lookMount = isOTC ? msg.get() : 0; + newOutfit.lookWing = isOTC ? msg.get() : 0; g_dispatcher.addTask([=, playerID = player->getID()]() { g_game.playerChangeOutfit(playerID, newOutfit); }); } @@ -2384,6 +2385,11 @@ void ProtocolGame::sendOutfitWindow() currentOutfit.lookMount = currentMount->clientId; } + Wing* currentWing = g_game.wings.getWingByID(player->getCurrentWing()); + if (currentWing) { + currentOutfit.lookWing = currentWing->id; + } + /*bool mounted; if (player->wasMounted) { mounted = currentOutfit.lookMount != 0; @@ -2432,6 +2438,19 @@ void ProtocolGame::sendOutfitWindow() msg.add(mount->clientId); msg.addString(mount->name); } + // 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); + } } writeToOutputBuffer(msg); @@ -2592,6 +2611,7 @@ void ProtocolGame::AddOutfit(NetworkMessage& msg, const Outfit_t& outfit) if (isOTC) { msg.add(outfit.lookMount); + msg.add(outfit.lookWing); } } @@ -2749,9 +2769,6 @@ void ProtocolGame::parseExtendedOpcode(NetworkMessage& msg) }); } - - - void ProtocolGame::sendAttachedEffect(const Creature* creature, uint16_t effectId) { const Player* player = creature->getPlayer(); diff --git a/src/wings.cpp b/src/wings.cpp new file mode 100644 index 0000000..59eb350 --- /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 (caseInsensitiveEqual(name, it.name)) { + 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 8b11a29..b36bef5 100644 --- a/vc17/theforgottenserver.vcxproj +++ b/vc17/theforgottenserver.vcxproj @@ -236,6 +236,7 @@ + @@ -323,6 +324,7 @@ + From 9afc0a25039e8d1b901b2819eb82afd1a9ebe3f3 Mon Sep 17 00:00:00 2001 From: kokekanon Date: Sat, 13 Apr 2024 20:13:24 -0400 Subject: [PATCH 2/2] feat: [Attach Effect] Auras // Effects --- data/XML/auras.xml | 4 + data/XML/effects.xml | 4 + data/XML/wings.xml | 3 +- data/talkactions/scripts/reload.lua | 8 +- schema.sql | 18 +- src/auras.cpp | 64 ++++++ src/auras.h | 35 ++++ src/const.h | 2 + src/creature.h | 2 + src/effects.cpp | 64 ++++++ src/effects.h | 35 ++++ src/enums.h | 2 + src/game.cpp | 51 ++++- src/game.h | 6 +- src/iologindata.cpp | 81 +++++++- src/luagame.cpp | 32 +++ src/luaplayer.cpp | 197 +++++++++++++++++++ src/luascript.cpp | 21 ++ src/luascript.h | 4 + src/player.cpp | 291 ++++++++++++++++++++++++++++ src/player.h | 36 +++- src/protocolgame.cpp | 46 ++++- vc17/theforgottenserver.vcxproj | 4 + 23 files changed, 997 insertions(+), 13 deletions(-) create mode 100644 data/XML/auras.xml create mode 100644 data/XML/effects.xml create mode 100644 src/auras.cpp create mode 100644 src/auras.h create mode 100644 src/effects.cpp create mode 100644 src/effects.h 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/wings.xml b/data/XML/wings.xml index 65d67a8..fd47520 100644 --- a/data/XML/wings.xml +++ b/data/XML/wings.xml @@ -1,6 +1,5 @@ - - + \ No newline at end of file diff --git a/data/talkactions/scripts/reload.lua b/data/talkactions/scripts/reload.lua index 19e1714..7c79e67 100644 --- a/data/talkactions/scripts/reload.lua +++ b/data/talkactions/scripts/reload.lua @@ -31,7 +31,13 @@ local reloadTypes = { ["wing"] = RELOAD_TYPE_WINGS, ["wings"] = RELOAD_TYPE_WINGS, - + + ["effect"] = RELOAD_TYPE_EFFECT, + ["effects"] = RELOAD_TYPE_EFFECT, + + ["aura"] = RELOAD_TYPE_AURA, + ["auras"] = RELOAD_TYPE_AURA, + ["move"] = RELOAD_TYPE_MOVEMENTS, ["movement"] = RELOAD_TYPE_MOVEMENTS, ["movements"] = RELOAD_TYPE_MOVEMENTS, diff --git a/schema.sql b/schema.sql index d6197f6..2036084 100644 --- a/schema.sql +++ b/schema.sql @@ -32,6 +32,10 @@ CREATE TABLE IF NOT EXISTS `players` ( `randomizemount` tinyint NOT NULL DEFAULT '0', `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', `direction` tinyint unsigned NOT NULL DEFAULT '2', `maglevel` int NOT NULL DEFAULT '0', `mana` int NOT NULL DEFAULT '0', @@ -360,7 +364,6 @@ CREATE TABLE `player_mounts` ( `mount_id` smallint UNSIGNED NOT NULL DEFAULT '0' ) ENGINE=InnoDB DEFAULT CHARSET=utf8; - CREATE TABLE IF NOT EXISTS `player_wings` ( `player_id` int NOT NULL DEFAULT '0', `wing_id` smallint unsigned NOT NULL DEFAULT '0', @@ -368,6 +371,19 @@ CREATE TABLE IF NOT EXISTS `player_wings` ( 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 `server_config` ( `config` varchar(50) NOT NULL, diff --git a/src/auras.cpp b/src/auras.cpp new file mode 100644 index 0000000..1cf745a --- /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 (caseInsensitiveEqual(name, it.name)) { + return ⁢ + } + } + + return nullptr; +} \ No newline at end of file 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/const.h b/src/const.h index 1c1e247..a2497da 100644 --- a/src/const.h +++ b/src/const.h @@ -478,6 +478,8 @@ enum ReloadTypes_t : uint8_t RELOAD_TYPE_MONSTERS, RELOAD_TYPE_MOUNTS, RELOAD_TYPE_WINGS, + RELOAD_TYPE_AURAS, + RELOAD_TYPE_EFFECTS, RELOAD_TYPE_MOVEMENTS, RELOAD_TYPE_NPCS, RELOAD_TYPE_QUESTS, diff --git a/src/creature.h b/src/creature.h index b2779d6..5609b4a 100644 --- a/src/creature.h +++ b/src/creature.h @@ -414,6 +414,8 @@ class Creature : virtual public Thing Outfit_t defaultOutfit; uint16_t currentMount; uint16_t currentWing; + uint16_t currentAura; + uint16_t currentEffect; Position lastPosition; LightInfo internalLight; diff --git a/src/effects.cpp b/src/effects.cpp new file mode 100644 index 0000000..985929e --- /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 (caseInsensitiveEqual(name, it.name)) { + 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 fd7a0b0..d15be46 100644 --- a/src/enums.h +++ b/src/enums.h @@ -504,6 +504,8 @@ struct Outfit_t uint16_t lookTypeEx = 0; uint16_t lookMount = 0; uint16_t lookWing = 0; + uint16_t lookAura = 0; + uint16_t lookEffect = 0; uint8_t lookHead = 0; uint8_t lookBody = 0; uint8_t lookLegs = 0; diff --git a/src/game.cpp b/src/game.cpp index f9d8323..f9ad6b9 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -77,6 +77,8 @@ void Game::setGameState(GameState_t newState) mounts.loadFromXml(); wings.loadFromXml(); + auras.loadFromXml(); + effects.loadFromXml(); raids.loadFromXml(); raids.startup(); @@ -3427,6 +3429,8 @@ void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit, bool randomize if (!playerOutfit) { outfit.lookMount = 0; // outfit.lookWing = 0; + // outfit.lookAura = 0; + // outfit.lookEffect = 0; } if (outfit.lookMount != 0) { @@ -3457,7 +3461,7 @@ void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit, bool randomize player->wasMounted = false; } - /// wings + // @ wings if (outfit.lookWing != 0) { Wing* wing = wings.getWingByID(outfit.lookWing); if (!wing) { @@ -3478,7 +3482,52 @@ void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit, bool randomize 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; + } + // @ + if (player->canWear(outfit.lookType, outfit.lookAddons)) { player->defaultOutfit = outfit; diff --git a/src/game.h b/src/game.h index 07f965e..1a95c2d 100644 --- a/src/game.h +++ b/src/game.h @@ -12,6 +12,8 @@ #include "map.h" #include "mounts.h" #include "wings.h" +#include "auras.h" +#include "effects.h" #include "npc.h" #include "player.h" #include "position.h" @@ -488,7 +490,9 @@ class Game Raids raids; Mounts mounts; Wings wings; - + Effects effects; + Auras auras; + std::forward_list toDecayItems; std::unordered_set getTilesToClean() const { return tilesToClean; } diff --git a/src/iologindata.cpp b/src/iologindata.cpp index b3b8653..f1d5844 100644 --- a/src/iologindata.cpp +++ b/src/iologindata.cpp @@ -229,7 +229,7 @@ bool IOLoginData::loadPlayerById(Player* player, uint32_t id) return loadPlayer( player, db.storeQuery(fmt::format( - "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `currentmount`, `randomizemount`, `currentwing`, `randomizewing`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `direction` FROM `players` WHERE `id` = {:d}", + "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `currentmount`, `randomizemount`, `currentwing`, `randomizewing`,`currenteffect`, `randomizeeffect`,`currentaura`, `randomizeaura`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `direction` FROM `players` WHERE `id` = {:d}", id))); } @@ -239,7 +239,7 @@ bool IOLoginData::loadPlayerByName(Player* player, std::string_view name) return loadPlayer( player, db.storeQuery(fmt::format( - "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `currentmount`, `randomizemount`, `currentwing`, `randomizewing`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `direction` FROM `players` WHERE `name` = {:s}", + "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `currentmount`, `randomizemount`, `currentwing`, `randomizewing`,`currenteffect`, `randomizeeffect`,`currentaura`, `randomizeaura`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `direction` FROM `players` WHERE `name` = {:s}", db.escapeString(name)))); } @@ -362,9 +362,13 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) player->currentOutfit = player->defaultOutfit; player->currentMount = result->getNumber("currentmount"); player->currentWing = result->getNumber("currentwing"); + player->currentEffect = result->getNumber("currenteffect"); + player->currentAura = result->getNumber("currentaura"); player->direction = static_cast(result->getNumber("direction")); player->randomizeMount = result->getNumber("randomizemount") != 0; player->randomizeWing = result->getNumber("randomizewing") != 0; + player->randomizeAura = result->getNumber("randomizeaura") != 0; + player->randomizeEffect = result->getNumber("randomizeeffect") != 0; if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { const time_t skullSeconds = result->getNumber("skulltime") - time(nullptr); if (skullSeconds > 0) { @@ -606,11 +610,42 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) 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(); + std::cout << "currentWing: " << currentWing << std::endl; if (currentWing > 0) { player->attachEffectById(currentWing); } + // @-- + // @-- Aura + uint16_t currentAura = player->getCurrentAura(); + std::cout << "currentAura: " << currentAura << std::endl; + if (currentAura > 0) { + player->attachEffectById(currentAura); + } + // @-- + // @-- Effects + uint16_t currentEffect = player->getCurrentEffect(); + std::cout << "EcurrentEffect: " << currentEffect << std::endl; + if (currentEffect > 0) { + player->attachEffectById(currentEffect); + } + // @-- player->updateBaseSpeed(); player->updateInventoryWeight(); @@ -721,6 +756,10 @@ bool IOLoginData::savePlayer(Player* player) query << "`randomizemount` = " << player->randomizeMount << ","; 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 << "`maglevel` = " << player->magLevel << ','; query << "`mana` = " << player->mana << ','; query << "`manamax` = " << player->manaMax << ','; @@ -939,7 +978,7 @@ bool IOLoginData::savePlayer(Player* player) return false; } - // save wings + // --@ save wings if (!db.executeQuery(fmt::format("DELETE FROM `player_wings` WHERE `player_id` = {:d}", player->getGUID()))) { return false; } @@ -951,10 +990,44 @@ bool IOLoginData::savePlayer(Player* player) 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; + } + // --@ // End the transaction return transaction.commit(); diff --git a/src/luagame.cpp b/src/luagame.cpp index 41bf420..e2f9165 100644 --- a/src/luagame.cpp +++ b/src/luagame.cpp @@ -269,6 +269,36 @@ int luaGameGetWings(lua_State* L) return 1; } +int 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 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; +} + int luaGameGetVocations(lua_State* L) { // Game.getVocations() @@ -706,6 +736,8 @@ void LuaScriptInterface::registerGame() registerMethod("Game", "getOutfits", luaGameGetOutfits); registerMethod("Game", "getMounts", luaGameGetMounts); registerMethod("Game", "getWings", luaGameGetWings); + registerMethod("Game", "getEffects", luaGameGetEffects); + registerMethod("Game", "getAuras", luaGameGetAuras); registerMethod("Game", "getGameState", luaGameGetGameState); registerMethod("Game", "setGameState", luaGameSetGameState); diff --git a/src/luaplayer.cpp b/src/luaplayer.cpp index dd6f060..2f5ed24 100644 --- a/src/luaplayer.cpp +++ b/src/luaplayer.cpp @@ -12,6 +12,8 @@ #include "spells.h" #include "vocation.h" #include "wings.h" +#include "auras.h" +#include "effects.h" extern Chat* g_chat; extern Game g_game; @@ -1685,6 +1687,189 @@ int luaPlayerToggleWing(lua_State* L) } // @ +// @ auras + +int 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 (isInteger(L, 2)) { + auraId = getInteger(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 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 (isInteger(L, 2)) { + auraId = getInteger(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 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 (isInteger(L, 2)) { + aura = g_game.auras.getAuraByID(getInteger(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 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 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 (isInteger(L, 2)) { + effectId = getInteger(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 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 (isInteger(L, 2)) { + effectId = getInteger(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 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 (isInteger(L, 2)) { + effect = g_game.effects.getEffectByID(getInteger(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 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 luaPlayerGetPremiumEndsAt(lua_State* L) { // player:getPremiumEndsAt() @@ -2464,6 +2649,18 @@ void LuaScriptInterface::registerPlayer() registerMethod("Player", "hasWing", luaPlayerHasWing); registerMethod("Player", "toggleWing", luaPlayerToggleWing); // @ + // @ auras + registerMethod("Player", "addAura", luaPlayerAddAura); + registerMethod("Player", "removeAura", luaPlayerRemoveAura); + registerMethod("Player", "hasAura", luaPlayerHasAura); + registerMethod("Player", "toggleAura", luaPlayerToggleAura); + // @ + // @ effects + registerMethod("Player", "addEffect", luaPlayerAddEffect); + registerMethod("Player", "removeEffect", luaPlayerRemoveEffect); + registerMethod("Player", "hasEffect", luaPlayerHasEffect); + registerMethod("Player", "toggleEffect", luaPlayerToggleEffect); + // @ registerMethod("Player", "getPremiumEndsAt", luaPlayerGetPremiumEndsAt); registerMethod("Player", "setPremiumEndsAt", luaPlayerSetPremiumEndsAt); diff --git a/src/luascript.cpp b/src/luascript.cpp index 680fea9..62c349d 100644 --- a/src/luascript.cpp +++ b/src/luascript.cpp @@ -1005,6 +1005,8 @@ void Lua::pushOutfit(lua_State* L, const Outfit_t& outfit) setField(L, "lookTypeEx", outfit.lookTypeEx); setField(L, "lookMount", outfit.lookMount); setField(L, "lookWing", outfit.lookWing); + setField(L, "lookAura", outfit.lookAura); + setField(L, "lookEffect", outfit.lookEffect); setField(L, "lookHead", outfit.lookHead); setField(L, "lookBody", outfit.lookBody); setField(L, "lookLegs", outfit.lookLegs); @@ -1041,6 +1043,23 @@ void Lua::pushWing(lua_State* L, const Wing* wing) setField(L, "id", wing->id); setField(L, "premium", wing->premium); } +void Lua::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 Lua::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); +} + void Lua::pushLoot(lua_State* L, const std::vector& lootList) { @@ -1959,6 +1978,8 @@ void LuaScriptInterface::registerFunctions() registerEnum(RELOAD_TYPE_MONSTERS); registerEnum(RELOAD_TYPE_MOUNTS); registerEnum(RELOAD_TYPE_WINGS); + registerEnum(RELOAD_TYPE_AURAS); + registerEnum(RELOAD_TYPE_EFFECTS); registerEnum(RELOAD_TYPE_MOVEMENTS); registerEnum(RELOAD_TYPE_NPCS); registerEnum(RELOAD_TYPE_QUESTS); diff --git a/src/luascript.h b/src/luascript.h index 1bcbb0b..43a6759 100644 --- a/src/luascript.h +++ b/src/luascript.h @@ -116,6 +116,8 @@ struct LootBlock; struct ModalWindow; struct Mount; struct Wing; +struct Aura; +struct Effect; struct Outfit; struct Position; @@ -778,6 +780,8 @@ void pushOutfit(lua_State* L, const Outfit_t& outfit); void pushOutfit(lua_State* L, const Outfit* outfit); void pushMount(lua_State* L, const Mount* mount); void pushWing(lua_State* L, const Wing* wing); +void pushAura(lua_State* L, const Aura* aura); +void pushEffect(lua_State* L, const Effect* effect); void pushLoot(lua_State* L, const std::vector& lootList); void pushReflect(lua_State* L, const Reflect& reflect); diff --git a/src/player.cpp b/src/player.cpp index ae4eab2..be3d3bd 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -4307,7 +4307,298 @@ bool Player::hasWings() const } 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; } /*bool Player::addOfflineTrainingTries(skills_t skill, uint64_t tries) { if (tries == 0 || skill == SKILL_LEVEL) { diff --git a/src/player.h b/src/player.h index fc8399f..dbfd0f3 100644 --- a/src/player.h +++ b/src/player.h @@ -127,7 +127,7 @@ class Player final : public Creature, public Cylinder bool hasMount(const Mount* mount) const; bool hasMounts() const; void dismount(); - + // -- @ wings uint16_t getRandomWing() const; uint16_t getCurrentWing() const; void setCurrentWing(uint16_t wingId); @@ -138,7 +138,31 @@ class Player final : public Creature, public Cylinder 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(); + // -- @ void sendFYIBox(std::string_view message) { if (client) { @@ -1084,6 +1108,8 @@ class Player final : public Creature, public Cylinder std::unordered_map outfits; std::unordered_set mounts; std::unordered_set wings; + std::unordered_set auras; + std::unordered_set effects; GuildWarVector guildWarVector; @@ -1117,6 +1143,8 @@ class Player final : public Creature, public Cylinder int64_t lastWalkthroughAttempt = 0; int64_t lastToggleMount = 0; int64_t lastToggleWing = 0; + int64_t lastToggleEffect = 0; + int64_t lastToggleAura = 0; int64_t lastPing; int64_t lastPong; @@ -1188,12 +1216,16 @@ class Player final : public Creature, public Cylinder bool ghostMode = false; bool wasMounted = false; bool wasWinged = false; + bool wasAuraed = false; + bool wasEffected = false; bool pzLocked = false; bool isConnecting = false; bool addAttackSkillPoint = false; bool randomizeMount = false; bool randomizeWing = false; + bool randomizeAura = false; + bool randomizeEffect = false; bool inventoryAbilities[CONST_SLOT_LAST + 1] = {}; diff --git a/src/protocolgame.cpp b/src/protocolgame.cpp index a289646..c4d5845 100644 --- a/src/protocolgame.cpp +++ b/src/protocolgame.cpp @@ -1071,6 +1071,8 @@ void ProtocolGame::parseSetOutfit(NetworkMessage& msg) newOutfit.lookAddons = msg.getByte(); newOutfit.lookMount = isOTC ? msg.get() : 0; newOutfit.lookWing = isOTC ? msg.get() : 0; + newOutfit.lookAura = isOTC ? msg.get() : 0; + newOutfit.lookEffect = isOTC ? msg.get() : 0; g_dispatcher.addTask([=, playerID = player->getID()]() { g_game.playerChangeOutfit(playerID, newOutfit); }); } @@ -2389,7 +2391,17 @@ void ProtocolGame::sendOutfitWindow() 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; + } + /*bool mounted; if (player->wasMounted) { mounted = currentOutfit.lookMount != 0; @@ -2451,6 +2463,36 @@ void ProtocolGame::sendOutfitWindow() 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); + } + } writeToOutputBuffer(msg); @@ -2612,6 +2654,8 @@ void ProtocolGame::AddOutfit(NetworkMessage& msg, const Outfit_t& outfit) if (isOTC) { msg.add(outfit.lookMount); msg.add(outfit.lookWing); + msg.add(outfit.lookAura); + msg.add(outfit.lookEffect); } } diff --git a/vc17/theforgottenserver.vcxproj b/vc17/theforgottenserver.vcxproj index b36bef5..7075553 100644 --- a/vc17/theforgottenserver.vcxproj +++ b/vc17/theforgottenserver.vcxproj @@ -237,6 +237,8 @@ + + @@ -325,6 +327,8 @@ + +