diff --git a/data/XML/mounts.xml b/data/XML/mounts.xml
index 0731871ed4..c604442e4f 100644
--- a/data/XML/mounts.xml
+++ b/data/XML/mounts.xml
@@ -200,4 +200,6 @@
+
+
diff --git a/data/XML/outfits.xml b/data/XML/outfits.xml
index 3d97ac4655..4041d2c1dc 100644
--- a/data/XML/outfits.xml
+++ b/data/XML/outfits.xml
@@ -107,6 +107,8 @@
+
+
@@ -215,4 +217,6 @@
+
+
diff --git a/data/items/items.otb b/data/items/items.otb
index 3def2d5c66..a76ae7874a 100644
Binary files a/data/items/items.otb and b/data/items/items.otb differ
diff --git a/data/items/items.xml b/data/items/items.xml
index 6eed1612a2..42fd8ec1bf 100644
--- a/data/items/items.xml
+++ b/data/items/items.xml
@@ -4420,7 +4420,7 @@
-
-
+
-
@@ -42934,4 +42934,10 @@
+ -
+
+
+ -
+
+
diff --git a/data/lib/core/achievements.lua b/data/lib/core/achievements.lua
index 2cf952c6b1..4f763c5aa2 100644
--- a/data/lib/core/achievements.lua
+++ b/data/lib/core/achievements.lua
@@ -589,6 +589,16 @@ achievements = {
[494] = {clientId = 528, name = "Shell we take a Ride", grade = 1, points = 3, description = "Equipped with the shell of a tortoise and claws of a lobster this insect like companion will help you through every hardship."},
[495] = {clientId = 530, name = "Some Like It Hot", grade = 1, points = 2, description = "You have braved the searing heat in the tunnels deep below Kazordoon and vanquished the Brainstealer. The voices inside your head are finally silenced."},
[496] = {clientId = 427, name = "Woodcarver", grade = 1, points = 3, secret = true, description = "You defeated Megasylvan Yselda in the wake of the sleeping carnisylvan menace deep under Bounac."},
+
+ -- 12.90
+ [502] = {clientId = 534, name = "Friendly Fire", grade = 1, points = 1, description = "You mastered the fire and tamed a supervulcano!"},
+ [503] = {clientId = 535, name = "Wedding Planner", grade = 1, points = 3, description = "Alas! What could be more beautiful and satisfying than bringing two loving hearts together? So romantic!"},
+ [504] = {clientId = 536, name = "Beaver Away", grade = 1, points = 1, description = "You really were as busy as a beaver in order to help the nagas. Enjoy some eager company!"},
+ [505] = {clientId = 537, name = "Snake Pit", grade = 1, points = 1, description = "Mysterious nagas, a vibrant jungle and a sinking island - you really know every corner of Marapur now."},
+ [506] = {clientId = 538, name = "Royalty of Hazard", grade = 1, points = 1, description = "For some it can't be hazardous enough."},
+ [507] = {clientId = 539, name = "Measuring the World", grade = 1, points = 3, description = "Step by step you discovered many of the secrets hidden in the world, thus gaining the right to wear the Discoverer outfit and hat. Made-to-measure for a brave traveller of the wilds."},
+ [508] = {clientId = 540, name = "Ripp-Ripp Hooray!", grade = 1, points = 3, description = "Don't get carried away by your success. Get carried away by your ripptor."},
+ [509] = {clientId = 531, name = "First Achievement", grade = 1, points = 1, secret = true, description = "Congratulations to your very first achievement! ... Well, not really. But imagine, it is. Because at this point during your journey into the past, achievements have been introduced."},
}
ACHIEVEMENT_FIRST = 1
diff --git a/data/scripts/actions/others/taming.lua b/data/scripts/actions/others/taming.lua
index b98abd99d3..8341f3bda7 100644
--- a/data/scripts/actions/others/taming.lua
+++ b/data/scripts/actions/others/taming.lua
@@ -359,6 +359,14 @@ local config = {
{sound = "GRRRRRRRRR", text = "The noble lion majestically rejects your amulet."}
},
success = {sound = "Grrr", text = "The noble lion will now accompany you as a friend and ally."}
+ },
+ [44579] = { -- colourful water lily
+ name = "giant beaver",
+ id = 201,
+ type = TYPE_MONSTER,
+ achievement = "Beaver Away",
+ chance = 100,
+ success = {sound = "Grrr!", text = "You tamed the giant beaver."}
}
}
diff --git a/src/const.h b/src/const.h
index 6dbc04df45..56484b1949 100644
--- a/src/const.h
+++ b/src/const.h
@@ -172,6 +172,7 @@ enum MagicEffectClasses : uint8_t
CONST_ME_HORESTIS = 238,
CONST_ME_DEVOVORGA = 239,
CONST_ME_FERUMBRAS_2 = 240,
+ CONST_ME_FOAM = 241,
};
enum ShootType_t : uint8_t
@@ -581,6 +582,8 @@ enum item_t : uint16_t
ITEM_DEPOT_BOX_XVI = 25468,
ITEM_DEPOT_BOX_XVII = 25469,
ITEM_DEPOT_BOX_XVIII = 34571,
+ ITEM_DEPOT_BOX_XIX = 44714,
+ ITEM_DEPOT_BOX_XX = 44715,
ITEM_MALE_CORPSE = 3058,
ITEM_FEMALE_CORPSE = 3065,
diff --git a/src/definitions.h b/src/definitions.h
index d65a657dcb..c7aafc934d 100644
--- a/src/definitions.h
+++ b/src/definitions.h
@@ -8,9 +8,9 @@ static constexpr auto STATUS_SERVER_NAME = "The Forgotten Server";
static constexpr auto STATUS_SERVER_VERSION = "1.5";
static constexpr auto STATUS_SERVER_DEVELOPERS = "The Forgotten Server Team";
-static constexpr auto CLIENT_VERSION_MIN = 1280;
-static constexpr auto CLIENT_VERSION_MAX = 1288;
-static constexpr auto CLIENT_VERSION_STR = "12.87";
+static constexpr auto CLIENT_VERSION_MIN = 1290;
+static constexpr auto CLIENT_VERSION_MAX = 1291;
+static constexpr auto CLIENT_VERSION_STR = "12.90";
static constexpr auto AUTHENTICATOR_DIGITS = 6U;
static constexpr auto AUTHENTICATOR_PERIOD = 30U;
diff --git a/src/game.cpp b/src/game.cpp
index cc4e587cb9..a77b7c5385 100644
--- a/src/game.cpp
+++ b/src/game.cpp
@@ -3045,7 +3045,7 @@ void Game::internalCloseTrade(Player* player, bool sendCancel /* = true*/)
}
}
-void Game::playerPurchaseItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint8_t amount,
+void Game::playerPurchaseItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint16_t amount,
bool ignoreCap /* = false*/, bool inBackpacks /* = false*/)
{
if (amount == 0 || amount > ITEM_STACK_SIZE) {
@@ -3083,7 +3083,7 @@ void Game::playerPurchaseItem(uint32_t playerId, uint16_t spriteId, uint8_t coun
merchant->onPlayerTrade(player, onBuy, it.id, subType, amount, ignoreCap, inBackpacks);
}
-void Game::playerSellItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint8_t amount, bool ignoreEquipped)
+void Game::playerSellItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint16_t amount, bool ignoreEquipped)
{
if (amount == 0 || amount > ITEM_STACK_SIZE) {
return;
diff --git a/src/game.h b/src/game.h
index b2b215d167..3eaab7e2f9 100644
--- a/src/game.h
+++ b/src/game.h
@@ -354,9 +354,9 @@ class Game
uint16_t spriteId);
void playerAcceptTrade(uint32_t playerId);
void playerLookInTrade(uint32_t playerId, bool lookAtCounterOffer, uint8_t index);
- void playerPurchaseItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint8_t amount, bool ignoreCap = false,
- bool inBackpacks = false);
- void playerSellItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint8_t amount,
+ void playerPurchaseItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint16_t amount,
+ bool ignoreCap = false, bool inBackpacks = false);
+ void playerSellItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint16_t amount,
bool ignoreEquipped = false);
void playerCloseShop(uint32_t playerId);
void playerLookInShop(uint32_t playerId, uint16_t spriteId, uint8_t count);
diff --git a/src/itemloader.h b/src/itemloader.h
index c419870e4d..b593782068 100644
--- a/src/itemloader.h
+++ b/src/itemloader.h
@@ -96,8 +96,9 @@ enum clientVersion_t
CLIENT_VERSION_1285 = 61,
CLIENT_VERSION_1286 = 62,
CLIENT_VERSION_1287 = 63,
+ CLIENT_VERSION_1290 = 64,
- CLIENT_VERSION_LAST = CLIENT_VERSION_1287
+ CLIENT_VERSION_LAST = CLIENT_VERSION_1290
};
enum rootattrib_
@@ -169,8 +170,8 @@ enum itemflags_t
FLAG_HORIZONTAL = 1 << 18,
FLAG_CANNOTDECAY = 1 << 19, // unused
FLAG_ALLOWDISTREAD = 1 << 20,
- FLAG_UNUSED = 1 << 21, // unused
- FLAG_CLIENTCHARGES = 1 << 22, /* deprecated */
+ FLAG_CLIENTDURATION = 1 << 21,
+ FLAG_CLIENTCHARGES = 1 << 22,
FLAG_LOOKTHROUGH = 1 << 23,
FLAG_ANIMATION = 1 << 24,
FLAG_FULLTILE = 1 << 25, // unused
diff --git a/src/items.cpp b/src/items.cpp
index 0c589377b1..62befdc18b 100644
--- a/src/items.cpp
+++ b/src/items.cpp
@@ -560,6 +560,8 @@ bool Items::loadFromOtb(const std::string& file)
iType.isAnimation = hasBitSet(FLAG_ANIMATION, flags);
// iType.walkStack = !hasBitSet(FLAG_FULLTILE, flags);
iType.forceUse = hasBitSet(FLAG_FORCEUSE, flags);
+ iType.showClientCharges = hasBitSet(FLAG_CLIENTCHARGES, flags);
+ iType.showClientDuration = hasBitSet(FLAG_CLIENTDURATION, flags);
iType.id = serverId;
iType.clientId = clientId;
diff --git a/src/items.h b/src/items.h
index 3a89980c5b..0f33e2061a 100644
--- a/src/items.h
+++ b/src/items.h
@@ -412,6 +412,8 @@ class ItemType
bool stopTime = false;
bool showCount = true;
bool supply = false;
+ bool showClientCharges = false;
+ bool showClientDuration = false;
};
class Items
diff --git a/src/luascript.cpp b/src/luascript.cpp
index 6a65a46b4a..54650e96d7 100644
--- a/src/luascript.cpp
+++ b/src/luascript.cpp
@@ -1465,6 +1465,7 @@ void LuaScriptInterface::registerFunctions()
registerEnum(CONST_ME_HORESTIS);
registerEnum(CONST_ME_DEVOVORGA);
registerEnum(CONST_ME_FERUMBRAS_2);
+ registerEnum(CONST_ME_FOAM);
registerEnum(CONST_ANI_NONE);
registerEnum(CONST_ANI_SPEAR);
diff --git a/src/networkmessage.cpp b/src/networkmessage.cpp
index 5bfc3e5be8..882fb2ddc0 100644
--- a/src/networkmessage.cpp
+++ b/src/networkmessage.cpp
@@ -95,6 +95,12 @@ void NetworkMessage::addItem(uint16_t id, uint8_t count)
addByte(0x00); // quiver ammo count
} else if (it.classification > 0) {
addByte(0x00); // item tier (0-10)
+ } else if (it.showClientCharges) {
+ add(it.charges);
+ addByte(0x00); // boolean (is brand new)
+ } else if (it.showClientDuration) {
+ add(it.decayTime);
+ addByte(0x00); // boolean (is brand new)
}
if (it.isPodium()) {
@@ -119,6 +125,14 @@ void NetworkMessage::addItem(const Item* item)
addByte(0x00); // item tier (0-10)
}
+ if (it.showClientCharges) {
+ add(item->getCharges());
+ addByte(0); // boolean (is brand new)
+ } else if (it.showClientDuration) {
+ add(item->getDuration() / 1000);
+ addByte(0); // boolean (is brand new)
+ }
+
if (it.isContainer()) {
addByte(0x00); // assigned loot container icon
// quiver ammo count
diff --git a/src/npc.cpp b/src/npc.cpp
index 70523e332f..5c98f87a57 100644
--- a/src/npc.cpp
+++ b/src/npc.cpp
@@ -344,7 +344,7 @@ void Npc::doSayToPlayer(Player* player, const std::string& text)
}
}
-void Npc::onPlayerTrade(Player* player, int32_t callback, uint16_t itemId, uint8_t count, uint8_t amount,
+void Npc::onPlayerTrade(Player* player, int32_t callback, uint16_t itemId, uint8_t count, uint16_t amount,
bool ignore /* = false*/, bool inBackpacks /* = false*/)
{
if (npcEventHandler) {
@@ -1176,7 +1176,7 @@ void NpcEventsHandler::onCreatureSay(Creature* creature, SpeakClasses type, cons
scriptInterface->callFunction(3);
}
-void NpcEventsHandler::onPlayerTrade(Player* player, int32_t callback, uint16_t itemId, uint8_t count, uint8_t amount,
+void NpcEventsHandler::onPlayerTrade(Player* player, int32_t callback, uint16_t itemId, uint8_t count, uint16_t amount,
bool ignore, bool inBackpacks)
{
if (callback == -1) {
diff --git a/src/npc.h b/src/npc.h
index f2abf09509..75c4e5df54 100644
--- a/src/npc.h
+++ b/src/npc.h
@@ -62,7 +62,7 @@ class NpcEventsHandler
void onCreatureDisappear(Creature* creature);
void onCreatureMove(Creature* creature, const Position& oldPos, const Position& newPos);
void onCreatureSay(Creature* creature, SpeakClasses, const std::string& text);
- void onPlayerTrade(Player* player, int32_t callback, uint16_t itemId, uint8_t count, uint8_t amount,
+ void onPlayerTrade(Player* player, int32_t callback, uint16_t itemId, uint8_t count, uint16_t amount,
bool ignore = false, bool inBackpacks = false);
void onPlayerCloseChannel(Player* player);
void onPlayerEndTrade(Player* player);
@@ -143,7 +143,7 @@ class Npc final : public Creature
}
void onPlayerCloseChannel(Player* player);
- void onPlayerTrade(Player* player, int32_t callback, uint16_t itemId, uint8_t count, uint8_t amount,
+ void onPlayerTrade(Player* player, int32_t callback, uint16_t itemId, uint8_t count, uint16_t amount,
bool ignore = false, bool inBackpacks = false);
void onPlayerEndTrade(Player* player, int32_t buyCallback, int32_t sellCallback);
diff --git a/src/protocolgame.cpp b/src/protocolgame.cpp
index 3b87a70165..a01b76716b 100644
--- a/src/protocolgame.cpp
+++ b/src/protocolgame.cpp
@@ -1370,7 +1370,7 @@ void ProtocolGame::parsePlayerPurchase(NetworkMessage& msg)
{
uint16_t id = msg.get();
uint8_t count = msg.getByte();
- uint8_t amount = msg.getByte();
+ uint16_t amount = msg.get();
bool ignoreCap = msg.getByte() != 0;
bool inBackpacks = msg.getByte() != 0;
g_dispatcher.addTask(DISPATCHER_TASK_EXPIRATION, [=, playerID = player->getID()]() {
@@ -1382,7 +1382,7 @@ void ProtocolGame::parsePlayerSale(NetworkMessage& msg)
{
uint16_t id = msg.get();
uint8_t count = msg.getByte();
- uint8_t amount = msg.getByte();
+ uint16_t amount = msg.get();
bool ignoreEquipped = msg.getByte() != 0;
g_dispatcher.addTask(DISPATCHER_TASK_EXPIRATION, [=, playerID = player->getID()]() {
g_game.playerSellItem(playerID, id, count, amount, ignoreEquipped);
@@ -2022,7 +2022,6 @@ void ProtocolGame::sendSaleItemList(const std::list& shop)
NetworkMessage msg;
msg.addByte(0x7B);
- msg.add(playerBank + playerMoney); // deprecated and ignored by QT client. OTClient still uses it.
std::map saleMap;
@@ -2092,7 +2091,7 @@ void ProtocolGame::sendSaleItemList(const std::list& shop)
uint8_t i = 0;
for (std::map::const_iterator it = saleMap.begin(); i < itemsToSend; ++it, ++i) {
msg.addItemId(it->first);
- msg.addByte(std::min(it->second, std::numeric_limits::max()));
+ msg.add(std::min(it->second, std::numeric_limits::max()));
}
writeToOutputBuffer(msg);
diff --git a/src/tools.cpp b/src/tools.cpp
index a8d6652645..a047b690ba 100644
--- a/src/tools.cpp
+++ b/src/tools.cpp
@@ -539,6 +539,7 @@ MagicEffectNames magicEffectNames = {
{"horestis", CONST_ME_HORESTIS},
{"devovorga", CONST_ME_DEVOVORGA},
{"ferumbras2", CONST_ME_FERUMBRAS_2},
+ {"foam", CONST_ME_FOAM},
};
ShootTypeNames shootTypeNames = {
@@ -656,11 +657,12 @@ SkullNames skullNames = {
{"red", SKULL_RED}, {"black", SKULL_BLACK}, {"orange", SKULL_ORANGE},
};
-std::vector depotBoxes = {ITEM_DEPOT_BOX_I, ITEM_DEPOT_BOX_II, ITEM_DEPOT_BOX_III, ITEM_DEPOT_BOX_IV,
- ITEM_DEPOT_BOX_V, ITEM_DEPOT_BOX_VI, ITEM_DEPOT_BOX_VII, ITEM_DEPOT_BOX_VIII,
- ITEM_DEPOT_BOX_IX, ITEM_DEPOT_BOX_X, ITEM_DEPOT_BOX_XI, ITEM_DEPOT_BOX_XII,
- ITEM_DEPOT_BOX_XIII, ITEM_DEPOT_BOX_XIV, ITEM_DEPOT_BOX_XV, ITEM_DEPOT_BOX_XVI,
- ITEM_DEPOT_BOX_XVII, ITEM_DEPOT_BOX_XVIII};
+std::vector depotBoxes = {
+ ITEM_DEPOT_BOX_I, ITEM_DEPOT_BOX_II, ITEM_DEPOT_BOX_III, ITEM_DEPOT_BOX_IV, ITEM_DEPOT_BOX_V,
+ ITEM_DEPOT_BOX_VI, ITEM_DEPOT_BOX_VII, ITEM_DEPOT_BOX_VIII, ITEM_DEPOT_BOX_IX, ITEM_DEPOT_BOX_X,
+ ITEM_DEPOT_BOX_XI, ITEM_DEPOT_BOX_XII, ITEM_DEPOT_BOX_XIII, ITEM_DEPOT_BOX_XIV, ITEM_DEPOT_BOX_XV,
+ ITEM_DEPOT_BOX_XVI, ITEM_DEPOT_BOX_XVII, ITEM_DEPOT_BOX_XVIII, ITEM_DEPOT_BOX_XIX, ITEM_DEPOT_BOX_XX,
+};
uint16_t getDepotBoxId(uint16_t index)
{