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