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