mirror of
https://github.com/ErikasKontenis/SabrehavenServer.git
synced 2025-05-03 19:09:19 +02:00
drafts for market system. It would be better if I remove the inbox system and market is still working like shit
This commit is contained in:
parent
396464b940
commit
f839f0b637
Binary file not shown.
Binary file not shown.
@ -13,6 +13,7 @@ function updateFeatures(version)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- you can add custom features here, list of them is in the modules\gamelib\const.lua
|
-- you can add custom features here, list of them is in the modules\gamelib\const.lua
|
||||||
|
g_game.enableFeature(GamePlayerMarket)
|
||||||
--g_game.enableFeature(GameClientPing)
|
--g_game.enableFeature(GameClientPing)
|
||||||
--g_game.enableFeature(GameExtendedOpcode)
|
--g_game.enableFeature(GameExtendedOpcode)
|
||||||
--g_game.enableFeature(GameMinimapLimitedToSingleFloor) -- it will generate minimap only for current floor
|
--g_game.enableFeature(GameMinimapLimitedToSingleFloor) -- it will generate minimap only for current floor
|
||||||
|
@ -740,6 +740,19 @@ local function onMarketMessage(messageMode, message)
|
|||||||
Market.displayMessage(message)
|
Market.displayMessage(message)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function dump(o)
|
||||||
|
if type(o) == 'table' then
|
||||||
|
local s = '{ '
|
||||||
|
for k,v in pairs(o) do
|
||||||
|
if type(k) ~= 'number' then k = '"'..k..'"' end
|
||||||
|
s = s .. '['..k..'] = ' .. dump(v) .. ','
|
||||||
|
end
|
||||||
|
return s .. '} '
|
||||||
|
else
|
||||||
|
return tostring(o)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function initMarketItems(items)
|
local function initMarketItems(items)
|
||||||
for c = MarketCategory.First, MarketCategory.Last do
|
for c = MarketCategory.First, MarketCategory.Last do
|
||||||
marketItems[c] = {}
|
marketItems[c] = {}
|
||||||
@ -768,7 +781,6 @@ local function initMarketItems(items)
|
|||||||
tradeAs = entry.id
|
tradeAs = entry.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
-- add new market item
|
-- add new market item
|
||||||
if marketItems[entry.category] ~= nil then
|
if marketItems[entry.category] ~= nil then
|
||||||
table.insert(marketItems[entry.category], marketItem)
|
table.insert(marketItems[entry.category], marketItem)
|
||||||
|
@ -37,39 +37,33 @@ local function readMarketOffer(msg, action, var)
|
|||||||
return MarketOffer.new({timestamp, counter}, action, Item.create(itemId), amount, price, playerName, state, var)
|
return MarketOffer.new({timestamp, counter}, action, Item.create(itemId), amount, price, playerName, state, var)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function dump(o)
|
||||||
|
if type(o) == 'table' then
|
||||||
|
local s = '{ '
|
||||||
|
for k,v in pairs(o) do
|
||||||
|
if type(k) ~= 'number' then k = '"'..k..'"' end
|
||||||
|
s = s .. '['..k..'] = ' .. dump(v) .. ','
|
||||||
|
end
|
||||||
|
return s .. '} '
|
||||||
|
else
|
||||||
|
return tostring(o)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- parsing protocols
|
-- parsing protocols
|
||||||
local function parseMarketEnter(protocol, msg)
|
local function parseMarketEnter(protocol, msg)
|
||||||
local items
|
local version = 981
|
||||||
if g_game.getClientVersion() < 944 then
|
local items = {}
|
||||||
items = {}
|
|
||||||
local itemsCount = msg:getU16()
|
|
||||||
for i = 1, itemsCount do
|
|
||||||
local itemId = msg:getU16()
|
|
||||||
local category = msg:getU8()
|
|
||||||
local name = msg:getString()
|
|
||||||
table.insert(items, {
|
|
||||||
id = itemId,
|
|
||||||
category = category,
|
|
||||||
name = name
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local balance = 0
|
table.insert(items, {
|
||||||
if g_game.getProtocolVersion() <= 1250 or not g_game.getFeature(GameTibia12Protocol) then
|
id = 3264,
|
||||||
if g_game.getProtocolVersion() >= 981 or g_game.getProtocolVersion() < 944 then
|
category = 20,
|
||||||
balance = msg:getU64()
|
name = "sword"
|
||||||
else
|
})
|
||||||
balance = msg:getU32()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local vocation = -1
|
local vocation = -1
|
||||||
if g_game.getProtocolVersion() >= 944 and g_game.getProtocolVersion() < 950 then
|
|
||||||
vocation = msg:getU8() -- get vocation id
|
|
||||||
end
|
|
||||||
local offers = msg:getU8()
|
local offers = msg:getU8()
|
||||||
|
local balance = msg:getU64();
|
||||||
local depotItems = {}
|
local depotItems = {}
|
||||||
local depotCount = msg:getU16()
|
local depotCount = msg:getU16()
|
||||||
for i = 1, depotCount do
|
for i = 1, depotCount do
|
||||||
@ -79,7 +73,9 @@ local function parseMarketEnter(protocol, msg)
|
|||||||
depotItems[itemId] = itemCount
|
depotItems[itemId] = itemCount
|
||||||
end
|
end
|
||||||
|
|
||||||
|
print(dump(depotItems))
|
||||||
signalcall(Market.onMarketEnter, depotItems, offers, balance, vocation, items)
|
signalcall(Market.onMarketEnter, depotItems, offers, balance, vocation, items)
|
||||||
|
--signalcall(Market.onMarketEnter, depotItems, offers, balance, vocation, items)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -89,9 +85,11 @@ local function parseMarketLeave(protocol, msg)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function parseMarketDetail(protocol, msg)
|
local function parseMarketDetail(protocol, msg)
|
||||||
|
print("DETAILS")
|
||||||
local itemId = msg:getU16()
|
local itemId = msg:getU16()
|
||||||
|
print (itemId + "assadsd")
|
||||||
local descriptions = {}
|
local descriptions = {}
|
||||||
|
table.insert(descriptions, {2, "28"})
|
||||||
for i = MarketItemDescription.First, MarketItemDescription.Last do
|
for i = MarketItemDescription.First, MarketItemDescription.Last do
|
||||||
if msg:peekU16() ~= 0x00 then
|
if msg:peekU16() ~= 0x00 then
|
||||||
table.insert(descriptions, {i, msg:getString()}) -- item descriptions
|
table.insert(descriptions, {i, msg:getString()}) -- item descriptions
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,8 @@ ropeSpotBlock = false
|
|||||||
showMonsterLoot = true
|
showMonsterLoot = true
|
||||||
blockHeight = false
|
blockHeight = false
|
||||||
dropItems = false
|
dropItems = false
|
||||||
|
marketOfferDuration = 1
|
||||||
|
premiumToCreateMarketOffer = false
|
||||||
|
|
||||||
-- Combat settings
|
-- Combat settings
|
||||||
-- NOTE: valid values for worldType are: "pvp", "no-pvp" and "pvp-enforced"
|
-- NOTE: valid values for worldType are: "pvp", "no-pvp" and "pvp-enforced"
|
||||||
|
@ -33802,3 +33802,17 @@ Flags = {Bottom,Unpass,Unmove}
|
|||||||
TypeID = 7475
|
TypeID = 7475
|
||||||
Name = "a sarcophagus"
|
Name = "a sarcophagus"
|
||||||
Flags = {Bottom,Unpass,Unmove}
|
Flags = {Bottom,Unpass,Unmove}
|
||||||
|
|
||||||
|
TypeID = 7476
|
||||||
|
Name = "your inbox"
|
||||||
|
Flags = {Container,Unmove}
|
||||||
|
Attributes = {Capacity=30}
|
||||||
|
|
||||||
|
TypeID = 7477
|
||||||
|
Name = "the market"
|
||||||
|
Flags = {Unmove}
|
||||||
|
|
||||||
|
TypeID = 7478
|
||||||
|
Name = "your store inbox"
|
||||||
|
Flags = {Container,Unmove}
|
||||||
|
Attributes = {Capacity=30}
|
@ -55,6 +55,47 @@ CREATE TABLE `account_bans` (
|
|||||||
|
|
||||||
-- --------------------------------------------------------
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `player_inboxitems` (
|
||||||
|
`player_id` int NOT NULL,
|
||||||
|
`sid` int NOT NULL,
|
||||||
|
`pid` int NOT NULL DEFAULT '0',
|
||||||
|
`itemtype` smallint unsigned NOT NULL,
|
||||||
|
`count` smallint NOT NULL DEFAULT '0',
|
||||||
|
`attributes` blob NOT NULL,
|
||||||
|
UNIQUE KEY `player_id_2` (`player_id`, `sid`),
|
||||||
|
FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `market_history` (
|
||||||
|
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`player_id` int NOT NULL,
|
||||||
|
`sale` tinyint NOT NULL DEFAULT '0',
|
||||||
|
`itemtype` smallint unsigned NOT NULL,
|
||||||
|
`amount` smallint unsigned NOT NULL,
|
||||||
|
`price` bigint unsigned NOT NULL DEFAULT '0',
|
||||||
|
`expires_at` bigint unsigned NOT NULL,
|
||||||
|
`inserted` bigint unsigned NOT NULL,
|
||||||
|
`state` tinyint unsigned NOT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `player_id` (`player_id`, `sale`),
|
||||||
|
FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `market_offers` (
|
||||||
|
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`player_id` int NOT NULL,
|
||||||
|
`sale` tinyint NOT NULL DEFAULT '0',
|
||||||
|
`itemtype` smallint unsigned NOT NULL,
|
||||||
|
`amount` smallint unsigned NOT NULL,
|
||||||
|
`created` bigint unsigned NOT NULL,
|
||||||
|
`anonymous` tinyint NOT NULL DEFAULT '0',
|
||||||
|
`price` bigint unsigned NOT NULL DEFAULT '0',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `sale` (`sale`,`itemtype`),
|
||||||
|
KEY `created` (`created`),
|
||||||
|
FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Table structure for table `account_ban_history`
|
-- Table structure for table `account_ban_history`
|
||||||
--
|
--
|
||||||
|
@ -273,6 +273,7 @@ ReturnValue Actions::internalUseItem(Player* player, const Position& pos, uint8_
|
|||||||
DepotLocker* myDepotLocker = player->getDepotLocker(depot->getDepotId(), true);
|
DepotLocker* myDepotLocker = player->getDepotLocker(depot->getDepotId(), true);
|
||||||
myDepotLocker->setParent(depot->getParent()->getTile());
|
myDepotLocker->setParent(depot->getParent()->getTile());
|
||||||
openContainer = myDepotLocker;
|
openContainer = myDepotLocker;
|
||||||
|
player->setLastDepotId(depot->getDepotId());
|
||||||
} else {
|
} else {
|
||||||
openContainer = container;
|
openContainer = container;
|
||||||
}
|
}
|
||||||
@ -325,6 +326,17 @@ bool Actions::useItem(Player* player, const Position& pos, uint8_t index, Item*
|
|||||||
player->setNextAction(OTSYS_TIME() + g_config.getNumber(ConfigManager::ACTIONS_DELAY_INTERVAL));
|
player->setNextAction(OTSYS_TIME() + g_config.getNumber(ConfigManager::ACTIONS_DELAY_INTERVAL));
|
||||||
player->stopWalk();
|
player->stopWalk();
|
||||||
|
|
||||||
|
if (item->getID() == ITEM_MARKET)
|
||||||
|
{
|
||||||
|
if (player->getLastDepotId() == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
player->sendMarketEnter(player->getLastDepotId());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (isHotkey) {
|
if (isHotkey) {
|
||||||
uint32_t count = 0;
|
uint32_t count = 0;
|
||||||
if (item->isRune()) {
|
if (item->isRune()) {
|
||||||
|
@ -63,6 +63,8 @@ bool ConfigManager::load()
|
|||||||
integer[GAME_PORT] = getGlobalNumber(L, "gameProtocolPort", 7172);
|
integer[GAME_PORT] = getGlobalNumber(L, "gameProtocolPort", 7172);
|
||||||
integer[LOGIN_PORT] = getGlobalNumber(L, "loginProtocolPort", 7171);
|
integer[LOGIN_PORT] = getGlobalNumber(L, "loginProtocolPort", 7171);
|
||||||
integer[STATUS_PORT] = getGlobalNumber(L, "statusProtocolPort", 7171);
|
integer[STATUS_PORT] = getGlobalNumber(L, "statusProtocolPort", 7171);
|
||||||
|
|
||||||
|
integer[MARKET_OFFER_DURATION] = getGlobalNumber(L, "marketOfferDuration", 30 * 24 * 60 * 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean[SHOW_MONSTER_LOOT] = getGlobalBoolean(L, "showMonsterLoot", true);
|
boolean[SHOW_MONSTER_LOOT] = getGlobalBoolean(L, "showMonsterLoot", true);
|
||||||
@ -74,6 +76,7 @@ bool ConfigManager::load()
|
|||||||
boolean[FREE_PREMIUM] = getGlobalBoolean(L, "freePremium", false);
|
boolean[FREE_PREMIUM] = getGlobalBoolean(L, "freePremium", false);
|
||||||
boolean[REPLACE_KICK_ON_LOGIN] = getGlobalBoolean(L, "replaceKickOnLogin", true);
|
boolean[REPLACE_KICK_ON_LOGIN] = getGlobalBoolean(L, "replaceKickOnLogin", true);
|
||||||
boolean[ALLOW_CLONES] = getGlobalBoolean(L, "allowClones", false);
|
boolean[ALLOW_CLONES] = getGlobalBoolean(L, "allowClones", false);
|
||||||
|
boolean[MARKET_PREMIUM] = getGlobalBoolean(L, "premiumToCreateMarketOffer", true);
|
||||||
boolean[STAMINA_SYSTEM] = getGlobalBoolean(L, "staminaSystem", true);
|
boolean[STAMINA_SYSTEM] = getGlobalBoolean(L, "staminaSystem", true);
|
||||||
boolean[WARN_UNSAFE_SCRIPTS] = getGlobalBoolean(L, "warnUnsafeScripts", true);
|
boolean[WARN_UNSAFE_SCRIPTS] = getGlobalBoolean(L, "warnUnsafeScripts", true);
|
||||||
boolean[CONVERT_UNSAFE_SCRIPTS] = getGlobalBoolean(L, "convertUnsafeScripts", true);
|
boolean[CONVERT_UNSAFE_SCRIPTS] = getGlobalBoolean(L, "convertUnsafeScripts", true);
|
||||||
@ -126,6 +129,8 @@ bool ConfigManager::load()
|
|||||||
integer[KILLS_MONTH_BANISHMENT] = getGlobalNumber(L, "killsMonthBanishment", 10);
|
integer[KILLS_MONTH_BANISHMENT] = getGlobalNumber(L, "killsMonthBanishment", 10);
|
||||||
integer[STAIRHOP_DELAY] = getGlobalNumber(L, "stairJumpExhaustion", 2000);
|
integer[STAIRHOP_DELAY] = getGlobalNumber(L, "stairJumpExhaustion", 2000);
|
||||||
integer[EXP_FROM_PLAYERS_LEVEL_RANGE] = getGlobalNumber(L, "expFromPlayersLevelRange", 75);
|
integer[EXP_FROM_PLAYERS_LEVEL_RANGE] = getGlobalNumber(L, "expFromPlayersLevelRange", 75);
|
||||||
|
integer[CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES] = getGlobalNumber(L, "checkExpiredMarketOffersEachMinutes", 60);
|
||||||
|
integer[MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER] = getGlobalNumber(L, "maxMarketOffersAtATimePerPlayer", 100);
|
||||||
integer[MAX_PACKETS_PER_SECOND] = getGlobalNumber(L, "maxPacketsPerSecond", 25);
|
integer[MAX_PACKETS_PER_SECOND] = getGlobalNumber(L, "maxPacketsPerSecond", 25);
|
||||||
integer[NEWBIE_TOWN] = getGlobalNumber(L, "newbieTownId", 1);
|
integer[NEWBIE_TOWN] = getGlobalNumber(L, "newbieTownId", 1);
|
||||||
integer[NEWBIE_LEVEL_THRESHOLD] = getGlobalNumber(L, "newbieLevelThreshold", 5);
|
integer[NEWBIE_LEVEL_THRESHOLD] = getGlobalNumber(L, "newbieLevelThreshold", 5);
|
||||||
|
@ -41,6 +41,7 @@ class ConfigManager
|
|||||||
ALLOW_CLONES,
|
ALLOW_CLONES,
|
||||||
BIND_ONLY_GLOBAL_ADDRESS,
|
BIND_ONLY_GLOBAL_ADDRESS,
|
||||||
OPTIMIZE_DATABASE,
|
OPTIMIZE_DATABASE,
|
||||||
|
MARKET_PREMIUM,
|
||||||
STAMINA_SYSTEM,
|
STAMINA_SYSTEM,
|
||||||
WARN_UNSAFE_SCRIPTS,
|
WARN_UNSAFE_SCRIPTS,
|
||||||
CONVERT_UNSAFE_SCRIPTS,
|
CONVERT_UNSAFE_SCRIPTS,
|
||||||
@ -113,6 +114,9 @@ class ConfigManager
|
|||||||
LOGIN_PORT,
|
LOGIN_PORT,
|
||||||
STATUS_PORT,
|
STATUS_PORT,
|
||||||
STAIRHOP_DELAY,
|
STAIRHOP_DELAY,
|
||||||
|
MARKET_OFFER_DURATION,
|
||||||
|
CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES,
|
||||||
|
MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER,
|
||||||
EXP_FROM_PLAYERS_LEVEL_RANGE,
|
EXP_FROM_PLAYERS_LEVEL_RANGE,
|
||||||
MAX_PACKETS_PER_SECOND,
|
MAX_PACKETS_PER_SECOND,
|
||||||
NEWBIE_TOWN,
|
NEWBIE_TOWN,
|
||||||
|
12
src/const.h
12
src/const.h
@ -21,6 +21,8 @@
|
|||||||
#define FS_CONST_H_0A49B5996F074465BF44B90F4F780E8B
|
#define FS_CONST_H_0A49B5996F074465BF44B90F4F780E8B
|
||||||
|
|
||||||
static constexpr int32_t NETWORKMESSAGE_MAXSIZE = 24590;
|
static constexpr int32_t NETWORKMESSAGE_MAXSIZE = 24590;
|
||||||
|
static constexpr int32_t MIN_MARKET_FEE = 20;
|
||||||
|
static constexpr int32_t MAX_MARKET_FEE = 100000;
|
||||||
|
|
||||||
enum MagicEffectClasses : uint8_t {
|
enum MagicEffectClasses : uint8_t {
|
||||||
CONST_ME_NONE,
|
CONST_ME_NONE,
|
||||||
@ -281,6 +283,8 @@ enum item_t : uint16_t {
|
|||||||
|
|
||||||
ITEM_DEPOT = 3502,
|
ITEM_DEPOT = 3502,
|
||||||
ITEM_LOCKER1 = 3497,
|
ITEM_LOCKER1 = 3497,
|
||||||
|
ITEM_INBOX = 7476,
|
||||||
|
ITEM_MARKET = 7477,
|
||||||
|
|
||||||
ITEM_MALE_CORPSE = 4240,
|
ITEM_MALE_CORPSE = 4240,
|
||||||
ITEM_FEMALE_CORPSE = 4247,
|
ITEM_FEMALE_CORPSE = 4247,
|
||||||
@ -301,6 +305,14 @@ enum item_t : uint16_t {
|
|||||||
ITEM_DOCUMENT_RO = 2819, //read-only
|
ITEM_DOCUMENT_RO = 2819, //read-only
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum ResourceTypes_t : uint8_t {
|
||||||
|
RESOURCE_BANK_BALANCE = 0x00,
|
||||||
|
RESOURCE_GOLD_EQUIPPED = 0x01,
|
||||||
|
RESOURCE_PREY_WILDCARDS = 0x0A,
|
||||||
|
RESOURCE_DAILYREWARD_STREAK = 0x14,
|
||||||
|
RESOURCE_DAILYREWARD_JOKERS = 0x15,
|
||||||
|
};
|
||||||
|
|
||||||
enum PlayerFlags : uint64_t {
|
enum PlayerFlags : uint64_t {
|
||||||
PlayerFlag_CannotUseCombat = 1 << 0,
|
PlayerFlag_CannotUseCombat = 1 << 0,
|
||||||
PlayerFlag_CannotAttackPlayer = 1 << 1,
|
PlayerFlag_CannotAttackPlayer = 1 << 1,
|
||||||
|
@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
#include "otpch.h"
|
#include "otpch.h"
|
||||||
|
|
||||||
|
#include "depotchest.h"
|
||||||
|
#include "inbox.h"
|
||||||
#include "container.h"
|
#include "container.h"
|
||||||
#include "iomap.h"
|
#include "iomap.h"
|
||||||
#include "game.h"
|
#include "game.h"
|
||||||
@ -264,12 +266,17 @@ ReturnValue Container::queryAdd(int32_t index, const Thing& thing, uint32_t coun
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Cylinder* cylinder = getParent();
|
const Cylinder* cylinder = getParent();
|
||||||
|
|
||||||
if (!hasBitSet(FLAG_NOLIMIT, flags)) {
|
if (!hasBitSet(FLAG_NOLIMIT, flags)) {
|
||||||
while (cylinder) {
|
while (cylinder) {
|
||||||
if (cylinder == &thing) {
|
if (cylinder == &thing) {
|
||||||
return RETURNVALUE_THISISIMPOSSIBLE;
|
return RETURNVALUE_THISISIMPOSSIBLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dynamic_cast<const Inbox*>(cylinder)) {
|
||||||
|
return RETURNVALUE_CONTAINERNOTENOUGHROOM;
|
||||||
|
}
|
||||||
|
|
||||||
cylinder = cylinder->getParent();
|
cylinder = cylinder->getParent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
68
src/depotchest.cpp
Normal file
68
src/depotchest.cpp
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// Copyright 2022 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 "depotchest.h"
|
||||||
|
|
||||||
|
#include "tools.h"
|
||||||
|
|
||||||
|
DepotChest::DepotChest(uint16_t type) :
|
||||||
|
Container(type), maxDepotItems(2000) {}
|
||||||
|
|
||||||
|
ReturnValue DepotChest::queryAdd(int32_t index, const Thing& thing, uint32_t count,
|
||||||
|
uint32_t flags, Creature* actor/* = nullptr*/) const
|
||||||
|
{
|
||||||
|
const Item* item = thing.getItem();
|
||||||
|
if (!item) {
|
||||||
|
return RETURNVALUE_NOTPOSSIBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool skipLimit = hasBitSet(FLAG_NOLIMIT, flags);
|
||||||
|
if (!skipLimit) {
|
||||||
|
int32_t addCount = 0;
|
||||||
|
|
||||||
|
if ((item->isStackable() && item->getItemCount() != count)) {
|
||||||
|
addCount = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item->getTopParent() != this) {
|
||||||
|
if (const Container* container = item->getContainer()) {
|
||||||
|
addCount = container->getItemHoldingCount() + 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
addCount = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getItemHoldingCount() + addCount > maxDepotItems) {
|
||||||
|
return RETURNVALUE_DEPOTISFULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Container::queryAdd(index, thing, count, flags, actor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DepotChest::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t)
|
||||||
|
{
|
||||||
|
Cylinder* parent = getParent();
|
||||||
|
if (parent) {
|
||||||
|
parent->postAddNotification(thing, oldParent, index, LINK_PARENT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DepotChest::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t)
|
||||||
|
{
|
||||||
|
Cylinder* parent = getParent();
|
||||||
|
if (parent) {
|
||||||
|
parent->postRemoveNotification(thing, newParent, index, LINK_PARENT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Cylinder* DepotChest::getParent() const
|
||||||
|
{
|
||||||
|
if (parent) {
|
||||||
|
return parent->getParent();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
40
src/depotchest.h
Normal file
40
src/depotchest.h
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// Copyright 2022 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_DEPOTCHEST_H
|
||||||
|
#define FS_DEPOTCHEST_H
|
||||||
|
|
||||||
|
#include "container.h"
|
||||||
|
|
||||||
|
class DepotChest final : public Container
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit DepotChest(uint16_t type);
|
||||||
|
|
||||||
|
//serialization
|
||||||
|
void setMaxDepotItems(uint32_t maxitems) {
|
||||||
|
maxDepotItems = maxitems;
|
||||||
|
}
|
||||||
|
|
||||||
|
//cylinder implementations
|
||||||
|
ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count,
|
||||||
|
uint32_t flags, Creature* actor = nullptr) const override;
|
||||||
|
|
||||||
|
void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) override;
|
||||||
|
void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) override;
|
||||||
|
|
||||||
|
//overrides
|
||||||
|
bool canRemove() const override {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Cylinder* getParent() const override;
|
||||||
|
Cylinder* getRealParent() const override {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32_t maxDepotItems;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FS_DEPOTCHEST_H
|
@ -23,6 +23,7 @@
|
|||||||
#include "creature.h"
|
#include "creature.h"
|
||||||
#include "player.h"
|
#include "player.h"
|
||||||
#include "tools.h"
|
#include "tools.h"
|
||||||
|
#include "inbox.h"
|
||||||
|
|
||||||
DepotLocker::DepotLocker(uint16_t type) :
|
DepotLocker::DepotLocker(uint16_t type) :
|
||||||
Container(type, 30), depotId(0) {}
|
Container(type, 30), depotId(0) {}
|
||||||
@ -87,3 +88,12 @@ void DepotLocker::postRemoveNotification(Thing* thing, const Cylinder* newParent
|
|||||||
parent->postRemoveNotification(thing, newParent, index, LINK_PARENT);
|
parent->postRemoveNotification(thing, newParent, index, LINK_PARENT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DepotLocker::removeInbox(Inbox* inbox)
|
||||||
|
{
|
||||||
|
auto cit = std::find(itemlist.begin(), itemlist.end(), inbox);
|
||||||
|
if (cit == itemlist.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
itemlist.erase(cit);
|
||||||
|
}
|
||||||
|
@ -22,6 +22,8 @@
|
|||||||
|
|
||||||
#include "container.h"
|
#include "container.h"
|
||||||
|
|
||||||
|
class Inbox;
|
||||||
|
|
||||||
class DepotLocker final : public Container
|
class DepotLocker final : public Container
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -51,6 +53,8 @@ class DepotLocker final : public Container
|
|||||||
void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) final;
|
void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) final;
|
||||||
void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) final;
|
void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) final;
|
||||||
|
|
||||||
|
void removeInbox(Inbox* inbox);
|
||||||
|
|
||||||
bool canRemove() const final {
|
bool canRemove() const final {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
67
src/enums.h
67
src/enums.h
@ -63,6 +63,26 @@ enum VipStatus_t : uint8_t {
|
|||||||
VIPSTATUS_ONLINE = 1,
|
VIPSTATUS_ONLINE = 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum MarketAction_t {
|
||||||
|
MARKETACTION_BUY = 0,
|
||||||
|
MARKETACTION_SELL = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum MarketRequest_t {
|
||||||
|
MARKETREQUEST_OWN_HISTORY = 1,
|
||||||
|
MARKETREQUEST_OWN_OFFERS = 2,
|
||||||
|
MARKETREQUEST_ITEM = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum MarketOfferState_t {
|
||||||
|
OFFERSTATE_ACTIVE = 0,
|
||||||
|
OFFERSTATE_CANCELLED = 1,
|
||||||
|
OFFERSTATE_EXPIRED = 2,
|
||||||
|
OFFERSTATE_ACCEPTED = 3,
|
||||||
|
|
||||||
|
OFFERSTATE_ACCEPTEDEX = 255,
|
||||||
|
};
|
||||||
|
|
||||||
enum OperatingSystem_t : uint8_t {
|
enum OperatingSystem_t : uint8_t {
|
||||||
CLIENTOS_NONE = 0,
|
CLIENTOS_NONE = 0,
|
||||||
|
|
||||||
@ -351,6 +371,8 @@ enum ReturnValue {
|
|||||||
RETURNVALUE_YOUDONTOWNTHISHOUSE,
|
RETURNVALUE_YOUDONTOWNTHISHOUSE,
|
||||||
RETURNVALUE_TRADEPLAYERALREADYOWNSAHOUSE,
|
RETURNVALUE_TRADEPLAYERALREADYOWNSAHOUSE,
|
||||||
RETURNVALUE_TRADEPLAYERHIGHESTBIDDER,
|
RETURNVALUE_TRADEPLAYERHIGHESTBIDDER,
|
||||||
|
RETURNVALUE_CANNOTMOVEITEMISNOTSTOREITEM,
|
||||||
|
RETURNVALUE_ITEMCANNOTBEMOVEDTHERE,
|
||||||
RETURNVALUE_YOUCANNOTTRADETHISHOUSE,
|
RETURNVALUE_YOUCANNOTTRADETHISHOUSE,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -371,6 +393,48 @@ struct LightInfo {
|
|||||||
constexpr LightInfo(uint8_t level, uint8_t color) : level(level), color(color) {}
|
constexpr LightInfo(uint8_t level, uint8_t color) : level(level), color(color) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct MarketOffer {
|
||||||
|
uint64_t price;
|
||||||
|
uint32_t timestamp;
|
||||||
|
uint16_t amount;
|
||||||
|
uint16_t counter;
|
||||||
|
uint16_t itemId;
|
||||||
|
std::string playerName;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MarketOfferEx {
|
||||||
|
MarketOfferEx() = default;
|
||||||
|
MarketOfferEx(MarketOfferEx&& other) :
|
||||||
|
id(other.id), playerId(other.playerId), timestamp(other.timestamp), price(other.price),
|
||||||
|
amount(other.amount), counter(other.counter), itemId(other.itemId), type(other.type),
|
||||||
|
playerName(std::move(other.playerName)) {}
|
||||||
|
|
||||||
|
uint32_t id;
|
||||||
|
uint32_t playerId;
|
||||||
|
uint32_t timestamp;
|
||||||
|
uint64_t price;
|
||||||
|
uint16_t amount;
|
||||||
|
uint16_t counter;
|
||||||
|
uint16_t itemId;
|
||||||
|
MarketAction_t type;
|
||||||
|
std::string playerName;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HistoryMarketOffer {
|
||||||
|
uint32_t timestamp;
|
||||||
|
uint64_t price;
|
||||||
|
uint16_t itemId;
|
||||||
|
uint16_t amount;
|
||||||
|
MarketOfferState_t state;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MarketStatistics {
|
||||||
|
uint32_t numTransactions = 0;
|
||||||
|
uint32_t highestPrice = 0;
|
||||||
|
uint64_t totalPrice = 0;
|
||||||
|
uint32_t lowestPrice = 0;
|
||||||
|
};
|
||||||
|
|
||||||
enum CombatOrigin
|
enum CombatOrigin
|
||||||
{
|
{
|
||||||
ORIGIN_NONE,
|
ORIGIN_NONE,
|
||||||
@ -398,4 +462,7 @@ struct CombatDamage
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using MarketOfferList = std::list<MarketOffer>;
|
||||||
|
using HistoryMarketOfferList = std::list<HistoryMarketOffer>;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -130,6 +130,9 @@ bool Events::load()
|
|||||||
else if (methodName == "onLookInTrade") {
|
else if (methodName == "onLookInTrade") {
|
||||||
playerOnLookInTrade = event;
|
playerOnLookInTrade = event;
|
||||||
}
|
}
|
||||||
|
else if (methodName == "onLookInMarket") {
|
||||||
|
playerOnLookInMarket = event;
|
||||||
|
}
|
||||||
else if (methodName == "onTradeRequest") {
|
else if (methodName == "onTradeRequest") {
|
||||||
playerOnTradeRequest = event;
|
playerOnTradeRequest = event;
|
||||||
}
|
}
|
||||||
@ -499,6 +502,33 @@ void Events::eventPlayerOnLookInTrade(Player* player, Player* partner, Item* ite
|
|||||||
scriptInterface.callVoidFunction(4);
|
scriptInterface.callVoidFunction(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Events::eventPlayerOnLookInMarket(Player* player, const ItemType* itemType)
|
||||||
|
{
|
||||||
|
// Player:onLookInMarket(itemType) or Player.onLookInMarket(self, itemType)
|
||||||
|
if (playerOnLookInMarket == -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!scriptInterface.reserveScriptEnv()) {
|
||||||
|
std::cout << "[Error - Events::eventPlayerOnLookInMarket] Call stack overflow" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptEnvironment* env = scriptInterface.getScriptEnv();
|
||||||
|
env->setScriptId(playerOnLookInMarket, &scriptInterface);
|
||||||
|
|
||||||
|
lua_State* L = scriptInterface.getLuaState();
|
||||||
|
scriptInterface.pushFunction(playerOnLookInMarket);
|
||||||
|
|
||||||
|
LuaScriptInterface::pushUserdata<Player>(L, player);
|
||||||
|
LuaScriptInterface::setMetatable(L, -1, "Player");
|
||||||
|
|
||||||
|
LuaScriptInterface::pushUserdata<const ItemType>(L, itemType);
|
||||||
|
LuaScriptInterface::setMetatable(L, -1, "ItemType");
|
||||||
|
|
||||||
|
return scriptInterface.callFunction(2);
|
||||||
|
}
|
||||||
|
|
||||||
bool Events::eventPlayerOnMoveItem(Player* player, Item* item, uint16_t count, const Position& fromPosition, const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder)
|
bool Events::eventPlayerOnMoveItem(Player* player, Item* item, uint16_t count, const Position& fromPosition, const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder)
|
||||||
{
|
{
|
||||||
// Player:onMoveItem(item, count, fromPosition, toPosition) or Player.onMoveItem(self, item, count, fromPosition, toPosition, fromCylinder, toCylinder)
|
// Player:onMoveItem(item, count, fromPosition, toPosition) or Player.onMoveItem(self, item, count, fromPosition, toPosition, fromCylinder, toCylinder)
|
||||||
|
@ -49,6 +49,7 @@ public:
|
|||||||
void eventPlayerOnLook(Player* player, const Position& position, Thing* thing, uint8_t stackpos, int32_t lookDistance);
|
void eventPlayerOnLook(Player* player, const Position& position, Thing* thing, uint8_t stackpos, int32_t lookDistance);
|
||||||
void eventPlayerOnLookInBattleList(Player* player, Creature* creature, int32_t lookDistance);
|
void eventPlayerOnLookInBattleList(Player* player, Creature* creature, int32_t lookDistance);
|
||||||
void eventPlayerOnLookInTrade(Player* player, Player* partner, Item* item, int32_t lookDistance);
|
void eventPlayerOnLookInTrade(Player* player, Player* partner, Item* item, int32_t lookDistance);
|
||||||
|
bool eventPlayerOnLookInMarket(Player* player, const ItemType* itemType);
|
||||||
bool eventPlayerOnMoveItem(Player* player, Item* item, uint16_t count, const Position& fromPosition, const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder);
|
bool eventPlayerOnMoveItem(Player* player, Item* item, uint16_t count, const Position& fromPosition, const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder);
|
||||||
void eventPlayerOnItemMoved(Player* player, Item* item, uint16_t count, const Position& fromPosition, const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder);
|
void eventPlayerOnItemMoved(Player* player, Item* item, uint16_t count, const Position& fromPosition, const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder);
|
||||||
bool eventPlayerOnMoveCreature(Player* player, Creature* creature, const Position& fromPosition, const Position& toPosition);
|
bool eventPlayerOnMoveCreature(Player* player, Creature* creature, const Position& fromPosition, const Position& toPosition);
|
||||||
@ -79,6 +80,7 @@ private:
|
|||||||
int32_t playerOnLook;
|
int32_t playerOnLook;
|
||||||
int32_t playerOnLookInBattleList;
|
int32_t playerOnLookInBattleList;
|
||||||
int32_t playerOnLookInTrade;
|
int32_t playerOnLookInTrade;
|
||||||
|
int32_t playerOnLookInMarket;
|
||||||
int32_t playerOnMoveItem;
|
int32_t playerOnMoveItem;
|
||||||
int32_t playerOnItemMoved;
|
int32_t playerOnItemMoved;
|
||||||
int32_t playerOnMoveCreature;
|
int32_t playerOnMoveCreature;
|
||||||
|
471
src/game.cpp
471
src/game.cpp
@ -28,6 +28,7 @@
|
|||||||
#include "game.h"
|
#include "game.h"
|
||||||
#include "actions.h"
|
#include "actions.h"
|
||||||
#include "iologindata.h"
|
#include "iologindata.h"
|
||||||
|
#include "iomarket.h"
|
||||||
#include "talkaction.h"
|
#include "talkaction.h"
|
||||||
#include "spells.h"
|
#include "spells.h"
|
||||||
#include "configmanager.h"
|
#include "configmanager.h"
|
||||||
@ -37,6 +38,8 @@
|
|||||||
#include "scheduler.h"
|
#include "scheduler.h"
|
||||||
#include "databasetasks.h"
|
#include "databasetasks.h"
|
||||||
#include "movement.h"
|
#include "movement.h"
|
||||||
|
#include "inbox.h"
|
||||||
|
#include "depotchest.h"
|
||||||
|
|
||||||
extern ConfigManager g_config;
|
extern ConfigManager g_config;
|
||||||
extern Actions* g_actions;
|
extern Actions* g_actions;
|
||||||
@ -310,6 +313,7 @@ Thing* Game::internalGetThing(Player* player, const Position& pos, int32_t index
|
|||||||
//inventory
|
//inventory
|
||||||
slots_t slot = static_cast<slots_t>(pos.y);
|
slots_t slot = static_cast<slots_t>(pos.y);
|
||||||
return player->getInventoryItem(slot);
|
return player->getInventoryItem(slot);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Game::internalGetPosition(Item* item, Position& pos, uint8_t& stackpos)
|
void Game::internalGetPosition(Item* item, Position& pos, uint8_t& stackpos)
|
||||||
@ -4452,6 +4456,473 @@ void Game::playerDebugAssert(uint32_t playerId, const std::string& assertLine, c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Game::playerLeaveMarket(uint32_t playerId)
|
||||||
|
{
|
||||||
|
Player* player = getPlayerByID(playerId);
|
||||||
|
if (!player) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
player->setInMarket(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Game::playerBrowseMarket(uint32_t playerId, uint16_t spriteId)
|
||||||
|
{
|
||||||
|
Player* player = getPlayerByID(playerId);
|
||||||
|
if (!player) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!player->isInMarket()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ItemType& it = Item::items[spriteId];
|
||||||
|
if (it.id == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MarketOfferList& buyOffers = IOMarket::getActiveOffers(MARKETACTION_BUY, it.id);
|
||||||
|
const MarketOfferList& sellOffers = IOMarket::getActiveOffers(MARKETACTION_SELL, it.id);
|
||||||
|
player->sendMarketBrowseItem(it.id, buyOffers, sellOffers);
|
||||||
|
g_events->eventPlayerOnLookInMarket(player, &it);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Game::playerBrowseMarketOwnOffers(uint32_t playerId)
|
||||||
|
{
|
||||||
|
Player* player = getPlayerByID(playerId);
|
||||||
|
if (!player) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!player->isInMarket()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MarketOfferList& buyOffers = IOMarket::getOwnOffers(MARKETACTION_BUY, player->getGUID());
|
||||||
|
const MarketOfferList& sellOffers = IOMarket::getOwnOffers(MARKETACTION_SELL, player->getGUID());
|
||||||
|
player->sendMarketBrowseOwnOffers(buyOffers, sellOffers);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Game::playerBrowseMarketOwnHistory(uint32_t playerId)
|
||||||
|
{
|
||||||
|
Player* player = getPlayerByID(playerId);
|
||||||
|
if (!player) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!player->isInMarket()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const HistoryMarketOfferList& buyOffers = IOMarket::getOwnHistory(MARKETACTION_BUY, player->getGUID());
|
||||||
|
const HistoryMarketOfferList& sellOffers = IOMarket::getOwnHistory(MARKETACTION_SELL, player->getGUID());
|
||||||
|
player->sendMarketBrowseOwnHistory(buyOffers, sellOffers);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Game::playerCreateMarketOffer(uint32_t playerId, uint8_t type, uint16_t spriteId, uint16_t amount, uint64_t price, bool anonymous)
|
||||||
|
{
|
||||||
|
if (amount == 0 || amount > 64000) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (price == 0 || price > 999999999999) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type != MARKETACTION_BUY && type != MARKETACTION_SELL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player* player = getPlayerByID(playerId);
|
||||||
|
if (!player) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!player->isInMarket()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_config.getBoolean(ConfigManager::MARKET_PREMIUM) && !player->isPremium()) {
|
||||||
|
player->sendTextMessage(MESSAGE_STATUS_DEFAULT, "Only premium accounts may create offers for that object.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ItemType& it = Item::items[spriteId];
|
||||||
|
if (it.id == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!it.stackable && amount > 2000) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t maxOfferCount = g_config.getNumber(ConfigManager::MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER);
|
||||||
|
if (maxOfferCount != 0 && IOMarket::getPlayerOfferCount(player->getGUID()) >= maxOfferCount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t fee = (price / 100.) * amount;
|
||||||
|
if (fee < MIN_MARKET_FEE) {
|
||||||
|
fee = MIN_MARKET_FEE;
|
||||||
|
}
|
||||||
|
else if (fee > MAX_MARKET_FEE) {
|
||||||
|
fee = MAX_MARKET_FEE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == MARKETACTION_SELL) {
|
||||||
|
if (fee > (player->getMoney() + player->bankBalance)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DepotLocker* depotLocker = player->getDepotLocker(player->getLastDepotId(), false);
|
||||||
|
if (!depotLocker) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::forward_list<Item*> itemList = getMarketItemList(it.id, amount, depotLocker, player->getInbox());
|
||||||
|
if (itemList.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it.stackable) {
|
||||||
|
uint16_t tmpAmount = amount;
|
||||||
|
for (Item* item : itemList) {
|
||||||
|
uint16_t removeCount = std::min<uint16_t>(tmpAmount, item->getItemCount());
|
||||||
|
tmpAmount -= removeCount;
|
||||||
|
internalRemoveItem(item, removeCount);
|
||||||
|
|
||||||
|
if (tmpAmount == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (Item* item : itemList) {
|
||||||
|
internalRemoveItem(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto debitCash = std::min(player->getMoney(), fee);
|
||||||
|
const auto debitBank = fee - debitCash;
|
||||||
|
removeMoney(player, debitCash);
|
||||||
|
player->bankBalance -= debitBank;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
uint64_t totalPrice = static_cast<uint64_t>(price) * amount;
|
||||||
|
totalPrice += fee;
|
||||||
|
if (totalPrice > (player->getMoney() + player->bankBalance)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto debitCash = std::min(player->getMoney(), totalPrice);
|
||||||
|
const auto debitBank = totalPrice - debitCash;
|
||||||
|
removeMoney(player, debitCash);
|
||||||
|
player->bankBalance -= debitBank;
|
||||||
|
}
|
||||||
|
|
||||||
|
IOMarket::createOffer(player->getGUID(), static_cast<MarketAction_t>(type), it.id, amount, price, anonymous);
|
||||||
|
|
||||||
|
player->sendMarketEnter(player->getLastDepotId());
|
||||||
|
const MarketOfferList& buyOffers = IOMarket::getActiveOffers(MARKETACTION_BUY, it.id);
|
||||||
|
const MarketOfferList& sellOffers = IOMarket::getActiveOffers(MARKETACTION_SELL, it.id);
|
||||||
|
player->sendMarketBrowseItem(it.id, buyOffers, sellOffers);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Game::playerCancelMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter)
|
||||||
|
{
|
||||||
|
Player* player = getPlayerByID(playerId);
|
||||||
|
if (!player) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!player->isInMarket()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MarketOfferEx offer = IOMarket::getOfferByCounter(timestamp, counter);
|
||||||
|
if (offer.id == 0 || offer.playerId != player->getGUID()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offer.type == MARKETACTION_BUY) {
|
||||||
|
player->bankBalance += offer.price * offer.amount;
|
||||||
|
player->sendMarketEnter(player->getLastDepotId());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const ItemType& it = Item::items[offer.itemId];
|
||||||
|
if (it.id == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it.stackable) {
|
||||||
|
uint16_t tmpAmount = offer.amount;
|
||||||
|
while (tmpAmount > 0) {
|
||||||
|
int32_t stackCount = std::min<int32_t>(100, tmpAmount);
|
||||||
|
Item* item = Item::CreateItem(it.id, stackCount);
|
||||||
|
if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) {
|
||||||
|
delete item;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpAmount -= stackCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int32_t subType;
|
||||||
|
if (it.charges != 0) {
|
||||||
|
subType = it.charges;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
subType = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint16_t i = 0; i < offer.amount; ++i) {
|
||||||
|
Item* item = Item::CreateItem(it.id, subType);
|
||||||
|
if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) {
|
||||||
|
delete item;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IOMarket::moveOfferToHistory(offer.id, OFFERSTATE_CANCELLED);
|
||||||
|
offer.amount = 0;
|
||||||
|
offer.timestamp += g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION);
|
||||||
|
player->sendMarketCancelOffer(offer);
|
||||||
|
player->sendMarketEnter(player->getLastDepotId());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter, uint16_t amount)
|
||||||
|
{
|
||||||
|
if (amount == 0 || amount > 64000) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player* player = getPlayerByID(playerId);
|
||||||
|
if (!player) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!player->isInMarket()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MarketOfferEx offer = IOMarket::getOfferByCounter(timestamp, counter);
|
||||||
|
if (offer.id == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t offerAccountId = IOLoginData::getAccountIdByPlayerId(offer.playerId);
|
||||||
|
if (offerAccountId == player->getAccount()) {
|
||||||
|
player->sendTextMessage(MESSAGE_STATUS_DEFAULT, "You cannot accept your own offer.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amount > offer.amount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ItemType& it = Item::items[offer.itemId];
|
||||||
|
if (it.id == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t totalPrice = offer.price * amount;
|
||||||
|
|
||||||
|
if (offer.type == MARKETACTION_BUY) {
|
||||||
|
DepotLocker* depotLocker = player->getDepotLocker(player->getLastDepotId(), false);
|
||||||
|
if (!depotLocker) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::forward_list<Item*> itemList = getMarketItemList(it.id, amount, depotLocker, player->getInbox());
|
||||||
|
if (itemList.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player* buyerPlayer = getPlayerByGUID(offer.playerId);
|
||||||
|
if (!buyerPlayer) {
|
||||||
|
buyerPlayer = new Player(nullptr);
|
||||||
|
if (!IOLoginData::loadPlayerById(buyerPlayer, offer.playerId)) {
|
||||||
|
delete buyerPlayer;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it.stackable) {
|
||||||
|
uint16_t tmpAmount = amount;
|
||||||
|
for (Item* item : itemList) {
|
||||||
|
uint16_t removeCount = std::min<uint16_t>(tmpAmount, item->getItemCount());
|
||||||
|
tmpAmount -= removeCount;
|
||||||
|
internalRemoveItem(item, removeCount);
|
||||||
|
|
||||||
|
if (tmpAmount == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (Item* item : itemList) {
|
||||||
|
internalRemoveItem(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
player->bankBalance += totalPrice;
|
||||||
|
|
||||||
|
if (it.stackable) {
|
||||||
|
uint16_t tmpAmount = amount;
|
||||||
|
while (tmpAmount > 0) {
|
||||||
|
uint16_t stackCount = std::min<uint16_t>(100, tmpAmount);
|
||||||
|
Item* item = Item::CreateItem(it.id, stackCount);
|
||||||
|
if (internalAddItem(buyerPlayer->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) {
|
||||||
|
delete item;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpAmount -= stackCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int32_t subType;
|
||||||
|
if (it.charges != 0) {
|
||||||
|
subType = it.charges;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
subType = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint16_t i = 0; i < amount; ++i) {
|
||||||
|
Item* item = Item::CreateItem(it.id, subType);
|
||||||
|
if (internalAddItem(buyerPlayer->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) {
|
||||||
|
delete item;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buyerPlayer->isOffline()) {
|
||||||
|
IOLoginData::savePlayer(buyerPlayer);
|
||||||
|
delete buyerPlayer;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buyerPlayer->onReceiveMail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (totalPrice > (player->getMoney() + player->bankBalance)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto debitCash = std::min(player->getMoney(), totalPrice);
|
||||||
|
const auto debitBank = totalPrice - debitCash;
|
||||||
|
removeMoney(player, debitCash);
|
||||||
|
player->bankBalance -= debitBank;
|
||||||
|
|
||||||
|
if (it.stackable) {
|
||||||
|
uint16_t tmpAmount = amount;
|
||||||
|
while (tmpAmount > 0) {
|
||||||
|
uint16_t stackCount = std::min<uint16_t>(100, tmpAmount);
|
||||||
|
Item* item = Item::CreateItem(it.id, stackCount);
|
||||||
|
if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) {
|
||||||
|
delete item;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpAmount -= stackCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int32_t subType;
|
||||||
|
if (it.charges != 0) {
|
||||||
|
subType = it.charges;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
subType = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint16_t i = 0; i < amount; ++i) {
|
||||||
|
Item* item = Item::CreateItem(it.id, subType);
|
||||||
|
if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) {
|
||||||
|
delete item;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Player* sellerPlayer = getPlayerByGUID(offer.playerId);
|
||||||
|
if (sellerPlayer) {
|
||||||
|
sellerPlayer->bankBalance += totalPrice;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
IOLoginData::increaseBankBalance(offer.playerId, totalPrice);
|
||||||
|
}
|
||||||
|
|
||||||
|
player->onReceiveMail();
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32_t marketOfferDuration = g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION);
|
||||||
|
|
||||||
|
IOMarket::appendHistory(player->getGUID(), (offer.type == MARKETACTION_BUY ? MARKETACTION_SELL : MARKETACTION_BUY), offer.itemId, amount, offer.price, offer.timestamp + marketOfferDuration, OFFERSTATE_ACCEPTEDEX);
|
||||||
|
|
||||||
|
IOMarket::appendHistory(offer.playerId, offer.type, offer.itemId, amount, offer.price, offer.timestamp + marketOfferDuration, OFFERSTATE_ACCEPTED);
|
||||||
|
|
||||||
|
offer.amount -= amount;
|
||||||
|
|
||||||
|
if (offer.amount == 0) {
|
||||||
|
IOMarket::deleteOffer(offer.id);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
IOMarket::acceptOffer(offer.id, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
player->sendMarketEnter(player->getLastDepotId());
|
||||||
|
offer.timestamp += marketOfferDuration;
|
||||||
|
player->sendMarketAcceptOffer(offer);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::forward_list<Item*> Game::getMarketItemList(uint16_t wareId, uint16_t sufficientCount, DepotLocker* depotLocker, Inbox* inbox)
|
||||||
|
{
|
||||||
|
std::forward_list<Item*> itemList;
|
||||||
|
uint16_t count = 0;
|
||||||
|
|
||||||
|
std::list<Container*> containers{ depotLocker, inbox };
|
||||||
|
do {
|
||||||
|
Container* container = containers.front();
|
||||||
|
containers.pop_front();
|
||||||
|
|
||||||
|
for (Item* item : container->getItemList()) {
|
||||||
|
Container* c = item->getContainer();
|
||||||
|
if (c && !c->empty()) {
|
||||||
|
containers.push_back(c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ItemType& itemType = Item::items[item->getID()];
|
||||||
|
if (itemType.id != wareId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c && (!itemType.isContainer() || c->capacity() != itemType.maxItems)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!item->hasMarketAttributes()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
itemList.push_front(item);
|
||||||
|
|
||||||
|
count += Item::countByType(item, -1);
|
||||||
|
if (count >= sufficientCount) {
|
||||||
|
return itemList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (!containers.empty());
|
||||||
|
return std::forward_list<Item*>();
|
||||||
|
}
|
||||||
|
|
||||||
void Game::parsePlayerExtendedOpcode(uint32_t playerId, uint8_t opcode, const std::string& buffer)
|
void Game::parsePlayerExtendedOpcode(uint32_t playerId, uint8_t opcode, const std::string& buffer)
|
||||||
{
|
{
|
||||||
Player* player = getPlayerByID(playerId);
|
Player* player = getPlayerByID(playerId);
|
||||||
|
@ -406,6 +406,15 @@ class Game
|
|||||||
void playerContinueRuleViolationReport(Player* player, const std::string& text);
|
void playerContinueRuleViolationReport(Player* player, const std::string& text);
|
||||||
void parsePlayerExtendedOpcode(uint32_t playerId, uint8_t opcode, const std::string& buffer);
|
void parsePlayerExtendedOpcode(uint32_t playerId, uint8_t opcode, const std::string& buffer);
|
||||||
|
|
||||||
|
void playerLeaveMarket(uint32_t playerId);
|
||||||
|
void playerBrowseMarket(uint32_t playerId, uint16_t spriteId);
|
||||||
|
void playerBrowseMarketOwnOffers(uint32_t playerId);
|
||||||
|
void playerBrowseMarketOwnHistory(uint32_t playerId);
|
||||||
|
void playerCreateMarketOffer(uint32_t playerId, uint8_t type, uint16_t spriteId, uint16_t amount, uint64_t price, bool anonymous);
|
||||||
|
void playerCancelMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter);
|
||||||
|
void playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter, uint16_t amount);
|
||||||
|
std::forward_list<Item*> getMarketItemList(uint16_t wareId, uint16_t sufficientCount, DepotLocker* depotLocker, Inbox* inbox);
|
||||||
|
|
||||||
void closeRuleViolationReport(Player* player);
|
void closeRuleViolationReport(Player* player);
|
||||||
void cancelRuleViolationReport(Player* player);
|
void cancelRuleViolationReport(Player* player);
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
#include "game.h"
|
#include "game.h"
|
||||||
#include "configmanager.h"
|
#include "configmanager.h"
|
||||||
#include "bed.h"
|
#include "bed.h"
|
||||||
|
#include "inbox.h"
|
||||||
|
|
||||||
extern ConfigManager g_config;
|
extern ConfigManager g_config;
|
||||||
extern Game g_game;
|
extern Game g_game;
|
||||||
|
57
src/inbox.cpp
Normal file
57
src/inbox.cpp
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// Copyright 2022 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 "inbox.h"
|
||||||
|
|
||||||
|
#include "tools.h"
|
||||||
|
|
||||||
|
Inbox::Inbox(uint16_t type) : Container(type, 30) {}
|
||||||
|
|
||||||
|
ReturnValue Inbox::queryAdd(int32_t, const Thing& thing, uint32_t,
|
||||||
|
uint32_t flags, Creature*) const
|
||||||
|
{
|
||||||
|
if (!hasBitSet(FLAG_NOLIMIT, flags)) {
|
||||||
|
return RETURNVALUE_CONTAINERNOTENOUGHROOM;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Item* item = thing.getItem();
|
||||||
|
if (!item) {
|
||||||
|
return RETURNVALUE_NOTPOSSIBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item == this) {
|
||||||
|
return RETURNVALUE_THISISIMPOSSIBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!item->isPickupable()) {
|
||||||
|
return RETURNVALUE_CANNOTPICKUP;
|
||||||
|
}
|
||||||
|
|
||||||
|
return RETURNVALUE_NOERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Inbox::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t)
|
||||||
|
{
|
||||||
|
Cylinder* parent = getParent();
|
||||||
|
if (parent) {
|
||||||
|
parent->postAddNotification(thing, oldParent, index, LINK_PARENT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Inbox::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t)
|
||||||
|
{
|
||||||
|
Cylinder* parent = getParent();
|
||||||
|
if (parent) {
|
||||||
|
parent->postRemoveNotification(thing, newParent, index, LINK_PARENT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Cylinder* Inbox::getParent() const
|
||||||
|
{
|
||||||
|
if (parent) {
|
||||||
|
return parent->getParent();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
32
src/inbox.h
Normal file
32
src/inbox.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Copyright 2022 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_INBOX_H
|
||||||
|
#define FS_INBOX_H
|
||||||
|
|
||||||
|
#include "container.h"
|
||||||
|
|
||||||
|
class Inbox final : public Container
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit Inbox(uint16_t type);
|
||||||
|
|
||||||
|
//cylinder implementations
|
||||||
|
ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count,
|
||||||
|
uint32_t flags, Creature* actor = nullptr) const override;
|
||||||
|
|
||||||
|
void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) override;
|
||||||
|
void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) override;
|
||||||
|
|
||||||
|
//overrides
|
||||||
|
bool canRemove() const override {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Cylinder* getParent() const override;
|
||||||
|
Cylinder* getRealParent() const override {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FS_INBOX_H
|
@ -22,6 +22,9 @@
|
|||||||
#include "iologindata.h"
|
#include "iologindata.h"
|
||||||
#include "configmanager.h"
|
#include "configmanager.h"
|
||||||
#include "game.h"
|
#include "game.h"
|
||||||
|
#include "depotchest.h"
|
||||||
|
#include "inbox.h"
|
||||||
|
#include <fmt/core.h>
|
||||||
|
|
||||||
extern ConfigManager g_config;
|
extern ConfigManager g_config;
|
||||||
extern Game g_game;
|
extern Game g_game;
|
||||||
@ -480,7 +483,8 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
ItemMap::const_iterator it2 = itemMap.find(pid);
|
ItemMap::const_iterator it2 = itemMap.find(pid);
|
||||||
if (it2 == itemMap.end()) {
|
if (it2 == itemMap.end()) {
|
||||||
continue;
|
continue;
|
||||||
@ -494,6 +498,35 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//load inbox items
|
||||||
|
itemMap.clear();
|
||||||
|
|
||||||
|
if ((result = db->storeQuery(fmt::format("SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_inboxitems` WHERE `player_id` = {:d} ORDER BY `sid` DESC", player->getGUID())))) {
|
||||||
|
loadItems(itemMap, result);
|
||||||
|
|
||||||
|
for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) {
|
||||||
|
const std::pair<Item*, int32_t>& pair = it->second;
|
||||||
|
Item* item = pair.first;
|
||||||
|
int32_t pid = pair.second;
|
||||||
|
|
||||||
|
if (pid >= 0 && pid < 100) {
|
||||||
|
player->getInbox()->internalAddThing(item);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ItemMap::const_iterator it2 = itemMap.find(pid);
|
||||||
|
|
||||||
|
if (it2 == itemMap.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Container* container = it2->second.first->getContainer();
|
||||||
|
if (container) {
|
||||||
|
container->internalAddThing(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//load storage map
|
//load storage map
|
||||||
query.str(std::string());
|
query.str(std::string());
|
||||||
query << "SELECT `key`, `value` FROM `player_storage` WHERE `player_id` = " << player->getGUID();
|
query << "SELECT `key`, `value` FROM `player_storage` WHERE `player_id` = " << player->getGUID();
|
||||||
@ -761,22 +794,37 @@ bool IOLoginData::savePlayer(Player* player)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//save depot items
|
if (player->lastDepotId != -1) {
|
||||||
query.str(std::string());
|
//save depot items
|
||||||
query << "DELETE FROM `player_depotitems` WHERE `player_id` = " << player->getGUID();
|
if (!db->executeQuery(fmt::format("DELETE FROM `player_depotitems` WHERE `player_id` = {:d}", player->getGUID()))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!db->executeQuery(query.str())) {
|
DBInsert depotQuery("INSERT INTO `player_depotitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES ");
|
||||||
|
itemList.clear();
|
||||||
|
|
||||||
|
for (const auto& it : player->depotLockerMap) {
|
||||||
|
itemList.emplace_back(it.first, it.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!saveItems(player, itemList, depotQuery, propWriteStream)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//save inbox items
|
||||||
|
if (!db->executeQuery(fmt::format("DELETE FROM `player_inboxitems` WHERE `player_id` = {:d}", player->getGUID()))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
DBInsert depotQuery("INSERT INTO `player_depotitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES ");
|
DBInsert inboxQuery("INSERT INTO `player_inboxitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES ");
|
||||||
itemList.clear();
|
itemList.clear();
|
||||||
|
|
||||||
for (const auto& it : player->depotLockerMap) {
|
for (Item* item : player->getInbox()->getItemList()) {
|
||||||
itemList.emplace_back(it.first, it.second);
|
itemList.emplace_back(0, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!saveItems(player, itemList, depotQuery, propWriteStream)) {
|
if (!saveItems(player, itemList, inboxQuery, propWriteStream)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -985,3 +1033,14 @@ void IOLoginData::removePremiumDays(uint32_t accountId, int32_t removeDays)
|
|||||||
query << "UPDATE `accounts` SET `premdays` = `premdays` - " << removeDays << " WHERE `id` = " << accountId;
|
query << "UPDATE `accounts` SET `premdays` = `premdays` - " << removeDays << " WHERE `id` = " << accountId;
|
||||||
Database::getInstance()->executeQuery(query.str());
|
Database::getInstance()->executeQuery(query.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t IOLoginData::getAccountIdByPlayerId(uint32_t playerId)
|
||||||
|
{
|
||||||
|
Database* db = Database::getInstance();
|
||||||
|
|
||||||
|
DBResult_ptr result = db->storeQuery(fmt::format("SELECT `account_id` FROM `players` WHERE `id` = {:d}", playerId));
|
||||||
|
if (!result) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return result->getNumber<uint32_t>("account_id");
|
||||||
|
}
|
@ -59,6 +59,7 @@ class IOLoginData
|
|||||||
|
|
||||||
static void addPremiumDays(uint32_t accountId, int32_t addDays);
|
static void addPremiumDays(uint32_t accountId, int32_t addDays);
|
||||||
static void removePremiumDays(uint32_t accountId, int32_t removeDays);
|
static void removePremiumDays(uint32_t accountId, int32_t removeDays);
|
||||||
|
static uint32_t getAccountIdByPlayerId(uint32_t playerId);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
typedef std::map<uint32_t, std::pair<Item*, uint32_t>> ItemMap;
|
typedef std::map<uint32_t, std::pair<Item*, uint32_t>> ItemMap;
|
||||||
|
307
src/iomarket.cpp
Normal file
307
src/iomarket.cpp
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
// Copyright 2022 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 "iomarket.h"
|
||||||
|
|
||||||
|
#include "configmanager.h"
|
||||||
|
#include "databasetasks.h"
|
||||||
|
#include "game.h"
|
||||||
|
#include "inbox.h"
|
||||||
|
#include "iologindata.h"
|
||||||
|
#include "scheduler.h"
|
||||||
|
#include <fmt/core.h>
|
||||||
|
|
||||||
|
extern ConfigManager g_config;
|
||||||
|
extern Game g_game;
|
||||||
|
|
||||||
|
MarketOfferList IOMarket::getActiveOffers(MarketAction_t action, uint16_t itemId)
|
||||||
|
{
|
||||||
|
MarketOfferList offerList;
|
||||||
|
|
||||||
|
DBResult_ptr result = Database::getInstance()->storeQuery(fmt::format("SELECT `id`, `amount`, `price`, `created`, `anonymous`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `player_name` FROM `market_offers` WHERE `sale` = {:d} AND `itemtype` = {:d}", action, itemId));
|
||||||
|
if (!result) {
|
||||||
|
return offerList;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32_t marketOfferDuration = g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION);
|
||||||
|
|
||||||
|
do {
|
||||||
|
MarketOffer offer;
|
||||||
|
offer.amount = result->getNumber<uint16_t>("amount");
|
||||||
|
offer.price = result->getNumber<uint64_t>("price");
|
||||||
|
offer.timestamp = result->getNumber<uint32_t>("created") + marketOfferDuration;
|
||||||
|
offer.counter = result->getNumber<uint32_t>("id") & 0xFFFF;
|
||||||
|
if (result->getNumber<uint16_t>("anonymous") == 0) {
|
||||||
|
offer.playerName = result->getString("player_name");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
offer.playerName = "Anonymous";
|
||||||
|
}
|
||||||
|
offerList.push_back(offer);
|
||||||
|
} while (result->next());
|
||||||
|
return offerList;
|
||||||
|
}
|
||||||
|
|
||||||
|
MarketOfferList IOMarket::getOwnOffers(MarketAction_t action, uint32_t playerId)
|
||||||
|
{
|
||||||
|
MarketOfferList offerList;
|
||||||
|
|
||||||
|
const int32_t marketOfferDuration = g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION);
|
||||||
|
|
||||||
|
DBResult_ptr result = Database::getInstance()->storeQuery(fmt::format("SELECT `id`, `amount`, `price`, `created`, `itemtype` FROM `market_offers` WHERE `player_id` = {:d} AND `sale` = {:d}", playerId, action));
|
||||||
|
if (!result) {
|
||||||
|
return offerList;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
MarketOffer offer;
|
||||||
|
offer.amount = result->getNumber<uint16_t>("amount");
|
||||||
|
offer.price = result->getNumber<uint64_t>("price");
|
||||||
|
offer.timestamp = result->getNumber<uint32_t>("created") + marketOfferDuration;
|
||||||
|
offer.counter = result->getNumber<uint32_t>("id") & 0xFFFF;
|
||||||
|
offer.itemId = result->getNumber<uint16_t>("itemtype");
|
||||||
|
offerList.push_back(offer);
|
||||||
|
} while (result->next());
|
||||||
|
return offerList;
|
||||||
|
}
|
||||||
|
|
||||||
|
HistoryMarketOfferList IOMarket::getOwnHistory(MarketAction_t action, uint32_t playerId)
|
||||||
|
{
|
||||||
|
HistoryMarketOfferList offerList;
|
||||||
|
|
||||||
|
DBResult_ptr result = Database::getInstance()->storeQuery(fmt::format("SELECT `itemtype`, `amount`, `price`, `expires_at`, `state` FROM `market_history` WHERE `player_id` = {:d} AND `sale` = {:d}", playerId, action));
|
||||||
|
if (!result) {
|
||||||
|
return offerList;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
HistoryMarketOffer offer;
|
||||||
|
offer.itemId = result->getNumber<uint16_t>("itemtype");
|
||||||
|
offer.amount = result->getNumber<uint16_t>("amount");
|
||||||
|
offer.price = result->getNumber<uint64_t>("price");
|
||||||
|
offer.timestamp = result->getNumber<uint32_t>("expires_at");
|
||||||
|
|
||||||
|
MarketOfferState_t offerState = static_cast<MarketOfferState_t>(result->getNumber<uint16_t>("state"));
|
||||||
|
if (offerState == OFFERSTATE_ACCEPTEDEX) {
|
||||||
|
offerState = OFFERSTATE_ACCEPTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
offer.state = offerState;
|
||||||
|
|
||||||
|
offerList.push_back(offer);
|
||||||
|
} while (result->next());
|
||||||
|
return offerList;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IOMarket::processExpiredOffers(DBResult_ptr result, bool)
|
||||||
|
{
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (!IOMarket::moveOfferToHistory(result->getNumber<uint32_t>("id"), OFFERSTATE_EXPIRED)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t playerId = result->getNumber<uint32_t>("player_id");
|
||||||
|
const uint16_t amount = result->getNumber<uint16_t>("amount");
|
||||||
|
if (result->getNumber<uint16_t>("sale") == 1) {
|
||||||
|
const ItemType& itemType = Item::items[result->getNumber<uint16_t>("itemtype")];
|
||||||
|
if (itemType.id == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player* player = g_game.getPlayerByGUID(playerId);
|
||||||
|
if (!player) {
|
||||||
|
player = new Player(nullptr);
|
||||||
|
if (!IOLoginData::loadPlayerById(player, playerId)) {
|
||||||
|
delete player;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemType.stackable) {
|
||||||
|
uint16_t tmpAmount = amount;
|
||||||
|
while (tmpAmount > 0) {
|
||||||
|
uint16_t stackCount = std::min<uint16_t>(100, tmpAmount);
|
||||||
|
Item* item = Item::CreateItem(itemType.id, stackCount);
|
||||||
|
if (g_game.internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) {
|
||||||
|
delete item;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpAmount -= stackCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int32_t subType;
|
||||||
|
if (itemType.charges != 0) {
|
||||||
|
subType = itemType.charges;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
subType = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint16_t i = 0; i < amount; ++i) {
|
||||||
|
Item* item = Item::CreateItem(itemType.id, subType);
|
||||||
|
if (g_game.internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) {
|
||||||
|
delete item;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player->isOffline()) {
|
||||||
|
IOLoginData::savePlayer(player);
|
||||||
|
delete player;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
uint64_t totalPrice = result->getNumber<uint64_t>("price") * amount;
|
||||||
|
|
||||||
|
Player* player = g_game.getPlayerByGUID(playerId);
|
||||||
|
if (player) {
|
||||||
|
player->setBankBalance(player->getBankBalance() + totalPrice);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
IOLoginData::increaseBankBalance(playerId, totalPrice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (result->next());
|
||||||
|
}
|
||||||
|
|
||||||
|
void IOMarket::checkExpiredOffers()
|
||||||
|
{
|
||||||
|
const time_t lastExpireDate = time(nullptr) - g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION);
|
||||||
|
|
||||||
|
g_databaseTasks.addTask(fmt::format("SELECT `id`, `amount`, `price`, `itemtype`, `player_id`, `sale` FROM `market_offers` WHERE `created` <= {:d}", lastExpireDate), IOMarket::processExpiredOffers, true);
|
||||||
|
|
||||||
|
int32_t checkExpiredMarketOffersEachMinutes = g_config.getNumber(ConfigManager::CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES);
|
||||||
|
if (checkExpiredMarketOffersEachMinutes <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_scheduler.addEvent(createSchedulerTask(checkExpiredMarketOffersEachMinutes * 60 * 1000, &IOMarket::checkExpiredOffers));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t IOMarket::getPlayerOfferCount(uint32_t playerId)
|
||||||
|
{
|
||||||
|
DBResult_ptr result = Database::getInstance()->storeQuery(fmt::format("SELECT COUNT(*) AS `count` FROM `market_offers` WHERE `player_id` = {:d}", playerId));
|
||||||
|
if (!result) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return result->getNumber<int32_t>("count");
|
||||||
|
}
|
||||||
|
|
||||||
|
MarketOfferEx IOMarket::getOfferByCounter(uint32_t timestamp, uint16_t counter)
|
||||||
|
{
|
||||||
|
MarketOfferEx offer;
|
||||||
|
|
||||||
|
const int32_t created = timestamp - g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION);
|
||||||
|
|
||||||
|
DBResult_ptr result = Database::getInstance()->storeQuery(fmt::format("SELECT `id`, `sale`, `itemtype`, `amount`, `created`, `price`, `player_id`, `anonymous`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `player_name` FROM `market_offers` WHERE `created` = {:d} AND (`id` & 65535) = {:d} LIMIT 1", created, counter));
|
||||||
|
if (!result) {
|
||||||
|
offer.id = 0;
|
||||||
|
offer.playerId = 0;
|
||||||
|
return offer;
|
||||||
|
}
|
||||||
|
|
||||||
|
offer.id = result->getNumber<uint32_t>("id");
|
||||||
|
offer.type = static_cast<MarketAction_t>(result->getNumber<uint16_t>("sale"));
|
||||||
|
offer.amount = result->getNumber<uint16_t>("amount");
|
||||||
|
offer.counter = result->getNumber<uint32_t>("id") & 0xFFFF;
|
||||||
|
offer.timestamp = result->getNumber<uint32_t>("created");
|
||||||
|
offer.price = result->getNumber<uint64_t>("price");
|
||||||
|
offer.itemId = result->getNumber<uint16_t>("itemtype");
|
||||||
|
offer.playerId = result->getNumber<uint32_t>("player_id");
|
||||||
|
if (result->getNumber<uint16_t>("anonymous") == 0) {
|
||||||
|
offer.playerName = result->getString("player_name");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
offer.playerName = "Anonymous";
|
||||||
|
}
|
||||||
|
return offer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IOMarket::createOffer(uint32_t playerId, MarketAction_t action, uint32_t itemId, uint16_t amount, uint64_t price, bool anonymous)
|
||||||
|
{
|
||||||
|
Database::getInstance()->executeQuery(fmt::format("INSERT INTO `market_offers` (`player_id`, `sale`, `itemtype`, `amount`, `price`, `created`, `anonymous`) VALUES ({:d}, {:d}, {:d}, {:d}, {:d}, {:d}, {:d})", playerId, action, itemId, amount, price, time(nullptr), anonymous));
|
||||||
|
}
|
||||||
|
|
||||||
|
void IOMarket::acceptOffer(uint32_t offerId, uint16_t amount)
|
||||||
|
{
|
||||||
|
Database::getInstance()->executeQuery(fmt::format("UPDATE `market_offers` SET `amount` = `amount` - {:d} WHERE `id` = {:d}", amount, offerId));
|
||||||
|
}
|
||||||
|
|
||||||
|
void IOMarket::deleteOffer(uint32_t offerId)
|
||||||
|
{
|
||||||
|
Database::getInstance()->executeQuery(fmt::format("DELETE FROM `market_offers` WHERE `id` = {:d}", offerId));
|
||||||
|
}
|
||||||
|
|
||||||
|
void IOMarket::appendHistory(uint32_t playerId, MarketAction_t type, uint16_t itemId, uint16_t amount, uint64_t price, time_t timestamp, MarketOfferState_t state)
|
||||||
|
{
|
||||||
|
g_databaseTasks.addTask(fmt::format("INSERT INTO `market_history` (`player_id`, `sale`, `itemtype`, `amount`, `price`, `expires_at`, `inserted`, `state`) VALUES ({:d}, {:d}, {:d}, {:d}, {:d}, {:d}, {:d}, {:d})", playerId, type, itemId, amount, price, timestamp, time(nullptr), state));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IOMarket::moveOfferToHistory(uint32_t offerId, MarketOfferState_t state)
|
||||||
|
{
|
||||||
|
const int32_t marketOfferDuration = g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION);
|
||||||
|
|
||||||
|
Database* db = Database::getInstance();
|
||||||
|
|
||||||
|
DBResult_ptr result = db->storeQuery(fmt::format("SELECT `player_id`, `sale`, `itemtype`, `amount`, `price`, `created` FROM `market_offers` WHERE `id` = {:d}", offerId));
|
||||||
|
if (!result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!db->executeQuery(fmt::format("DELETE FROM `market_offers` WHERE `id` = {:d}", offerId))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
appendHistory(result->getNumber<uint32_t>("player_id"), static_cast<MarketAction_t>(result->getNumber<uint16_t>("sale")), result->getNumber<uint16_t>("itemtype"), result->getNumber<uint16_t>("amount"), result->getNumber<uint64_t>("price"), result->getNumber<uint32_t>("created") + marketOfferDuration, state);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IOMarket::updateStatistics()
|
||||||
|
{
|
||||||
|
DBResult_ptr result = Database::getInstance()->storeQuery(fmt::format("SELECT `sale` AS `sale`, `itemtype` AS `itemtype`, COUNT(`price`) AS `num`, MIN(`price`) AS `min`, MAX(`price`) AS `max`, SUM(`price`) AS `sum` FROM `market_history` WHERE `state` = {:d} GROUP BY `itemtype`, `sale`", OFFERSTATE_ACCEPTED));
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
MarketStatistics* statistics;
|
||||||
|
if (result->getNumber<uint16_t>("sale") == MARKETACTION_BUY) {
|
||||||
|
statistics = &purchaseStatistics[result->getNumber<uint16_t>("itemtype")];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
statistics = &saleStatistics[result->getNumber<uint16_t>("itemtype")];
|
||||||
|
}
|
||||||
|
|
||||||
|
statistics->numTransactions = result->getNumber<uint32_t>("num");
|
||||||
|
statistics->lowestPrice = result->getNumber<uint64_t>("min");
|
||||||
|
statistics->totalPrice = result->getNumber<uint64_t>("sum");
|
||||||
|
statistics->highestPrice = result->getNumber<uint64_t>("max");
|
||||||
|
} while (result->next());
|
||||||
|
}
|
||||||
|
|
||||||
|
MarketStatistics* IOMarket::getPurchaseStatistics(uint16_t itemId)
|
||||||
|
{
|
||||||
|
auto it = purchaseStatistics.find(itemId);
|
||||||
|
if (it == purchaseStatistics.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return &it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
MarketStatistics* IOMarket::getSaleStatistics(uint16_t itemId)
|
||||||
|
{
|
||||||
|
auto it = saleStatistics.find(itemId);
|
||||||
|
if (it == saleStatistics.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return &it->second;
|
||||||
|
}
|
47
src/iomarket.h
Normal file
47
src/iomarket.h
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// Copyright 2022 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_IOMARKET_H
|
||||||
|
#define FS_IOMARKET_H
|
||||||
|
|
||||||
|
#include "database.h"
|
||||||
|
#include "enums.h"
|
||||||
|
|
||||||
|
class IOMarket
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static IOMarket& getInstance() {
|
||||||
|
static IOMarket instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MarketOfferList getActiveOffers(MarketAction_t action, uint16_t itemId);
|
||||||
|
static MarketOfferList getOwnOffers(MarketAction_t action, uint32_t playerId);
|
||||||
|
static HistoryMarketOfferList getOwnHistory(MarketAction_t action, uint32_t playerId);
|
||||||
|
|
||||||
|
static void processExpiredOffers(DBResult_ptr result, bool);
|
||||||
|
static void checkExpiredOffers();
|
||||||
|
|
||||||
|
static uint32_t getPlayerOfferCount(uint32_t playerId);
|
||||||
|
static MarketOfferEx getOfferByCounter(uint32_t timestamp, uint16_t counter);
|
||||||
|
|
||||||
|
static void createOffer(uint32_t playerId, MarketAction_t action, uint32_t itemId, uint16_t amount, uint64_t price, bool anonymous);
|
||||||
|
static void acceptOffer(uint32_t offerId, uint16_t amount);
|
||||||
|
static void deleteOffer(uint32_t offerId);
|
||||||
|
|
||||||
|
static void appendHistory(uint32_t playerId, MarketAction_t type, uint16_t itemId, uint16_t amount, uint64_t price, time_t timestamp, MarketOfferState_t state);
|
||||||
|
static bool moveOfferToHistory(uint32_t offerId, MarketOfferState_t state);
|
||||||
|
|
||||||
|
void updateStatistics();
|
||||||
|
|
||||||
|
MarketStatistics* getPurchaseStatistics(uint16_t itemId);
|
||||||
|
MarketStatistics* getSaleStatistics(uint16_t itemId);
|
||||||
|
|
||||||
|
private:
|
||||||
|
IOMarket() = default;
|
||||||
|
|
||||||
|
std::map<uint16_t, MarketStatistics> purchaseStatistics;
|
||||||
|
std::map<uint16_t, MarketStatistics> saleStatistics;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FS_IOMARKET_H
|
41
src/item.cpp
41
src/item.cpp
@ -1302,3 +1302,44 @@ void Item::startDecaying()
|
|||||||
{
|
{
|
||||||
g_game.startDecay(this);
|
g_game.startDecay(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Item::hasMarketAttributes() const
|
||||||
|
{
|
||||||
|
if (!attributes) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// discard items with custom boost and reflect
|
||||||
|
/*
|
||||||
|
TODO: find out if necessary
|
||||||
|
for (uint16_t i = 0; i < COMBAT_COUNT; ++i) {
|
||||||
|
if (getBoostPercent(indexToCombatType(i), false) > 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Reflect tmpReflect = getReflect(indexToCombatType(i), false);
|
||||||
|
if (tmpReflect.chance != 0 || tmpReflect.percent != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// discard items with other modified attributes
|
||||||
|
for (const auto& attr : attributes->getList()) {
|
||||||
|
if (attr.type == ITEM_ATTRIBUTE_CHARGES) {
|
||||||
|
uint16_t charges = static_cast<uint16_t>(attr.value.integer);
|
||||||
|
if (charges != items[id].charges) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (attr.type == ITEM_ATTRIBUTE_DURATION) {
|
||||||
|
uint32_t duration = static_cast<uint32_t>(attr.value.integer);
|
||||||
|
if (duration != getDefaultDuration()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
@ -776,6 +776,8 @@ class Item : virtual public Thing
|
|||||||
|
|
||||||
virtual void startDecaying();
|
virtual void startDecaying();
|
||||||
|
|
||||||
|
bool hasMarketAttributes() const;
|
||||||
|
|
||||||
void setLoadedFromMap(bool value) {
|
void setLoadedFromMap(bool value) {
|
||||||
loadedFromMap = value;
|
loadedFromMap = value;
|
||||||
}
|
}
|
||||||
|
@ -258,6 +258,7 @@ class ItemType
|
|||||||
bool useEvent = false;
|
bool useEvent = false;
|
||||||
bool multiUseEvent = false;
|
bool multiUseEvent = false;
|
||||||
bool distUse = false;
|
bool distUse = false;
|
||||||
|
bool storeItem = false;
|
||||||
bool disguise = false;
|
bool disguise = false;
|
||||||
bool forceUse = false;
|
bool forceUse = false;
|
||||||
bool changeUse = false;
|
bool changeUse = false;
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
#include "protocolstatus.h"
|
#include "protocolstatus.h"
|
||||||
#include "spells.h"
|
#include "spells.h"
|
||||||
#include "iologindata.h"
|
#include "iologindata.h"
|
||||||
|
#include "iomarket.h"
|
||||||
#include "configmanager.h"
|
#include "configmanager.h"
|
||||||
#include "teleport.h"
|
#include "teleport.h"
|
||||||
#include "databasemanager.h"
|
#include "databasemanager.h"
|
||||||
@ -35,6 +36,8 @@
|
|||||||
#include "monster.h"
|
#include "monster.h"
|
||||||
#include "scheduler.h"
|
#include "scheduler.h"
|
||||||
#include "databasetasks.h"
|
#include "databasetasks.h"
|
||||||
|
#include "inbox.h"
|
||||||
|
#include "depotchest.h"
|
||||||
|
|
||||||
extern Chat* g_chat;
|
extern Chat* g_chat;
|
||||||
extern Game g_game;
|
extern Game g_game;
|
||||||
@ -1629,6 +1632,7 @@ void LuaScriptInterface::registerFunctions()
|
|||||||
registerEnumIn("configKeys", ConfigManager::ALLOW_CLONES)
|
registerEnumIn("configKeys", ConfigManager::ALLOW_CLONES)
|
||||||
registerEnumIn("configKeys", ConfigManager::BIND_ONLY_GLOBAL_ADDRESS)
|
registerEnumIn("configKeys", ConfigManager::BIND_ONLY_GLOBAL_ADDRESS)
|
||||||
registerEnumIn("configKeys", ConfigManager::OPTIMIZE_DATABASE)
|
registerEnumIn("configKeys", ConfigManager::OPTIMIZE_DATABASE)
|
||||||
|
registerEnumIn("configKeys", ConfigManager::MARKET_PREMIUM)
|
||||||
registerEnumIn("configKeys", ConfigManager::STAMINA_SYSTEM)
|
registerEnumIn("configKeys", ConfigManager::STAMINA_SYSTEM)
|
||||||
registerEnumIn("configKeys", ConfigManager::WARN_UNSAFE_SCRIPTS)
|
registerEnumIn("configKeys", ConfigManager::WARN_UNSAFE_SCRIPTS)
|
||||||
registerEnumIn("configKeys", ConfigManager::CONVERT_UNSAFE_SCRIPTS)
|
registerEnumIn("configKeys", ConfigManager::CONVERT_UNSAFE_SCRIPTS)
|
||||||
@ -1685,6 +1689,9 @@ void LuaScriptInterface::registerFunctions()
|
|||||||
registerEnumIn("configKeys", ConfigManager::LOGIN_PORT)
|
registerEnumIn("configKeys", ConfigManager::LOGIN_PORT)
|
||||||
registerEnumIn("configKeys", ConfigManager::STATUS_PORT)
|
registerEnumIn("configKeys", ConfigManager::STATUS_PORT)
|
||||||
registerEnumIn("configKeys", ConfigManager::STAIRHOP_DELAY)
|
registerEnumIn("configKeys", ConfigManager::STAIRHOP_DELAY)
|
||||||
|
registerEnumIn("configKeys", ConfigManager::MARKET_OFFER_DURATION)
|
||||||
|
registerEnumIn("configKeys", ConfigManager::CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES)
|
||||||
|
registerEnumIn("configKeys", ConfigManager::MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER)
|
||||||
registerEnumIn("configKeys", ConfigManager::EXP_FROM_PLAYERS_LEVEL_RANGE)
|
registerEnumIn("configKeys", ConfigManager::EXP_FROM_PLAYERS_LEVEL_RANGE)
|
||||||
registerEnumIn("configKeys", ConfigManager::MAX_PACKETS_PER_SECOND)
|
registerEnumIn("configKeys", ConfigManager::MAX_PACKETS_PER_SECOND)
|
||||||
registerEnumIn("configKeys", ConfigManager::NEWBIE_TOWN)
|
registerEnumIn("configKeys", ConfigManager::NEWBIE_TOWN)
|
||||||
@ -1989,6 +1996,7 @@ void LuaScriptInterface::registerFunctions()
|
|||||||
registerMethod("Player", "getFreeCapacity", LuaScriptInterface::luaPlayerGetFreeCapacity);
|
registerMethod("Player", "getFreeCapacity", LuaScriptInterface::luaPlayerGetFreeCapacity);
|
||||||
|
|
||||||
registerMethod("Player", "getDepotChest", LuaScriptInterface::luaPlayerGetDepotChest);
|
registerMethod("Player", "getDepotChest", LuaScriptInterface::luaPlayerGetDepotChest);
|
||||||
|
registerMethod("Player", "getInbox", LuaScriptInterface::luaPlayerGetInbox);
|
||||||
|
|
||||||
registerMethod("Player", "getMurderTimestamps", LuaScriptInterface::luaPlayerGetMurderTimestamps);
|
registerMethod("Player", "getMurderTimestamps", LuaScriptInterface::luaPlayerGetMurderTimestamps);
|
||||||
registerMethod("Player", "getPlayerKillerEnd", LuaScriptInterface::luaPlayerGetPlayerKillerEnd);
|
registerMethod("Player", "getPlayerKillerEnd", LuaScriptInterface::luaPlayerGetPlayerKillerEnd);
|
||||||
@ -2300,6 +2308,9 @@ void LuaScriptInterface::registerFunctions()
|
|||||||
registerMethod("ItemType", "getNutrition", LuaScriptInterface::luaItemTypeGetNutrition);
|
registerMethod("ItemType", "getNutrition", LuaScriptInterface::luaItemTypeGetNutrition);
|
||||||
registerMethod("ItemType", "getRequiredLevel", LuaScriptInterface::luaItemTypeGetRequiredLevel);
|
registerMethod("ItemType", "getRequiredLevel", LuaScriptInterface::luaItemTypeGetRequiredLevel);
|
||||||
|
|
||||||
|
registerMethod("ItemType", "getMarketBuyStatistics", LuaScriptInterface::luaItemTypeGetMarketBuyStatistics);
|
||||||
|
registerMethod("ItemType", "getMarketSellStatistics", LuaScriptInterface::luaItemTypeGetMarketSellStatistics);
|
||||||
|
|
||||||
registerMethod("ItemType", "hasSubType", LuaScriptInterface::luaItemTypeHasSubType);
|
registerMethod("ItemType", "hasSubType", LuaScriptInterface::luaItemTypeHasSubType);
|
||||||
|
|
||||||
// Combat
|
// Combat
|
||||||
@ -7276,6 +7287,26 @@ int LuaScriptInterface::luaPlayerGetDepotChest(lua_State* L)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int LuaScriptInterface::luaPlayerGetInbox(lua_State* L)
|
||||||
|
{
|
||||||
|
// player:getInbox()
|
||||||
|
Player* player = getUserdata<Player>(L, 1);
|
||||||
|
if (!player) {
|
||||||
|
lua_pushnil(L);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Inbox* inbox = player->getInbox();
|
||||||
|
if (inbox) {
|
||||||
|
pushUserdata<Item>(L, inbox);
|
||||||
|
setItemMetatable(L, -1, inbox);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pushBoolean(L, false);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
int LuaScriptInterface::luaPlayerGetMurderTimestamps(lua_State * L)
|
int LuaScriptInterface::luaPlayerGetMurderTimestamps(lua_State * L)
|
||||||
{
|
{
|
||||||
// player:getMurderTimestamps()
|
// player:getMurderTimestamps()
|
||||||
@ -10656,6 +10687,52 @@ int LuaScriptInterface::luaItemTypeGetRequiredLevel(lua_State* L)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int LuaScriptInterface::luaItemTypeGetMarketBuyStatistics(lua_State* L)
|
||||||
|
{
|
||||||
|
// itemType:getMarketBuyStatistics()
|
||||||
|
const ItemType* itemType = getUserdata<const ItemType>(L, 1);
|
||||||
|
if (itemType) {
|
||||||
|
MarketStatistics* statistics = IOMarket::getInstance().getPurchaseStatistics(itemType->id);
|
||||||
|
if (statistics) {
|
||||||
|
lua_createtable(L, 4, 0);
|
||||||
|
setField(L, "numTransactions", statistics->numTransactions);
|
||||||
|
setField(L, "totalPrice", statistics->totalPrice);
|
||||||
|
setField(L, "highestPrice", statistics->highestPrice);
|
||||||
|
setField(L, "lowestPrice", statistics->lowestPrice);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lua_pushnil(L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lua_pushnil(L);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaScriptInterface::luaItemTypeGetMarketSellStatistics(lua_State* L)
|
||||||
|
{
|
||||||
|
// itemType:getMarketSellStatistics()
|
||||||
|
const ItemType* itemType = getUserdata<const ItemType>(L, 1);
|
||||||
|
if (itemType) {
|
||||||
|
MarketStatistics* statistics = IOMarket::getInstance().getSaleStatistics(itemType->id);
|
||||||
|
if (statistics) {
|
||||||
|
lua_createtable(L, 4, 0);
|
||||||
|
setField(L, "numTransactions", statistics->numTransactions);
|
||||||
|
setField(L, "totalPrice", statistics->totalPrice);
|
||||||
|
setField(L, "highestPrice", statistics->highestPrice);
|
||||||
|
setField(L, "lowestPrice", statistics->lowestPrice);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lua_pushnil(L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lua_pushnil(L);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
int LuaScriptInterface::luaItemTypeHasSubType(lua_State* L)
|
int LuaScriptInterface::luaItemTypeHasSubType(lua_State* L)
|
||||||
{
|
{
|
||||||
// itemType:hasSubType()
|
// itemType:hasSubType()
|
||||||
|
@ -807,6 +807,7 @@ class LuaScriptInterface
|
|||||||
static int luaPlayerGetFreeCapacity(lua_State* L);
|
static int luaPlayerGetFreeCapacity(lua_State* L);
|
||||||
|
|
||||||
static int luaPlayerGetDepotChest(lua_State* L);
|
static int luaPlayerGetDepotChest(lua_State* L);
|
||||||
|
static int luaPlayerGetInbox(lua_State* L);
|
||||||
|
|
||||||
static int luaPlayerGetMurderTimestamps(lua_State* L);
|
static int luaPlayerGetMurderTimestamps(lua_State* L);
|
||||||
static int luaPlayerGetPlayerKillerEnd(lua_State* L);
|
static int luaPlayerGetPlayerKillerEnd(lua_State* L);
|
||||||
@ -1110,6 +1111,9 @@ class LuaScriptInterface
|
|||||||
static int luaItemTypeGetNutrition(lua_State* L);
|
static int luaItemTypeGetNutrition(lua_State* L);
|
||||||
static int luaItemTypeGetRequiredLevel(lua_State* L);
|
static int luaItemTypeGetRequiredLevel(lua_State* L);
|
||||||
|
|
||||||
|
static int luaItemTypeGetMarketBuyStatistics(lua_State* L);
|
||||||
|
static int luaItemTypeGetMarketSellStatistics(lua_State* L);
|
||||||
|
|
||||||
static int luaItemTypeHasSubType(lua_State* L);
|
static int luaItemTypeHasSubType(lua_State* L);
|
||||||
|
|
||||||
// Combat
|
// Combat
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
#include "player.h"
|
#include "player.h"
|
||||||
#include "iologindata.h"
|
#include "iologindata.h"
|
||||||
#include "town.h"
|
#include "town.h"
|
||||||
|
#include "inbox.h"
|
||||||
|
|
||||||
extern Game g_game;
|
extern Game g_game;
|
||||||
|
|
||||||
@ -94,50 +95,42 @@ bool Mailbox::sendItem(Item* item) const
|
|||||||
{
|
{
|
||||||
std::string receiver;
|
std::string receiver;
|
||||||
std::string townName;
|
std::string townName;
|
||||||
|
townName = "thais";
|
||||||
if (!getDestination(item, receiver, townName)) {
|
if (!getDestination(item, receiver, townName)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (receiver.empty() || townName.empty()) {
|
/**No need to continue if its still empty**/
|
||||||
return false;
|
if (receiver.empty()) {
|
||||||
}
|
|
||||||
|
|
||||||
Town* town = g_game.map.towns.getTown(townName);
|
|
||||||
if (!town) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Player* player = g_game.getPlayerByName(receiver);
|
Player* player = g_game.getPlayerByName(receiver);
|
||||||
if (player) {
|
if (player) {
|
||||||
DepotLocker* depotLocker = player->getDepotLocker(town->getID(), true);
|
if (g_game.internalMoveItem(item->getParent(), player->getInbox(), INDEX_WHEREEVER,
|
||||||
if (depotLocker) {
|
item, item->getItemCount(), nullptr, FLAG_NOLIMIT) == RETURNVALUE_NOERROR) {
|
||||||
if (g_game.internalMoveItem(item->getParent(), depotLocker, INDEX_WHEREEVER,
|
g_game.transformItem(item, item->getID() + 1);
|
||||||
item, item->getItemCount(), nullptr, FLAG_NOLIMIT) == RETURNVALUE_NOERROR) {
|
player->onReceiveMail();
|
||||||
g_game.transformItem(item, item->getID() + 1);
|
return true;
|
||||||
player->onReceiveMail(town->getID());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
Player tmpPlayer(nullptr);
|
Player tmpPlayer(nullptr);
|
||||||
if (!IOLoginData::loadPlayerByName(&tmpPlayer, receiver)) {
|
if (!IOLoginData::loadPlayerByName(&tmpPlayer, receiver)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
DepotLocker* depotLocker = tmpPlayer.getDepotLocker(town->getID(), true);
|
if (g_game.internalMoveItem(item->getParent(), tmpPlayer.getInbox(), INDEX_WHEREEVER,
|
||||||
if (depotLocker) {
|
item, item->getItemCount(), nullptr, FLAG_NOLIMIT) == RETURNVALUE_NOERROR) {
|
||||||
if (g_game.internalMoveItem(item->getParent(), depotLocker, INDEX_WHEREEVER,
|
g_game.transformItem(item, item->getID() + 1);
|
||||||
item, item->getItemCount(), nullptr, FLAG_NOLIMIT) == RETURNVALUE_NOERROR) {
|
IOLoginData::savePlayer(&tmpPlayer);
|
||||||
g_game.transformItem(item, item->getID() + 1);
|
return true;
|
||||||
IOLoginData::savePlayer(&tmpPlayer);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool Mailbox::getDestination(Item* item, std::string& name, std::string& town) const
|
bool Mailbox::getDestination(Item* item, std::string& name, std::string& town) const
|
||||||
{
|
{
|
||||||
const Container* container = item->getContainer();
|
const Container* container = item->getContainer();
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#include "server.h"
|
#include "server.h"
|
||||||
|
|
||||||
#include "game.h"
|
#include "game.h"
|
||||||
|
#include "iomarket.h"
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
#include <csignal> // for sigemptyset()
|
#include <csignal> // for sigemptyset()
|
||||||
@ -305,6 +306,9 @@ void mainLoader(int, char*[], ServiceManager* services)
|
|||||||
|
|
||||||
g_game.map.houses.payHouses(rentPeriod);
|
g_game.map.houses.payHouses(rentPeriod);
|
||||||
|
|
||||||
|
IOMarket::checkExpiredOffers();
|
||||||
|
IOMarket::getInstance().updateStatistics();
|
||||||
|
|
||||||
std::cout << ">> Loaded all modules, server starting up..." << std::endl;
|
std::cout << ">> Loaded all modules, server starting up..." << std::endl;
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
|
@ -32,6 +32,8 @@
|
|||||||
#include "monster.h"
|
#include "monster.h"
|
||||||
#include "movement.h"
|
#include "movement.h"
|
||||||
#include "scheduler.h"
|
#include "scheduler.h"
|
||||||
|
#include "depotchest.h"
|
||||||
|
#include "inbox.h"
|
||||||
|
|
||||||
extern ConfigManager g_config;
|
extern ConfigManager g_config;
|
||||||
extern Game g_game;
|
extern Game g_game;
|
||||||
@ -46,8 +48,9 @@ MuteCountMap Player::muteCountMap;
|
|||||||
uint32_t Player::playerAutoID = 0x10000000;
|
uint32_t Player::playerAutoID = 0x10000000;
|
||||||
|
|
||||||
Player::Player(ProtocolGame_ptr p) :
|
Player::Player(ProtocolGame_ptr p) :
|
||||||
Creature(), lastPing(OTSYS_TIME()), lastPong(lastPing), client(std::move(p))
|
Creature(), lastPing(OTSYS_TIME()), lastPong(lastPing), client(std::move(p)), inbox(new Inbox(ITEM_INBOX))
|
||||||
{
|
{
|
||||||
|
inbox->incrementReferenceCounter();
|
||||||
}
|
}
|
||||||
|
|
||||||
Player::~Player()
|
Player::~Player()
|
||||||
@ -60,9 +63,11 @@ Player::~Player()
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& it : depotLockerMap) {
|
for (const auto& it : depotLockerMap) {
|
||||||
it.second->decrementReferenceCounter();
|
it.second->removeInbox(inbox);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inbox->decrementReferenceCounter();
|
||||||
|
|
||||||
setWriteItem(nullptr);
|
setWriteItem(nullptr);
|
||||||
setEditHouse(nullptr);
|
setEditHouse(nullptr);
|
||||||
}
|
}
|
||||||
@ -579,14 +584,14 @@ bool Player::canSeeCreature(const Creature* creature) const
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::onReceiveMail(uint32_t townId) const
|
void Player::onReceiveMail() const
|
||||||
{
|
{
|
||||||
if (isNearDepotBox(townId)) {
|
if (isNearDepotBox()) {
|
||||||
sendTextMessage(MESSAGE_EVENT_ADVANCE, "New mail has arrived.");
|
sendTextMessage(MESSAGE_EVENT_ADVANCE, "New mail has arrived.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Player::isNearDepotBox(uint32_t townId) const
|
bool Player::isNearDepotBox() const
|
||||||
{
|
{
|
||||||
const Position& pos = getPosition();
|
const Position& pos = getPosition();
|
||||||
for (int32_t cx = -1; cx <= 1; ++cx) {
|
for (int32_t cx = -1; cx <= 1; ++cx) {
|
||||||
@ -597,25 +602,43 @@ bool Player::isNearDepotBox(uint32_t townId) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (DepotLocker* depotLocker = tile->getDepotLocker()) {
|
if (DepotLocker* depotLocker = tile->getDepotLocker()) {
|
||||||
if (depotLocker->getDepotId() == townId) {
|
return true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DepotChest* Player::getDepotChest(uint32_t depotId, bool autoCreate)
|
||||||
|
{
|
||||||
|
auto it = depotChests.find(depotId);
|
||||||
|
if (it != depotChests.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!autoCreate) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
it = depotChests.emplace(depotId, new DepotChest(ITEM_DEPOT)).first;
|
||||||
|
it->second->setMaxDepotItems(getMaxDepotItems());
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
DepotLocker* Player::getDepotLocker(uint32_t depotId, bool autoCreate)
|
DepotLocker* Player::getDepotLocker(uint32_t depotId, bool autoCreate)
|
||||||
{
|
{
|
||||||
auto it = depotLockerMap.find(depotId);
|
auto it = depotLockerMap.find(depotId);
|
||||||
if (it != depotLockerMap.end()) {
|
if (it != depotLockerMap.end()) {
|
||||||
|
inbox->setParent(it->second);
|
||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (autoCreate) {
|
if (autoCreate) {
|
||||||
DepotLocker* depotLocker = new DepotLocker(ITEM_LOCKER1);
|
DepotLocker* depotLocker = new DepotLocker(ITEM_LOCKER1);
|
||||||
depotLocker->setDepotId(depotId);
|
depotLocker->setDepotId(depotId);
|
||||||
|
depotLocker->internalAddThing(Item::CreateItem(ITEM_MARKET));
|
||||||
|
depotLocker->internalAddThing(inbox);
|
||||||
Item* depotItem = Item::CreateItem(ITEM_DEPOT);
|
Item* depotItem = Item::CreateItem(ITEM_DEPOT);
|
||||||
if (depotItem) {
|
if (depotItem) {
|
||||||
depotLocker->internalAddThing(depotItem);
|
depotLocker->internalAddThing(depotItem);
|
||||||
@ -1006,6 +1029,11 @@ void Player::onCreatureMove(Creature* creature, const Tile* newTile, const Posit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// leave market
|
||||||
|
if (inMarket) {
|
||||||
|
inMarket = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (party) {
|
if (party) {
|
||||||
party->updateSharedExperience();
|
party->updateSharedExperience();
|
||||||
}
|
}
|
||||||
|
64
src/player.h
64
src/player.h
@ -35,6 +35,7 @@
|
|||||||
#include "town.h"
|
#include "town.h"
|
||||||
|
|
||||||
class BehaviourDatabase;
|
class BehaviourDatabase;
|
||||||
|
class DepotChest;
|
||||||
class House;
|
class House;
|
||||||
class NetworkMessage;
|
class NetworkMessage;
|
||||||
class Weapon;
|
class Weapon;
|
||||||
@ -207,6 +208,10 @@ class Player final : public Creature, public Cylinder
|
|||||||
bool isInWar(const Player* player) const;
|
bool isInWar(const Player* player) const;
|
||||||
bool isInWarList(uint32_t guild_id) const;
|
bool isInWarList(uint32_t guild_id) const;
|
||||||
|
|
||||||
|
Inbox* getInbox() const {
|
||||||
|
return inbox;
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t getClientIcons() const;
|
uint16_t getClientIcons() const;
|
||||||
|
|
||||||
const GuildWarList& getGuildWarList() const {
|
const GuildWarList& getGuildWarList() const {
|
||||||
@ -306,6 +311,20 @@ class Player final : public Creature, public Cylinder
|
|||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setInMarket(bool value) {
|
||||||
|
inMarket = value;
|
||||||
|
}
|
||||||
|
bool isInMarket() const {
|
||||||
|
return inMarket;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLastDepotId(int16_t newId) {
|
||||||
|
lastDepotId = newId;
|
||||||
|
}
|
||||||
|
int16_t getLastDepotId() const {
|
||||||
|
return lastDepotId;
|
||||||
|
}
|
||||||
|
|
||||||
void resetIdleTime() {
|
void resetIdleTime() {
|
||||||
idleTime = 0;
|
idleTime = 0;
|
||||||
resetLastWalkingTime();
|
resetLastWalkingTime();
|
||||||
@ -454,9 +473,10 @@ class Player final : public Creature, public Cylinder
|
|||||||
void addConditionSuppressions(uint32_t conditions);
|
void addConditionSuppressions(uint32_t conditions);
|
||||||
void removeConditionSuppressions(uint32_t conditions);
|
void removeConditionSuppressions(uint32_t conditions);
|
||||||
|
|
||||||
|
DepotChest* getDepotChest(uint32_t depotId, bool autoCreate);
|
||||||
DepotLocker* getDepotLocker(uint32_t depotId, bool autoCreate);
|
DepotLocker* getDepotLocker(uint32_t depotId, bool autoCreate);
|
||||||
void onReceiveMail(uint32_t townId) const;
|
void onReceiveMail() const;
|
||||||
bool isNearDepotBox(uint32_t townId) const;
|
bool isNearDepotBox() const;
|
||||||
|
|
||||||
bool canSee(const Position& pos) const final;
|
bool canSee(const Position& pos) const final;
|
||||||
bool canSeeCreature(const Creature* creature) const final;
|
bool canSeeCreature(const Creature* creature) const final;
|
||||||
@ -856,6 +876,42 @@ class Player final : public Creature, public Cylinder
|
|||||||
client->sendToChannel(creature, type, text, channelId);
|
client->sendToChannel(creature, type, text, channelId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
void sendMarketEnter(uint32_t depotId) const {
|
||||||
|
if (client) {
|
||||||
|
client->sendMarketEnter(depotId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void sendMarketLeave() {
|
||||||
|
inMarket = false;
|
||||||
|
if (client) {
|
||||||
|
client->sendMarketLeave();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void sendMarketBrowseItem(uint16_t itemId, const MarketOfferList& buyOffers, const MarketOfferList& sellOffers) const {
|
||||||
|
if (client) {
|
||||||
|
client->sendMarketBrowseItem(itemId, buyOffers, sellOffers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void sendMarketBrowseOwnOffers(const MarketOfferList& buyOffers, const MarketOfferList& sellOffers) const {
|
||||||
|
if (client) {
|
||||||
|
client->sendMarketBrowseOwnOffers(buyOffers, sellOffers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void sendMarketBrowseOwnHistory(const HistoryMarketOfferList& buyOffers, const HistoryMarketOfferList& sellOffers) const {
|
||||||
|
if (client) {
|
||||||
|
client->sendMarketBrowseOwnHistory(buyOffers, sellOffers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void sendMarketAcceptOffer(const MarketOfferEx& offer) const {
|
||||||
|
if (client) {
|
||||||
|
client->sendMarketAcceptOffer(offer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void sendMarketCancelOffer(const MarketOfferEx& offer) const {
|
||||||
|
if (client) {
|
||||||
|
client->sendMarketCancelOffer(offer);
|
||||||
|
}
|
||||||
|
}
|
||||||
void sendTradeItemRequest(const std::string& traderName, const Item* item, bool ack) const {
|
void sendTradeItemRequest(const std::string& traderName, const Item* item, bool ack) const {
|
||||||
if (client) {
|
if (client) {
|
||||||
client->sendTradeItemRequest(traderName, item, ack);
|
client->sendTradeItemRequest(traderName, item, ack);
|
||||||
@ -1023,6 +1079,7 @@ class Player final : public Creature, public Cylinder
|
|||||||
|
|
||||||
std::map<uint8_t, OpenContainer> openContainers;
|
std::map<uint8_t, OpenContainer> openContainers;
|
||||||
std::map<uint32_t, DepotLocker*> depotLockerMap;
|
std::map<uint32_t, DepotLocker*> depotLockerMap;
|
||||||
|
std::map<uint32_t, DepotChest*> depotChests;
|
||||||
std::map<uint32_t, int32_t> storageMap;
|
std::map<uint32_t, int32_t> storageMap;
|
||||||
|
|
||||||
std::vector<OutfitEntry> outfits;
|
std::vector<OutfitEntry> outfits;
|
||||||
@ -1061,6 +1118,7 @@ class Player final : public Creature, public Cylinder
|
|||||||
Guild* guild = nullptr;
|
Guild* guild = nullptr;
|
||||||
const GuildRank* guildRank = nullptr;
|
const GuildRank* guildRank = nullptr;
|
||||||
Group* group = nullptr;
|
Group* group = nullptr;
|
||||||
|
Inbox* inbox;
|
||||||
Item* tradeItem = nullptr;
|
Item* tradeItem = nullptr;
|
||||||
Item* inventory[CONST_SLOT_LAST + 1] = {};
|
Item* inventory[CONST_SLOT_LAST + 1] = {};
|
||||||
Item* writeItem = nullptr;
|
Item* writeItem = nullptr;
|
||||||
@ -1103,6 +1161,7 @@ class Player final : public Creature, public Cylinder
|
|||||||
|
|
||||||
uint16_t staminaMinutes = 3360;
|
uint16_t staminaMinutes = 3360;
|
||||||
uint16_t maxWriteLen = 0;
|
uint16_t maxWriteLen = 0;
|
||||||
|
int16_t lastDepotId = -1;
|
||||||
|
|
||||||
uint8_t soul = 0;
|
uint8_t soul = 0;
|
||||||
uint8_t blessings = 0;
|
uint8_t blessings = 0;
|
||||||
@ -1118,6 +1177,7 @@ class Player final : public Creature, public Cylinder
|
|||||||
AccountType_t accountType = ACCOUNT_TYPE_NORMAL;
|
AccountType_t accountType = ACCOUNT_TYPE_NORMAL;
|
||||||
|
|
||||||
bool secureMode = false;
|
bool secureMode = false;
|
||||||
|
bool inMarket = false;
|
||||||
bool ghostMode = false;
|
bool ghostMode = false;
|
||||||
bool pzLocked = false;
|
bool pzLocked = false;
|
||||||
bool isConnecting = false;
|
bool isConnecting = false;
|
||||||
|
@ -30,10 +30,13 @@
|
|||||||
#include "configmanager.h"
|
#include "configmanager.h"
|
||||||
#include "actions.h"
|
#include "actions.h"
|
||||||
#include "game.h"
|
#include "game.h"
|
||||||
|
#include "inbox.h"
|
||||||
|
#include "iomarket.h"
|
||||||
#include "iologindata.h"
|
#include "iologindata.h"
|
||||||
#include "waitlist.h"
|
#include "waitlist.h"
|
||||||
#include "ban.h"
|
#include "ban.h"
|
||||||
#include "scheduler.h"
|
#include "scheduler.h"
|
||||||
|
#include "depotchest.h"
|
||||||
|
|
||||||
extern ConfigManager g_config;
|
extern ConfigManager g_config;
|
||||||
extern Actions actions;
|
extern Actions actions;
|
||||||
@ -451,6 +454,11 @@ void ProtocolGame::parsePacket(NetworkMessage& msg)
|
|||||||
case 0xE8: parseDebugAssert(msg); break;
|
case 0xE8: parseDebugAssert(msg); break;
|
||||||
case 0xF0: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerShowQuestLog, player->getID()); break;
|
case 0xF0: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerShowQuestLog, player->getID()); break;
|
||||||
case 0xF1: parseQuestLine(msg); break;
|
case 0xF1: parseQuestLine(msg); break;
|
||||||
|
case 0xF4: parseMarketLeave(); break;
|
||||||
|
case 0xF5: parseMarketBrowse(msg); break;
|
||||||
|
case 0xF6: parseMarketCreateOffer(msg); break;
|
||||||
|
case 0xF7: parseMarketCancelOffer(msg); break;
|
||||||
|
case 0xF8: parseMarketAcceptOffer(msg); break;
|
||||||
default:
|
default:
|
||||||
std::cout << "Player: " << player->getName() << " sent an unknown packet header: 0x" << std::hex << static_cast<uint16_t>(recvbyte) << std::dec << "!" << std::endl;
|
std::cout << "Player: " << player->getName() << " sent an unknown packet header: 0x" << std::hex << static_cast<uint16_t>(recvbyte) << std::dec << "!" << std::endl;
|
||||||
break;
|
break;
|
||||||
@ -997,6 +1005,61 @@ void ProtocolGame::parseQuestLine(NetworkMessage& msg)
|
|||||||
addGameTask(&Game::playerShowQuestLine, player->getID(), questId);
|
addGameTask(&Game::playerShowQuestLine, player->getID(), questId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ProtocolGame::parseMarketLeave()
|
||||||
|
{
|
||||||
|
addGameTask(&Game::playerLeaveMarket, player->getID());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolGame::parseMarketBrowse(NetworkMessage& msg)
|
||||||
|
{
|
||||||
|
uint8_t browseId = msg.get<uint8_t>();
|
||||||
|
if (browseId == MARKETREQUEST_OWN_OFFERS) {
|
||||||
|
addGameTask(&Game::playerBrowseMarketOwnOffers, player->getID());
|
||||||
|
}
|
||||||
|
else if (browseId == MARKETREQUEST_OWN_HISTORY) {
|
||||||
|
addGameTask(&Game::playerBrowseMarketOwnHistory, player->getID());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
uint16_t spriteID = msg.get<uint16_t>();
|
||||||
|
addGameTask(&Game::playerBrowseMarket, player->getID(), spriteID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolGame::parseMarketCreateOffer(NetworkMessage& msg)
|
||||||
|
{
|
||||||
|
uint8_t type = msg.getByte();
|
||||||
|
uint16_t spriteId = msg.get<uint16_t>();
|
||||||
|
|
||||||
|
const ItemType& it = Item::items[spriteId];
|
||||||
|
if (it.id == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO:
|
||||||
|
//else if (it.classification > 0) {
|
||||||
|
// msg.getByte(); // item tier
|
||||||
|
//}
|
||||||
|
|
||||||
|
uint16_t amount = msg.get<uint16_t>();
|
||||||
|
uint64_t price = msg.get<uint64_t>();
|
||||||
|
bool anonymous = (msg.getByte() != 0);
|
||||||
|
addGameTask(&Game::playerCreateMarketOffer, player->getID(), type, spriteId, amount, price, anonymous);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolGame::parseMarketCancelOffer(NetworkMessage& msg)
|
||||||
|
{
|
||||||
|
uint32_t timestamp = msg.get<uint32_t>();
|
||||||
|
uint16_t counter = msg.get<uint16_t>();
|
||||||
|
addGameTask(&Game::playerCancelMarketOffer, player->getID(), timestamp, counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolGame::parseMarketAcceptOffer(NetworkMessage& msg)
|
||||||
|
{
|
||||||
|
uint32_t timestamp = msg.get<uint32_t>();
|
||||||
|
uint16_t counter = msg.get<uint16_t>();
|
||||||
|
uint16_t amount = msg.get<uint16_t>();
|
||||||
|
addGameTask(&Game::playerAcceptMarketOffer, player->getID(), timestamp, counter, amount);
|
||||||
|
}
|
||||||
|
|
||||||
void ProtocolGame::parseSeekInContainer(NetworkMessage& msg)
|
void ProtocolGame::parseSeekInContainer(NetworkMessage& msg)
|
||||||
{
|
{
|
||||||
uint8_t containerId = msg.getByte();
|
uint8_t containerId = msg.getByte();
|
||||||
@ -1251,6 +1314,259 @@ void ProtocolGame::sendContainer(uint8_t cid, const Container* container, bool h
|
|||||||
writeToOutputBuffer(msg);
|
writeToOutputBuffer(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ProtocolGame::sendMarketEnter(uint32_t depotId)
|
||||||
|
{
|
||||||
|
NetworkMessage msg;
|
||||||
|
msg.addByte(0xF6);
|
||||||
|
msg.addByte(std::min<uint32_t>(IOMarket::getPlayerOfferCount(player->getGUID()), std::numeric_limits<uint8_t>::max()));
|
||||||
|
|
||||||
|
DepotLocker* depotLocker = player->getDepotLocker(depotId, false);
|
||||||
|
if (!depotLocker) {
|
||||||
|
msg.add<uint16_t>(0x00);
|
||||||
|
writeToOutputBuffer(msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
player->setInMarket(true);
|
||||||
|
|
||||||
|
msg.add<uint64_t>(player->getBankBalance());
|
||||||
|
|
||||||
|
std::map<uint16_t, uint32_t> depotItems;
|
||||||
|
std::forward_list<Container*> containerList{ depotLocker, player->getInbox() };
|
||||||
|
|
||||||
|
do {
|
||||||
|
Container* container = containerList.front();
|
||||||
|
containerList.pop_front();
|
||||||
|
|
||||||
|
for (Item* item : container->getItemList()) {
|
||||||
|
Container* c = item->getContainer();
|
||||||
|
if (c && !c->empty()) {
|
||||||
|
containerList.push_front(c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ItemType& itemType = Item::items[item->getID()];
|
||||||
|
if (itemType.id == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c && (!itemType.isContainer() || c->capacity() != itemType.maxItems)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!item->hasMarketAttributes()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
depotItems[itemType.id] += Item::countByType(item, -1);
|
||||||
|
}
|
||||||
|
} while (!containerList.empty());
|
||||||
|
|
||||||
|
uint16_t itemsToSend = std::min<size_t>(depotItems.size(), std::numeric_limits<uint16_t>::max());
|
||||||
|
uint16_t i = 0;
|
||||||
|
|
||||||
|
msg.add<uint16_t>(itemsToSend);
|
||||||
|
for (std::map<uint16_t, uint32_t>::const_iterator it = depotItems.begin(); i < itemsToSend; ++it, ++i) {
|
||||||
|
const ItemType& itemType = Item::items[it->first];
|
||||||
|
msg.add<uint16_t>(itemType.id);
|
||||||
|
// TODO
|
||||||
|
//if (itemType.classification > 0) {
|
||||||
|
// msg.addByte(0);
|
||||||
|
//}
|
||||||
|
msg.add<uint16_t>(std::min<uint32_t>(0xFFFF, it->second));
|
||||||
|
}
|
||||||
|
writeToOutputBuffer(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolGame::sendMarketLeave()
|
||||||
|
{
|
||||||
|
NetworkMessage msg;
|
||||||
|
msg.addByte(0xF7);
|
||||||
|
writeToOutputBuffer(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolGame::sendMarketBrowseItem(uint16_t itemId, const MarketOfferList& buyOffers, const MarketOfferList& sellOffers)
|
||||||
|
{
|
||||||
|
NetworkMessage msg;
|
||||||
|
msg.addByte(0xF9);
|
||||||
|
msg.addByte(MARKETREQUEST_ITEM);
|
||||||
|
msg.addItemId(itemId);
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
//if (Item::items[itemId].classification > 0) {
|
||||||
|
// msg.addByte(0); // item tier
|
||||||
|
//}
|
||||||
|
|
||||||
|
msg.add<uint32_t>(buyOffers.size());
|
||||||
|
for (const MarketOffer& offer : buyOffers) {
|
||||||
|
msg.add<uint32_t>(offer.timestamp);
|
||||||
|
msg.add<uint16_t>(offer.counter);
|
||||||
|
msg.add<uint16_t>(offer.amount);
|
||||||
|
msg.add<uint64_t>(offer.price);
|
||||||
|
msg.addString(offer.playerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.add<uint32_t>(sellOffers.size());
|
||||||
|
for (const MarketOffer& offer : sellOffers) {
|
||||||
|
msg.add<uint32_t>(offer.timestamp);
|
||||||
|
msg.add<uint16_t>(offer.counter);
|
||||||
|
msg.add<uint16_t>(offer.amount);
|
||||||
|
msg.add<uint64_t>(offer.price);
|
||||||
|
msg.addString(offer.playerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeToOutputBuffer(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolGame::sendMarketAcceptOffer(const MarketOfferEx& offer)
|
||||||
|
{
|
||||||
|
NetworkMessage msg;
|
||||||
|
msg.addByte(0xF9);
|
||||||
|
msg.addByte(MARKETREQUEST_ITEM);
|
||||||
|
msg.addItemId(offer.itemId);
|
||||||
|
// TODO
|
||||||
|
//if (Item::items[offer.itemId].classification > 0) {
|
||||||
|
// msg.addByte(0);
|
||||||
|
//}
|
||||||
|
|
||||||
|
if (offer.type == MARKETACTION_BUY) {
|
||||||
|
msg.add<uint32_t>(0x01);
|
||||||
|
msg.add<uint32_t>(offer.timestamp);
|
||||||
|
msg.add<uint16_t>(offer.counter);
|
||||||
|
msg.add<uint16_t>(offer.amount);
|
||||||
|
msg.add<uint64_t>(offer.price);
|
||||||
|
msg.addString(offer.playerName);
|
||||||
|
msg.add<uint32_t>(0x00);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
msg.add<uint32_t>(0x00);
|
||||||
|
msg.add<uint32_t>(0x01);
|
||||||
|
msg.add<uint32_t>(offer.timestamp);
|
||||||
|
msg.add<uint16_t>(offer.counter);
|
||||||
|
msg.add<uint16_t>(offer.amount);
|
||||||
|
msg.add<uint64_t>(offer.price);
|
||||||
|
msg.addString(offer.playerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeToOutputBuffer(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolGame::sendMarketBrowseOwnOffers(const MarketOfferList& buyOffers, const MarketOfferList& sellOffers)
|
||||||
|
{
|
||||||
|
NetworkMessage msg;
|
||||||
|
msg.addByte(0xF9);
|
||||||
|
msg.addByte(MARKETREQUEST_OWN_OFFERS);
|
||||||
|
|
||||||
|
msg.add<uint32_t>(buyOffers.size());
|
||||||
|
for (const MarketOffer& offer : buyOffers) {
|
||||||
|
msg.add<uint32_t>(offer.timestamp);
|
||||||
|
msg.add<uint16_t>(offer.counter);
|
||||||
|
msg.addItemId(offer.itemId);
|
||||||
|
// TODO
|
||||||
|
//if (Item::items[offer.itemId].classification > 0) {
|
||||||
|
// msg.addByte(0);
|
||||||
|
//}
|
||||||
|
msg.add<uint16_t>(offer.amount);
|
||||||
|
msg.add<uint64_t>(offer.price);
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.add<uint32_t>(sellOffers.size());
|
||||||
|
for (const MarketOffer& offer : sellOffers) {
|
||||||
|
msg.add<uint32_t>(offer.timestamp);
|
||||||
|
msg.add<uint16_t>(offer.counter);
|
||||||
|
msg.addItemId(offer.itemId);
|
||||||
|
// TODO
|
||||||
|
//if (Item::items[offer.itemId].classification > 0) {
|
||||||
|
// msg.addByte(0);
|
||||||
|
//}
|
||||||
|
msg.add<uint16_t>(offer.amount);
|
||||||
|
msg.add<uint64_t>(offer.price);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeToOutputBuffer(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolGame::sendMarketCancelOffer(const MarketOfferEx& offer)
|
||||||
|
{
|
||||||
|
NetworkMessage msg;
|
||||||
|
msg.addByte(0xF9);
|
||||||
|
msg.addByte(MARKETREQUEST_OWN_OFFERS);
|
||||||
|
|
||||||
|
if (offer.type == MARKETACTION_BUY) {
|
||||||
|
msg.add<uint32_t>(0x01);
|
||||||
|
msg.add<uint32_t>(offer.timestamp);
|
||||||
|
msg.add<uint16_t>(offer.counter);
|
||||||
|
msg.addItemId(offer.itemId);
|
||||||
|
// TODO
|
||||||
|
//if (Item::items[offer.itemId].classification > 0) {
|
||||||
|
// msg.addByte(0);
|
||||||
|
//}
|
||||||
|
msg.add<uint16_t>(offer.amount);
|
||||||
|
msg.add<uint64_t>(offer.price);
|
||||||
|
msg.add<uint32_t>(0x00);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
msg.add<uint32_t>(0x00);
|
||||||
|
msg.add<uint32_t>(0x01);
|
||||||
|
msg.add<uint32_t>(offer.timestamp);
|
||||||
|
msg.add<uint16_t>(offer.counter);
|
||||||
|
msg.addItemId(offer.itemId);
|
||||||
|
// TODO
|
||||||
|
//if (Item::items[offer.itemId].classification > 0) {
|
||||||
|
// msg.addByte(0);
|
||||||
|
//}
|
||||||
|
msg.add<uint16_t>(offer.amount);
|
||||||
|
msg.add<uint64_t>(offer.price);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeToOutputBuffer(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProtocolGame::sendMarketBrowseOwnHistory(const HistoryMarketOfferList& buyOffers, const HistoryMarketOfferList& sellOffers)
|
||||||
|
{
|
||||||
|
uint32_t i = 0;
|
||||||
|
std::map<uint32_t, uint16_t> counterMap;
|
||||||
|
uint32_t buyOffersToSend = std::min<uint32_t>(buyOffers.size(), 810 + std::max<int32_t>(0, 810 - sellOffers.size()));
|
||||||
|
uint32_t sellOffersToSend = std::min<uint32_t>(sellOffers.size(), 810 + std::max<int32_t>(0, 810 - buyOffers.size()));
|
||||||
|
|
||||||
|
NetworkMessage msg;
|
||||||
|
msg.addByte(0xF9);
|
||||||
|
msg.addByte(MARKETREQUEST_OWN_HISTORY);
|
||||||
|
|
||||||
|
msg.add<uint32_t>(buyOffersToSend);
|
||||||
|
for (auto it = buyOffers.begin(); i < buyOffersToSend; ++it, ++i) {
|
||||||
|
msg.add<uint32_t>(it->timestamp);
|
||||||
|
msg.add<uint16_t>(counterMap[it->timestamp]++);
|
||||||
|
msg.addItemId(it->itemId);
|
||||||
|
// TODO
|
||||||
|
//if (Item::items[it->itemId].classification > 0) {
|
||||||
|
// msg.addByte(0);
|
||||||
|
//}
|
||||||
|
msg.add<uint16_t>(it->amount);
|
||||||
|
msg.add<uint64_t>(it->price);
|
||||||
|
msg.addByte(it->state);
|
||||||
|
}
|
||||||
|
|
||||||
|
counterMap.clear();
|
||||||
|
i = 0;
|
||||||
|
|
||||||
|
msg.add<uint32_t>(sellOffersToSend);
|
||||||
|
for (auto it = sellOffers.begin(); i < sellOffersToSend; ++it, ++i) {
|
||||||
|
msg.add<uint32_t>(it->timestamp);
|
||||||
|
msg.add<uint16_t>(counterMap[it->timestamp]++);
|
||||||
|
msg.addItemId(it->itemId);
|
||||||
|
// TODO
|
||||||
|
//if (Item::items[it->itemId].classification > 0) {
|
||||||
|
// msg.addByte(0);
|
||||||
|
//}
|
||||||
|
msg.add<uint16_t>(it->amount);
|
||||||
|
msg.add<uint64_t>(it->price);
|
||||||
|
msg.addByte(it->state);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeToOutputBuffer(msg);
|
||||||
|
}
|
||||||
|
|
||||||
void ProtocolGame::sendQuestLog()
|
void ProtocolGame::sendQuestLog()
|
||||||
{
|
{
|
||||||
NetworkMessage msg;
|
NetworkMessage msg;
|
||||||
|
@ -115,6 +115,13 @@ class ProtocolGame final : public Protocol
|
|||||||
|
|
||||||
void parseQuestLine(NetworkMessage& msg);
|
void parseQuestLine(NetworkMessage& msg);
|
||||||
|
|
||||||
|
//market methods
|
||||||
|
void parseMarketLeave();
|
||||||
|
void parseMarketBrowse(NetworkMessage& msg);
|
||||||
|
void parseMarketCreateOffer(NetworkMessage& msg);
|
||||||
|
void parseMarketCancelOffer(NetworkMessage& msg);
|
||||||
|
void parseMarketAcceptOffer(NetworkMessage& msg);
|
||||||
|
|
||||||
void parseInviteToParty(NetworkMessage& msg);
|
void parseInviteToParty(NetworkMessage& msg);
|
||||||
void parseJoinParty(NetworkMessage& msg);
|
void parseJoinParty(NetworkMessage& msg);
|
||||||
void parseRevokePartyInvite(NetworkMessage& msg);
|
void parseRevokePartyInvite(NetworkMessage& msg);
|
||||||
@ -160,6 +167,13 @@ class ProtocolGame final : public Protocol
|
|||||||
void sendCreatureTurn(const Creature* creature, uint32_t stackpos);
|
void sendCreatureTurn(const Creature* creature, uint32_t stackpos);
|
||||||
void sendCreatureSay(const Creature* creature, SpeakClasses type, const std::string& text, const Position* pos = nullptr);
|
void sendCreatureSay(const Creature* creature, SpeakClasses type, const std::string& text, const Position* pos = nullptr);
|
||||||
|
|
||||||
|
void sendMarketEnter(uint32_t depotId);
|
||||||
|
void sendMarketLeave();
|
||||||
|
void sendMarketBrowseItem(uint16_t itemId, const MarketOfferList& buyOffers, const MarketOfferList& sellOffers);
|
||||||
|
void sendMarketAcceptOffer(const MarketOfferEx& offer);
|
||||||
|
void sendMarketBrowseOwnOffers(const MarketOfferList& buyOffers, const MarketOfferList& sellOffers);
|
||||||
|
void sendMarketCancelOffer(const MarketOfferEx& offer);
|
||||||
|
void sendMarketBrowseOwnHistory(const HistoryMarketOfferList& buyOffers, const HistoryMarketOfferList& sellOffers);
|
||||||
void sendQuestLog();
|
void sendQuestLog();
|
||||||
void sendQuestLine(const Quest* quest);
|
void sendQuestLine(const Quest* quest);
|
||||||
|
|
||||||
|
51
src/storeinbox.cpp
Normal file
51
src/storeinbox.cpp
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2022 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 "storeinbox.h"
|
||||||
|
|
||||||
|
StoreInbox::StoreInbox(uint16_t type) : Container(type, 20, true, true) {}
|
||||||
|
|
||||||
|
ReturnValue StoreInbox::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags, Creature*) const
|
||||||
|
{
|
||||||
|
const Item* item = thing.getItem();
|
||||||
|
if (!item) {
|
||||||
|
return RETURNVALUE_NOTPOSSIBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item == this) {
|
||||||
|
return RETURNVALUE_THISISIMPOSSIBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!item->isPickupable()) {
|
||||||
|
return RETURNVALUE_CANNOTPICKUP;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasBitSet(FLAG_NOLIMIT, flags)) {
|
||||||
|
if (!item->isStoreItem()) {
|
||||||
|
return RETURNVALUE_CANNOTMOVEITEMISNOTSTOREITEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Container* container = item->getContainer();
|
||||||
|
if (container && !container->empty()) {
|
||||||
|
return RETURNVALUE_ITEMCANNOTBEMOVEDTHERE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return RETURNVALUE_NOERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StoreInbox::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t)
|
||||||
|
{
|
||||||
|
if (parent) {
|
||||||
|
parent->postAddNotification(thing, oldParent, index, LINK_TOPPARENT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StoreInbox::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t)
|
||||||
|
{
|
||||||
|
if (parent) {
|
||||||
|
parent->postRemoveNotification(thing, newParent, index, LINK_TOPPARENT);
|
||||||
|
}
|
||||||
|
}
|
33
src/storeinbox.h
Normal file
33
src/storeinbox.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// Copyright 2022 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_STOREINBOX_H
|
||||||
|
#define FS_STOREINBOX_H
|
||||||
|
|
||||||
|
#include "container.h"
|
||||||
|
|
||||||
|
class StoreInbox final : public Container
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit StoreInbox(uint16_t type);
|
||||||
|
|
||||||
|
StoreInbox* getStoreInbox() override {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
const StoreInbox* getStoreInbox() const override {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
//cylinder implementations
|
||||||
|
ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count,
|
||||||
|
uint32_t flags, Creature* actor = nullptr) const override;
|
||||||
|
|
||||||
|
void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) override;
|
||||||
|
void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) override;
|
||||||
|
|
||||||
|
bool canRemove() const override {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FS_STOREINBOX_H
|
@ -48,7 +48,7 @@ typedef std::vector<int32_t> IntegerVec;
|
|||||||
|
|
||||||
StringVec explodeString(const std::string& inString, const std::string& separator, int32_t limit = -1);
|
StringVec explodeString(const std::string& inString, const std::string& separator, int32_t limit = -1);
|
||||||
IntegerVec vectorAtoi(const StringVec& stringVector);
|
IntegerVec vectorAtoi(const StringVec& stringVector);
|
||||||
inline bool hasBitSet(uint32_t flag, uint32_t flags) {
|
constexpr bool hasBitSet(uint32_t flag, uint32_t flags) {
|
||||||
return (flags & flag) != 0;
|
return (flags & flag) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,6 +159,7 @@
|
|||||||
<ClCompile Include="..\src\database.cpp" />
|
<ClCompile Include="..\src\database.cpp" />
|
||||||
<ClCompile Include="..\src\databasemanager.cpp" />
|
<ClCompile Include="..\src\databasemanager.cpp" />
|
||||||
<ClCompile Include="..\src\databasetasks.cpp" />
|
<ClCompile Include="..\src\databasetasks.cpp" />
|
||||||
|
<ClCompile Include="..\src\depotchest.cpp" />
|
||||||
<ClCompile Include="..\src\depotlocker.cpp" />
|
<ClCompile Include="..\src\depotlocker.cpp" />
|
||||||
<ClCompile Include="..\src\events.cpp" />
|
<ClCompile Include="..\src\events.cpp" />
|
||||||
<ClCompile Include="..\src\fileloader.cpp" />
|
<ClCompile Include="..\src\fileloader.cpp" />
|
||||||
@ -168,6 +169,7 @@
|
|||||||
<ClCompile Include="..\src\guild.cpp" />
|
<ClCompile Include="..\src\guild.cpp" />
|
||||||
<ClCompile Include="..\src\house.cpp" />
|
<ClCompile Include="..\src\house.cpp" />
|
||||||
<ClCompile Include="..\src\housetile.cpp" />
|
<ClCompile Include="..\src\housetile.cpp" />
|
||||||
|
<ClCompile Include="..\src\inbox.cpp" />
|
||||||
<ClCompile Include="..\src\ioguild.cpp" />
|
<ClCompile Include="..\src\ioguild.cpp" />
|
||||||
<ClCompile Include="..\src\iologindata.cpp" />
|
<ClCompile Include="..\src\iologindata.cpp" />
|
||||||
<ClCompile Include="..\src\iomap.cpp" />
|
<ClCompile Include="..\src\iomap.cpp" />
|
||||||
@ -218,6 +220,7 @@
|
|||||||
<ClCompile Include="..\src\wildcardtree.cpp" />
|
<ClCompile Include="..\src\wildcardtree.cpp" />
|
||||||
<ClCompile Include="..\src\xtea.cpp" />
|
<ClCompile Include="..\src\xtea.cpp" />
|
||||||
<ClCompile Include="..\src\quests.cpp" />
|
<ClCompile Include="..\src\quests.cpp" />
|
||||||
|
<ClCompile Include="..\src\iomarket.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="..\src\account.h" />
|
<ClInclude Include="..\src\account.h" />
|
||||||
@ -240,6 +243,7 @@
|
|||||||
<ClInclude Include="..\src\databasemanager.h" />
|
<ClInclude Include="..\src\databasemanager.h" />
|
||||||
<ClInclude Include="..\src\databasetasks.h" />
|
<ClInclude Include="..\src\databasetasks.h" />
|
||||||
<ClInclude Include="..\src\definitions.h" />
|
<ClInclude Include="..\src\definitions.h" />
|
||||||
|
<ClInclude Include="..\src\depotchest.h" />
|
||||||
<ClInclude Include="..\src\depotlocker.h" />
|
<ClInclude Include="..\src\depotlocker.h" />
|
||||||
<ClInclude Include="..\src\events.h" />
|
<ClInclude Include="..\src\events.h" />
|
||||||
<ClInclude Include="..\src\enums.h" />
|
<ClInclude Include="..\src\enums.h" />
|
||||||
@ -250,6 +254,7 @@
|
|||||||
<ClInclude Include="..\src\guild.h" />
|
<ClInclude Include="..\src\guild.h" />
|
||||||
<ClInclude Include="..\src\house.h" />
|
<ClInclude Include="..\src\house.h" />
|
||||||
<ClInclude Include="..\src\housetile.h" />
|
<ClInclude Include="..\src\housetile.h" />
|
||||||
|
<ClInclude Include="..\src\inbox.h" />
|
||||||
<ClInclude Include="..\src\ioguild.h" />
|
<ClInclude Include="..\src\ioguild.h" />
|
||||||
<ClInclude Include="..\src\iologindata.h" />
|
<ClInclude Include="..\src\iologindata.h" />
|
||||||
<ClInclude Include="..\src\iomap.h" />
|
<ClInclude Include="..\src\iomap.h" />
|
||||||
@ -297,6 +302,7 @@
|
|||||||
<ClInclude Include="..\src\wildcardtree.h" />
|
<ClInclude Include="..\src\wildcardtree.h" />
|
||||||
<ClInclude Include="..\src\xtea.h" />
|
<ClInclude Include="..\src\xtea.h" />
|
||||||
<ClInclude Include="..\src\quests.h" />
|
<ClInclude Include="..\src\quests.h" />
|
||||||
|
<ClInclude Include="..\src\iomarket.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
<ImportGroup Label="ExtensionTargets">
|
<ImportGroup Label="ExtensionTargets">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user