added mounts, auras, wings and shop. Packet compression does not work extendedOpCodes does not work but shop is because of extra configuration

This commit is contained in:
ErikasKontenis
2022-08-08 18:55:47 +03:00
parent b130e66149
commit 1c35d04337
58 changed files with 2909 additions and 77 deletions

View File

@@ -1,6 +1,7 @@
set(tfs_SRC
${CMAKE_CURRENT_LIST_DIR}/otpch.cpp
${CMAKE_CURRENT_LIST_DIR}/actions.cpp
${CMAKE_CURRENT_LIST_DIR}/auras.cpp
${CMAKE_CURRENT_LIST_DIR}/ban.cpp
${CMAKE_CURRENT_LIST_DIR}/baseevents.cpp
${CMAKE_CURRENT_LIST_DIR}/bed.cpp
@@ -38,6 +39,7 @@ set(tfs_SRC
${CMAKE_CURRENT_LIST_DIR}/map.cpp
${CMAKE_CURRENT_LIST_DIR}/monster.cpp
${CMAKE_CURRENT_LIST_DIR}/monsters.cpp
${CMAKE_CURRENT_LIST_DIR}/mounts.cpp
${CMAKE_CURRENT_LIST_DIR}/movement.cpp
${CMAKE_CURRENT_LIST_DIR}/networkmessage.cpp
${CMAKE_CURRENT_LIST_DIR}/npc.cpp
@@ -56,6 +58,7 @@ set(tfs_SRC
${CMAKE_CURRENT_LIST_DIR}/scheduler.cpp
${CMAKE_CURRENT_LIST_DIR}/scriptmanager.cpp
${CMAKE_CURRENT_LIST_DIR}/server.cpp
${CMAKE_CURRENT_LIST_DIR}/shaders.cpp
${CMAKE_CURRENT_LIST_DIR}/spawn.cpp
${CMAKE_CURRENT_LIST_DIR}/spells.cpp
${CMAKE_CURRENT_LIST_DIR}/script.cpp
@@ -68,6 +71,7 @@ set(tfs_SRC
${CMAKE_CURRENT_LIST_DIR}/vocation.cpp
${CMAKE_CURRENT_LIST_DIR}/waitlist.cpp
${CMAKE_CURRENT_LIST_DIR}/wildcardtree.cpp
${CMAKE_CURRENT_LIST_DIR}/wings.cpp
${CMAKE_CURRENT_LIST_DIR}/xtea.cpp
${CMAKE_CURRENT_LIST_DIR}/quests.cpp
PARENT_SCOPE)

View File

@@ -324,7 +324,6 @@ ReturnValue Actions::internalUseItem(Player* player, const Position& pos, uint8_
bool Actions::useItem(Player* player, const Position& pos, uint8_t index, Item* item, bool isHotkey)
{
player->setNextAction(OTSYS_TIME() + g_config.getNumber(ConfigManager::ACTIONS_DELAY_INTERVAL));
player->stopWalk();
if (item->getID() == ITEM_MARKET)
{
@@ -360,7 +359,6 @@ bool Actions::useItemEx(Player* player, const Position& fromPos, const Position&
uint8_t toStackPos, Item* item, bool isHotkey, Creature* creature/* = nullptr*/)
{
player->setNextAction(OTSYS_TIME() + g_config.getNumber(ConfigManager::EX_ACTIONS_DELAY_INTERVAL));
player->stopWalk();
Action* action = getAction(item);
if (!action) {

63
src/auras.cpp Normal file
View File

@@ -0,0 +1,63 @@
#include "otpch.h"
#include "auras.h"
#include "pugicast.h"
#include "tools.h"
bool Auras::reload()
{
auras.clear();
return loadFromXml();
}
bool Auras::loadFromXml()
{
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file("data/XML/auras.xml");
if (!result) {
printXMLError("Error - Auras::loadFromXml", "data/XML/auras.xml", result);
return false;
}
for (auto auraNode : doc.child("auras").children()) {
auras.emplace_back(
static_cast<uint8_t>(pugi::cast<uint16_t>(auraNode.attribute("id").value())),
pugi::cast<uint16_t>(auraNode.attribute("clientid").value()),
auraNode.attribute("name").as_string(),
pugi::cast<int32_t>(auraNode.attribute("speed").value()),
auraNode.attribute("premium").as_bool()
);
}
auras.shrink_to_fit();
return true;
}
Aura* Auras::getAuraByID(uint8_t id)
{
auto it = std::find_if(auras.begin(), auras.end(), [id](const Aura& aura) {
return aura.id == id;
});
return it != auras.end() ? &*it : nullptr;
}
Aura* Auras::getAuraByName(const std::string& name) {
auto auraName = name.c_str();
for (auto& it : auras) {
if (strcasecmp(auraName, it.name.c_str()) == 0) {
return &it;
}
}
return nullptr;
}
Aura* Auras::getAuraByClientID(uint16_t clientId)
{
auto it = std::find_if(auras.begin(), auras.end(), [clientId](const Aura& aura) {
return aura.clientId == clientId;
});
return it != auras.end() ? &*it : nullptr;
}

33
src/auras.h Normal file
View File

@@ -0,0 +1,33 @@
#ifndef FS_AURAS_H
#define FS_AURAS_H
struct Aura
{
Aura(uint8_t id, uint16_t clientId, std::string name, int32_t speed, bool premium) :
name(std::move(name)), speed(speed), clientId(clientId), id(id), premium(premium) {}
std::string name;
int32_t speed;
uint16_t clientId;
uint8_t id;
bool premium;
};
class Auras
{
public:
bool reload();
bool loadFromXml();
Aura* getAuraByID(uint8_t id);
Aura* getAuraByName(const std::string& name);
Aura* getAuraByClientID(uint16_t clientId);
const std::vector<Aura>& getAuras() const {
return auras;
}
private:
std::vector<Aura> auras;
};
#endif

View File

@@ -87,6 +87,7 @@ bool ConfigManager::load()
boolean[ROPE_SPOT_BLOCK] = getGlobalBoolean(L, "ropeSpotBlock", false);
boolean[DROP_ITEMS] = getGlobalBoolean(L, "dropItems", false);
boolean[DISTANCE_WEAPONS_DROP_ON_GROUND] = getGlobalBoolean(L, "distanceWeaponsDropOnGround", true);
boolean[PACKET_COMPRESSION] = getGlobalBoolean(L, "packetCompression", true);
string[DEFAULT_PRIORITY] = getGlobalString(L, "defaultPriority", "high");
string[SERVER_NAME] = getGlobalString(L, "serverName", "");

View File

@@ -53,6 +53,7 @@ class ConfigManager
DISTANCE_WEAPONS_DROP_ON_GROUND,
CORPSE_OWNER_ENABLED,
ROPE_SPOT_BLOCK,
PACKET_COMPRESSION,
LAST_BOOLEAN_CONFIG /* this must be the last one */
};

View File

@@ -20,7 +20,7 @@
#ifndef FS_CONST_H_0A49B5996F074465BF44B90F4F780E8B
#define FS_CONST_H_0A49B5996F074465BF44B90F4F780E8B
static constexpr int32_t NETWORKMESSAGE_MAXSIZE = 24590;
static constexpr int32_t NETWORKMESSAGE_MAXSIZE = 65500;
static constexpr int32_t MIN_MARKET_FEE = 20;
static constexpr int32_t MAX_MARKET_FEE = 100000;
@@ -378,6 +378,7 @@ enum PlayerFlags : uint64_t {
enum ReloadTypes_t : uint8_t {
RELOAD_TYPE_ALL,
RELOAD_TYPE_ACTIONS,
RELOAD_TYPE_AURAS,
RELOAD_TYPE_CHAT,
RELOAD_TYPE_COMMANDS,
RELOAD_TYPE_CONFIG,
@@ -393,8 +394,129 @@ enum ReloadTypes_t : uint8_t {
RELOAD_TYPE_QUESTS,
RELOAD_TYPE_RAIDS,
RELOAD_TYPE_SPELLS,
RELOAD_TYPE_SHADERS,
RELOAD_TYPE_TALKACTIONS,
RELOAD_TYPE_WEAPONS,
RELOAD_TYPE_WINGS,
};
// OTCv8 features (from src/client/const.h)
enum GameFeature {
GameProtocolChecksum = 1,
GameAccountNames = 2,
GameChallengeOnLogin = 3,
GamePenalityOnDeath = 4,
GameNameOnNpcTrade = 5,
GameDoubleFreeCapacity = 6,
GameDoubleExperience = 7,
GameTotalCapacity = 8,
GameSkillsBase = 9,
GamePlayerRegenerationTime = 10,
GameChannelPlayerList = 11,
GamePlayerMounts = 12,
GameEnvironmentEffect = 13,
GameCreatureEmblems = 14,
GameItemAnimationPhase = 15,
GameMagicEffectU16 = 16,
GamePlayerMarket = 17,
GameSpritesU32 = 18,
GameTileAddThingWithStackpos = 19,
GameOfflineTrainingTime = 20,
GamePurseSlot = 21,
GameFormatCreatureName = 22,
GameSpellList = 23,
GameClientPing = 24,
GameExtendedClientPing = 25,
GameDoubleHealth = 28,
GameDoubleSkills = 29,
GameChangeMapAwareRange = 30,
GameMapMovePosition = 31,
GameAttackSeq = 32,
GameBlueNpcNameColor = 33,
GameDiagonalAnimatedText = 34,
GameLoginPending = 35,
GameNewSpeedLaw = 36,
GameForceFirstAutoWalkStep = 37,
GameMinimapRemove = 38,
GameDoubleShopSellAmount = 39,
GameContainerPagination = 40,
GameThingMarks = 41,
GameLooktypeU16 = 42,
GamePlayerStamina = 43,
GamePlayerAddons = 44,
GameMessageStatements = 45,
GameMessageLevel = 46,
GameNewFluids = 47,
GamePlayerStateU16 = 48,
GameNewOutfitProtocol = 49,
GamePVPMode = 50,
GameWritableDate = 51,
GameAdditionalVipInfo = 52,
GameBaseSkillU16 = 53,
GameCreatureIcons = 54,
GameHideNpcNames = 55,
GameSpritesAlphaChannel = 56,
GamePremiumExpiration = 57,
GameBrowseField = 58,
GameEnhancedAnimations = 59,
GameOGLInformation = 60,
GameMessageSizeCheck = 61,
GamePreviewState = 62,
GameLoginPacketEncryption = 63,
GameClientVersion = 64,
GameContentRevision = 65,
GameExperienceBonus = 66,
GameAuthenticator = 67,
GameUnjustifiedPoints = 68,
GameSessionKey = 69,
GameDeathType = 70,
GameIdleAnimations = 71,
GameKeepUnawareTiles = 72,
GameIngameStore = 73,
GameIngameStoreHighlights = 74,
GameIngameStoreServiceType = 75,
GameAdditionalSkills = 76,
GameDistanceEffectU16 = 77,
GamePrey = 78,
GameDoubleMagicLevel = 79,
GameExtendedOpcode = 80,
GameMinimapLimitedToSingleFloor = 81,
GameSendWorldName = 82,
GameDoubleLevel = 83,
GameDoubleSoul = 84,
GameDoublePlayerGoodsMoney = 85,
GameCreatureWalkthrough = 86,
GameDoubleTradeMoney = 87,
GameSequencedPackets = 88,
GameTibia12Protocol = 89,
// 90-99 otclientv8 features
GameNewWalking = 90,
GameSlowerManualWalking = 91,
GameItemTooltip = 93,
GameBot = 95,
GameBiggerMapCache = 96,
GameForceLight = 97,
GameNoDebug = 98,
GameBotProtection = 99,
// Custom features for customer
GameFasterAnimations = 101,
GameCenteredOutfits = 102,
GameSendIdentifiers = 103,
GameWingsAndAura = 104,
GamePlayerStateU32 = 105,
GameOutfitShaders = 106,
// advanced features
GamePacketSizeU32 = 110,
GamePacketCompression = 111,
LastGameFeature = 120
};
enum ClientVersion_t : uint16_t {
@@ -417,6 +539,22 @@ static constexpr int32_t PSTRG_RESERVED_RANGE_SIZE = 10000000;
//[1000 - 1500];
static constexpr int32_t PSTRG_OUTFITS_RANGE_START = (PSTRG_RESERVED_RANGE_START + 1000);
static constexpr int32_t PSTRG_OUTFITS_RANGE_SIZE = 500;
//[2001 - 2011];
static constexpr int32_t PSTRG_MOUNTS_RANGE_START = (PSTRG_RESERVED_RANGE_START + 2001);
static constexpr int32_t PSTRG_MOUNTS_RANGE_SIZE = 10;
static constexpr int32_t PSTRG_MOUNTS_CURRENTMOUNT = (PSTRG_MOUNTS_RANGE_START + 10);
//[2012 - 2022];
static constexpr int32_t PSTRG_WINGS_RANGE_START = (PSTRG_RESERVED_RANGE_START + 2012);
static constexpr int32_t PSTRG_WINGS_RANGE_SIZE = 10;
static constexpr int32_t PSTRG_WINGS_CURRENTWINGS = (PSTRG_WINGS_RANGE_START + 10);
//[2023 - 2033];
static constexpr int32_t PSTRG_AURAS_RANGE_START = (PSTRG_RESERVED_RANGE_START + 2023);
static constexpr int32_t PSTRG_AURAS_RANGE_SIZE = 10;
static constexpr int32_t PSTRG_AURAS_CURRENTAURA = (PSTRG_AURAS_RANGE_START + 10);
//[2034 - 2044];
static constexpr int32_t PSTRG_SHADERS_RANGE_START = (PSTRG_RESERVED_RANGE_START + 2034);
static constexpr int32_t PSTRG_SHADERS_RANGE_SIZE = 10;
static constexpr int32_t PSTRG_SHADERS_CURRENTSHADER = (PSTRG_SHADERS_RANGE_START + 10);
#define IS_IN_KEYRANGE(key, range) (key >= PSTRG_##range##_START && ((key - PSTRG_##range##_START) <= PSTRG_##range##_SIZE))

View File

@@ -56,6 +56,7 @@ enum itemAttrTypes : uint32_t {
ITEM_ATTRIBUTE_DOORQUESTVALUE = 1 << 24,
ITEM_ATTRIBUTE_DOORLEVEL = 1 << 25,
ITEM_ATTRIBUTE_CHESTQUESTNUMBER = 1 << 26,
ITEM_ATTRIBUTE_AUTOOPEN = 1 << 27,
};
enum VipStatus_t : uint8_t {
@@ -93,6 +94,16 @@ enum OperatingSystem_t : uint8_t {
CLIENTOS_OTCLIENT_LINUX = 10,
CLIENTOS_OTCLIENT_WINDOWS = 11,
CLIENTOS_OTCLIENT_MAC = 12,
// by default OTCv8 uses CLIENTOS_WINDOWS for backward compatibility
// for correct value enable g_game.enableFeature(GameExtendedOpcode)
// in modules/game_features/features.lua
CLIENTOS_OTCLIENTV8_LINUX = 20,
CLIENTOS_OTCLIENTV8_WINDOWS = 21,
CLIENTOS_OTCLIENTV8_MAC = 22,
CLIENTOS_OTCLIENTV8_ANDROID = 23,
CLIENTOS_OTCLIENTV8_IOS = 24,
CLIENTOS_OTCLIENTV8_WEB = 25
};
enum AccountType_t : uint8_t {
@@ -379,6 +390,10 @@ enum ReturnValue {
struct Outfit_t {
uint16_t lookType = 0;
uint16_t lookTypeEx = 0;
uint16_t lookMount = 0;
uint16_t lookWings = 0;
uint16_t lookAura = 0;
uint16_t lookShader = 0;
uint8_t lookHead = 0;
uint8_t lookBody = 0;
uint8_t lookLegs = 0;

View File

@@ -106,6 +106,10 @@ void Game::setGameState(GameState_t newState)
raids.startup();
quests.loadFromXml();
mounts.loadFromXml();
auras.loadFromXml();
wings.loadFromXml();
shaders.loadFromXml();
loadMotdNum();
loadPlayersRecord();
@@ -1866,6 +1870,18 @@ void Game::playerReceivePingBack(uint32_t playerId)
player->sendPingBack();
}
void Game::playerReceiveNewPing(uint32_t playerId, uint16_t ping, uint16_t fps)
{
Player* player = getPlayerByID(playerId);
if (!player) {
return;
}
player->receivePing();
player->setLocalPing(ping);
player->setFPS(fps);
}
void Game::playerAutoWalk(uint32_t playerId, const std::forward_list<Direction>& listDir)
{
Player* player = getPlayerByID(playerId);
@@ -2906,6 +2922,17 @@ void Game::playerRequestOutfit(uint32_t playerId)
player->sendOutfitWindow();
}
void Game::playerToggleOutfitExtension(uint32_t playerId, int mount, int wings, int aura, int shader)
{
Player* player = getPlayerByID(playerId);
if (!player) {
return;
}
if (mount != -1)
player->toggleMount(mount == 1);
}
void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit)
{
if (!g_config.getBoolean(ConfigManager::ALLOW_CHANGEOUTFIT)) {
@@ -2917,6 +2944,74 @@ void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit)
return;
}
const Outfit* playerOutfit = Outfits::getInstance().getOutfitByLookType(player->getSex(), outfit.lookType);
if (!playerOutfit) {
outfit.lookMount = 0;
outfit.lookWings = 0;
outfit.lookAura = 0;
outfit.lookShader = 0;
}
if (outfit.lookMount != 0) {
Mount* mount = mounts.getMountByClientID(outfit.lookMount);
if (!mount) {
return;
}
if (!player->hasMount(mount)) {
return;
}
if (player->isMounted()) {
Mount* prevMount = mounts.getMountByID(player->getCurrentMount());
if (prevMount) {
changeSpeed(player, mount->speed - prevMount->speed);
}
player->setCurrentMount(mount->id);
}
else {
player->setCurrentMount(mount->id);
outfit.lookMount = 0;
}
}
else if (player->isMounted()) {
player->dismount();
}
if (outfit.lookWings != 0) {
Wing* wing = wings.getWingByID(outfit.lookWings);
if (!wing) {
return;
}
if (!player->hasWing(wing)) {
return;
}
}
if (outfit.lookAura != 0) {
Aura* aura = auras.getAuraByID(outfit.lookAura);
if (!aura) {
return;
}
if (!player->hasAura(aura)) {
return;
}
}
if (outfit.lookShader) {
Shader* shader = shaders.getShaderByID(outfit.lookShader);
if (!shader) {
return;
}
if (!player->hasShader(shader)) {
return;
}
}
if (player->canWear(outfit.lookType, outfit.lookAddons)) {
player->defaultOutfit = outfit;
@@ -5099,6 +5194,7 @@ bool Game::reload(ReloadTypes_t reloadType)
{
switch (reloadType) {
case RELOAD_TYPE_ACTIONS: return g_actions->reload();
case RELOAD_TYPE_AURAS: return auras.reload();
case RELOAD_TYPE_CHAT: return g_chat->load();
case RELOAD_TYPE_CONFIG: return g_config.reload();
case RELOAD_TYPE_CREATURESCRIPTS: return g_creatureEvents->reload();
@@ -5106,6 +5202,7 @@ bool Game::reload(ReloadTypes_t reloadType)
case RELOAD_TYPE_GLOBALEVENTS: return g_globalEvents->reload();
case RELOAD_TYPE_ITEMS: return Item::items.reload();
case RELOAD_TYPE_MONSTERS: return g_monsters.reload();
case RELOAD_TYPE_MOUNTS: return mounts.reload();
case RELOAD_TYPE_MOVEMENTS: return g_moveEvents->reload();
case RELOAD_TYPE_NPCS: {
Npcs::reload();
@@ -5114,7 +5211,7 @@ bool Game::reload(ReloadTypes_t reloadType)
case RELOAD_TYPE_QUESTS: return quests.reload();
case RELOAD_TYPE_RAIDS: return raids.reload() && raids.startup();
case RELOAD_TYPE_SHADERS: return shaders.reload();
case RELOAD_TYPE_SPELLS: {
if (!g_spells->reload()) {
std::cout << "[Error - Game::reload] Failed to reload spells." << std::endl;
@@ -5128,6 +5225,7 @@ bool Game::reload(ReloadTypes_t reloadType)
}
case RELOAD_TYPE_TALKACTIONS: return g_talkActions->reload();
case RELOAD_TYPE_WINGS: return wings.reload();
default: {
if (!g_spells->reload()) {
@@ -5151,6 +5249,10 @@ bool Game::reload(ReloadTypes_t reloadType)
g_talkActions->reload();
Item::items.reload();
quests.reload();
auras.reload();
mounts.reload();
wings.reload();
shaders.reload();
g_globalEvents->reload();
g_events->load();
g_chat->load();
@@ -5158,3 +5260,14 @@ bool Game::reload(ReloadTypes_t reloadType)
}
}
}
void Game::startProgressbar(Creature* creature, uint32_t duration, bool ltr)
{
SpectatorVec spectators;
map.getSpectators(spectators, creature->getPosition(), false, true);
for (Creature* spectator : spectators) {
if (Player* tmpPlayer = spectator->getPlayer()) {
tmpPlayer->sendProgressbar(creature->getID(), duration, ltr);
}
}
}

View File

@@ -32,6 +32,7 @@
#include "npc.h"
#include "wildcardtree.h"
#include "quests.h"
#include "shaders.h"
class ServiceManager;
class Creature;
@@ -360,6 +361,7 @@ class Game
void playerOpenPrivateChannel(uint32_t playerId, std::string& receiver);
void playerReceivePing(uint32_t playerId);
void playerReceivePingBack(uint32_t playerId);
void playerReceiveNewPing(uint32_t playerId, uint16_t ping, uint16_t fps);
void playerAutoWalk(uint32_t playerId, const std::forward_list<Direction>& listDir);
void playerStopAutoWalk(uint32_t playerId);
void playerUseItemEx(uint32_t playerId, const Position& fromPos, uint8_t fromStackPos,
@@ -399,6 +401,7 @@ class Game
void playerPassPartyLeadership(uint32_t playerId, uint32_t newLeaderId);
void playerLeaveParty(uint32_t playerId);
void playerEnableSharedPartyExperience(uint32_t playerId, bool sharedExpActive);
void playerToggleOutfitExtension(uint32_t playerId, int mount, int wings, int aura, int shader);
void playerProcessRuleViolationReport(uint32_t playerId, const std::string& name);
void playerCloseRuleViolationReport(uint32_t playerId, const std::string& name);
void playerCancelRuleViolationReport(uint32_t playerId);
@@ -503,10 +506,16 @@ class Game
void setBedSleeper(BedItem* bed, uint32_t guid);
void removeBedSleeper(uint32_t guid);
bool reload(ReloadTypes_t reloadType);
void startProgressbar(Creature* creature, uint32_t duration, bool ltr = true);
Auras auras;
Groups groups;
Map map;
Mounts mounts;
Raids raids;
Quests quests;
Wings wings;
Shaders shaders;
protected:
bool playerSaySpell(Player* player, SpeakClasses type, const std::string& text);

View File

@@ -525,7 +525,7 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result)
return true;
}
bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& propWriteStream)
bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& propWriteStream, std::map<Container*, int>& openContainers)
{
std::ostringstream ss;
@@ -540,6 +540,17 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList,
Item* item = it.second;
++runningId;
if (Container* container = item->getContainer()) {
auto it = openContainers.find(container);
if (it == openContainers.end()) {
container->resetAutoOpen();
}
else {
container->setAutoOpen(it->second);
}
queue.emplace_back(container, runningId);
}
propWriteStream.clear();
item->serializeAttr(propWriteStream);
@@ -550,10 +561,6 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList,
if (!query_insert.addRow(ss)) {
return false;
}
if (Container* container = item->getContainer()) {
queue.emplace_back(container, runningId);
}
}
while (!queue.empty()) {
@@ -567,6 +574,14 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList,
Container* subContainer = item->getContainer();
if (subContainer) {
auto it = openContainers.find(subContainer);
if (it == openContainers.end()) {
subContainer->resetAutoOpen();
}
else {
subContainer->setAutoOpen(it->second);
}
queue.emplace_back(subContainer, runningId);
}
@@ -749,6 +764,11 @@ bool IOLoginData::savePlayer(Player* player)
//item saving
query.str(std::string());
std::map<Container*, int> openContainers;
for (auto container : player->getOpenContainers()) {
if (!container.second.container) continue;
openContainers[container.second.container] = container.first;
}
query << "DELETE FROM `player_items` WHERE `player_id` = " << player->getGUID();
if (!db->executeQuery(query.str())) {
return false;
@@ -764,7 +784,7 @@ bool IOLoginData::savePlayer(Player* player)
}
}
if (!saveItems(player, itemList, itemsQuery, propWriteStream)) {
if (!saveItems(player, itemList, itemsQuery, propWriteStream, openContainers)) {
return false;
}
@@ -783,7 +803,7 @@ bool IOLoginData::savePlayer(Player* player)
itemList.emplace_back(it.first, it.second);
}
if (!saveItems(player, itemList, depotQuery, propWriteStream)) {
if (!saveItems(player, itemList, depotQuery, propWriteStream, openContainers)) {
return false;
}

View File

@@ -65,7 +65,7 @@ class IOLoginData
typedef std::map<uint32_t, std::pair<Item*, uint32_t>> ItemMap;
static void loadItems(ItemMap& itemMap, DBResult_ptr result);
static bool saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& stream);
static bool saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& stream, std::map<Container*, int>& openContainers);
};
#endif

View File

@@ -551,6 +551,17 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream)
break;
}
case ATTR_AUTOOPEN:
{
int8_t autoOpen;
if (!propStream.read<int8_t>(autoOpen)) {
return ATTR_READ_ERROR;
}
setIntAttr(ITEM_ATTRIBUTE_AUTOOPEN, autoOpen);
break;
}
case ATTR_KEYNUMBER: {
uint16_t keyNumber;
if (!propStream.read<uint16_t>(keyNumber)) {
@@ -788,6 +799,11 @@ void Item::serializeAttr(PropWriteStream& propWriteStream) const
propWriteStream.write<uint8_t>(getIntAttr(ITEM_ATTRIBUTE_SHOOTRANGE));
}
if (hasAttribute(ITEM_ATTRIBUTE_AUTOOPEN)) {
propWriteStream.write<uint8_t>(ATTR_AUTOOPEN);
propWriteStream.write<int8_t>(getIntAttr(ITEM_ATTRIBUTE_AUTOOPEN));
}
if (hasAttribute(ITEM_ATTRIBUTE_KEYNUMBER)) {
propWriteStream.write<uint8_t>(ATTR_KEYNUMBER);
propWriteStream.write<uint16_t>(getIntAttr(ITEM_ATTRIBUTE_KEYNUMBER));

View File

@@ -103,6 +103,7 @@ enum AttrTypes_t {
ATTR_DEFENSE = 35,
ATTR_ARMOR = 36,
ATTR_SHOOTRANGE = 37,
ATTR_AUTOOPEN = 38
};
enum Attr_ReadValue {
@@ -818,6 +819,22 @@ class Item : virtual public Thing
return !parent || parent->isRemoved();
}
int8_t getAutoOpen()
{
if (hasAttribute(ITEM_ATTRIBUTE_AUTOOPEN)) {
return getIntAttr(ITEM_ATTRIBUTE_AUTOOPEN);
}
return -1;
}
void setAutoOpen(int8_t value)
{
setIntAttr(ITEM_ATTRIBUTE_AUTOOPEN, value);
}
void resetAutoOpen()
{
removeAttribute(ITEM_ATTRIBUTE_AUTOOPEN);
}
protected:
std::string getWeightDescription(uint32_t weight) const;

View File

@@ -731,6 +731,9 @@ Position LuaScriptInterface::getPosition(lua_State* L, int32_t arg)
Outfit_t LuaScriptInterface::getOutfit(lua_State* L, int32_t arg)
{
Outfit_t outfit;
outfit.lookMount = getField<uint16_t>(L, arg, "lookMount");
outfit.lookWings = getField<uint16_t>(L, arg, "lookWings");
outfit.lookAura = getField<uint16_t>(L, arg, "lookAura");
outfit.lookAddons = getField<uint8_t>(L, arg, "lookAddons");
outfit.lookFeet = getField<uint8_t>(L, arg, "lookFeet");
@@ -741,7 +744,9 @@ Outfit_t LuaScriptInterface::getOutfit(lua_State* L, int32_t arg)
outfit.lookTypeEx = getField<uint8_t>(L, arg, "lookTypeEx");
outfit.lookType = getField<uint8_t>(L, arg, "lookType");
lua_pop(L, 6);
outfit.lookShader = getField<uint16_t>(L, arg, "lookShader");
lua_pop(L, 8);
return outfit;
}
@@ -878,7 +883,7 @@ void LuaScriptInterface::pushPosition(lua_State* L, const Position& position, in
void LuaScriptInterface::pushOutfit(lua_State* L, const Outfit_t& outfit)
{
lua_createtable(L, 0, 6);
lua_createtable(L, 0, 8);
setField(L, "lookType", outfit.lookType);
setField(L, "lookTypeEx", outfit.lookTypeEx);
setField(L, "lookHead", outfit.lookHead);
@@ -886,6 +891,10 @@ void LuaScriptInterface::pushOutfit(lua_State* L, const Outfit_t& outfit)
setField(L, "lookLegs", outfit.lookLegs);
setField(L, "lookFeet", outfit.lookFeet);
setField(L, "lookAddons", outfit.lookAddons);
setField(L, "lookMount", outfit.lookMount);
setField(L, "lookWings", outfit.lookWings);
setField(L, "lookAura", outfit.lookAura);
setField(L, "lookShader", outfit.lookShader);
}
#define registerEnum(value) { std::string enumName = #value; registerGlobalVariable(enumName.substr(enumName.find_last_of(':') + 1), value); }
@@ -1636,6 +1645,7 @@ void LuaScriptInterface::registerFunctions()
registerEnum(RELOAD_TYPE_ALL)
registerEnum(RELOAD_TYPE_ACTIONS)
registerEnum(RELOAD_TYPE_AURAS)
registerEnum(RELOAD_TYPE_CHAT)
registerEnum(RELOAD_TYPE_COMMANDS)
registerEnum(RELOAD_TYPE_CONFIG)
@@ -1651,8 +1661,10 @@ void LuaScriptInterface::registerFunctions()
registerEnum(RELOAD_TYPE_QUESTS)
registerEnum(RELOAD_TYPE_RAIDS)
registerEnum(RELOAD_TYPE_SPELLS)
registerEnum(RELOAD_TYPE_SHADERS)
registerEnum(RELOAD_TYPE_TALKACTIONS)
registerEnum(RELOAD_TYPE_WEAPONS)
registerEnum(RELOAD_TYPE_WINGS)
// _G
registerGlobalVariable("INDEX_WHEREEVER", INDEX_WHEREEVER);
@@ -2015,6 +2027,8 @@ void LuaScriptInterface::registerFunctions()
registerMethod("Creature", "getPathTo", LuaScriptInterface::luaCreatureGetPathTo);
registerMethod("Creature", "sendProgressbar", LuaScriptInterface::luaCreatureSetProgressbar);
// Player
registerClass("Player", "Creature", LuaScriptInterface::luaPlayerCreate);
registerMetaMethod("Player", "__eq", LuaScriptInterface::luaUserdataCompare);
@@ -2138,6 +2152,10 @@ void LuaScriptInterface::registerFunctions()
registerMethod("Player", "hasOutfit", LuaScriptInterface::luaPlayerHasOutfit);
registerMethod("Player", "sendOutfitWindow", LuaScriptInterface::luaPlayerSendOutfitWindow);
registerMethod("Player", "addMount", LuaScriptInterface::luaPlayerAddMount);
registerMethod("Player", "removeMount", LuaScriptInterface::luaPlayerRemoveMount);
registerMethod("Player", "hasMount", LuaScriptInterface::luaPlayerHasMount);
registerMethod("Player", "getPremiumDays", LuaScriptInterface::luaPlayerGetPremiumDays);
registerMethod("Player", "addPremiumDays", LuaScriptInterface::luaPlayerAddPremiumDays);
registerMethod("Player", "removePremiumDays", LuaScriptInterface::luaPlayerRemovePremiumDays);
@@ -8597,6 +8615,79 @@ int LuaScriptInterface::luaPlayerSendOutfitWindow(lua_State* L)
return 1;
}
int LuaScriptInterface::luaPlayerAddMount(lua_State* L) {
// player:addMount(mountId or mountName)
Player* player = getUserdata<Player>(L, 1);
if (!player) {
lua_pushnil(L);
return 1;
}
uint8_t mountId;
if (isNumber(L, 2)) {
mountId = getNumber<uint8_t>(L, 2);
}
else {
Mount* mount = g_game.mounts.getMountByName(getString(L, 2));
if (!mount) {
lua_pushnil(L);
return 1;
}
mountId = mount->id;
}
pushBoolean(L, player->tameMount(mountId));
return 1;
}
int LuaScriptInterface::luaPlayerRemoveMount(lua_State* L) {
// player:removeMount(mountId or mountName)
Player* player = getUserdata<Player>(L, 1);
if (!player) {
lua_pushnil(L);
return 1;
}
uint8_t mountId;
if (isNumber(L, 2)) {
mountId = getNumber<uint8_t>(L, 2);
}
else {
Mount* mount = g_game.mounts.getMountByName(getString(L, 2));
if (!mount) {
lua_pushnil(L);
return 1;
}
mountId = mount->id;
}
pushBoolean(L, player->untameMount(mountId));
return 1;
}
int LuaScriptInterface::luaPlayerHasMount(lua_State* L) {
// player:hasMount(mountId or mountName)
const Player* player = getUserdata<const Player>(L, 1);
if (!player) {
lua_pushnil(L);
return 1;
}
Mount* mount = nullptr;
if (isNumber(L, 2)) {
mount = g_game.mounts.getMountByID(getNumber<uint8_t>(L, 2));
}
else {
mount = g_game.mounts.getMountByName(getString(L, 2));
}
if (mount) {
pushBoolean(L, player->hasMount(mount));
}
else {
lua_pushnil(L);
}
return 1;
}
int LuaScriptInterface::luaPlayerGetPremiumDays(lua_State* L)
{
// player:getPremiumDays()
@@ -8823,10 +8914,14 @@ int LuaScriptInterface::luaPlayerGetClient(lua_State* L)
// player:getClient()
Player* player = getUserdata<Player>(L, 1);
if (player) {
lua_createtable(L, 0, 2);
lua_createtable(L, 0, 5);
setField(L, "version", player->getProtocolVersion());
setField(L, "os", player->getOperatingSystem());
} else {
setField(L, "otcv8", player->getOTCv8Version());
setField(L, "ping", player->getLocalPing());
setField(L, "fps", player->getFPS());
}
else {
lua_pushnil(L);
}
return 1;
@@ -11294,11 +11389,15 @@ int LuaScriptInterface::luaConditionSetSpeedDelta(lua_State* L)
int LuaScriptInterface::luaConditionSetOutfit(lua_State* L)
{
// condition:setOutfit(outfit)
// condition:setOutfit(lookTypeEx, lookType, lookHead, lookBody, lookLegs, lookFeet[, lookAddons])
// condition:setOutfit(lookTypeEx, lookType, lookHead, lookBody, lookLegs, lookFeet[, lookAddons[, lookMount, lookWings, lookAura, lookShader]])
Outfit_t outfit;
if (isTable(L, 2)) {
outfit = getOutfit(L, 2);
} else {
outfit.lookShader = getNumber<uint16_t>(L, 12, outfit.lookShader);
outfit.lookAura = getNumber<uint16_t>(L, 11, outfit.lookAura);
outfit.lookWings = getNumber<uint16_t>(L, 10, outfit.lookWings);
outfit.lookMount = getNumber<uint16_t>(L, 9, outfit.lookMount);
outfit.lookAddons = getNumber<uint8_t>(L, 8, outfit.lookAddons);
outfit.lookFeet = getNumber<uint8_t>(L, 7);
outfit.lookLegs = getNumber<uint8_t>(L, 6);
@@ -12277,3 +12376,20 @@ void LuaEnvironment::executeTimerEvent(uint32_t eventIndex)
luaL_unref(luaState, LUA_REGISTRYINDEX, parameter);
}
}
int LuaScriptInterface::luaCreatureSetProgressbar(lua_State* L)
{
// creature:sendProgressbar(duration, leftToRight)
Creature* creature = getUserdata<Creature>(L, 1);
uint32_t duration = getNumber<uint32_t>(L, 2);
bool ltr = getBoolean(L, 3);
if (creature) {
g_game.startProgressbar(creature, duration, ltr);
pushBoolean(L, true);
}
else {
lua_pushnil(L);
}
return 1;
}

View File

@@ -786,6 +786,8 @@ class LuaScriptInterface
static int luaCreatureGetPathTo(lua_State* L);
static int luaCreatureSetProgressbar(lua_State* L);
// Player
static int luaPlayerCreate(lua_State* L);
@@ -909,6 +911,10 @@ class LuaScriptInterface
static int luaPlayerHasOutfit(lua_State* L);
static int luaPlayerSendOutfitWindow(lua_State* L);
static int luaPlayerAddMount(lua_State* L);
static int luaPlayerRemoveMount(lua_State* L);
static int luaPlayerHasMount(lua_State* L);
static int luaPlayerGetPremiumDays(lua_State* L);
static int luaPlayerAddPremiumDays(lua_State* L);
static int luaPlayerRemovePremiumDays(lua_State* L);

View File

@@ -775,6 +775,23 @@ bool Monsters::loadMonster(const std::string& file, const std::string& monsterNa
std::cout << "[Warning - Monsters::loadMonster] Missing look type/typeex. " << file << std::endl;
}
if ((attr = node.attribute("mount"))) {
mType->info.outfit.lookMount = pugi::cast<uint16_t>(attr.value());
}
if ((attr = node.attribute("aura"))) {
mType->info.outfit.lookAura = pugi::cast<uint16_t>(attr.value());
}
if ((attr = node.attribute("wings"))) {
mType->info.outfit.lookWings = pugi::cast<uint16_t>(attr.value());
}
if ((attr = node.attribute("shader"))) {
Shader* shader = g_game.shaders.getShaderByName(attr.as_string());
mType->info.outfit.lookShader = shader ? shader->id : 0;
}
if ((attr = node.attribute("corpse"))) {
mType->info.lookcorpse = pugi::cast<uint16_t>(attr.value());
}

82
src/mounts.cpp Normal file
View File

@@ -0,0 +1,82 @@
/**
* The Forgotten Server - a free and open-source MMORPG server emulator
* Copyright (C) 2019 Mark Samman <mark.samman@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "otpch.h"
#include "mounts.h"
#include "pugicast.h"
#include "tools.h"
bool Mounts::reload()
{
mounts.clear();
return loadFromXml();
}
bool Mounts::loadFromXml()
{
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file("data/XML/mounts.xml");
if (!result) {
printXMLError("Error - Mounts::loadFromXml", "data/XML/mounts.xml", result);
return false;
}
for (auto mountNode : doc.child("mounts").children()) {
mounts.emplace_back(
static_cast<uint8_t>(pugi::cast<uint16_t>(mountNode.attribute("id").value())),
pugi::cast<uint16_t>(mountNode.attribute("clientid").value()),
mountNode.attribute("name").as_string(),
pugi::cast<int32_t>(mountNode.attribute("speed").value()),
mountNode.attribute("premium").as_bool()
);
}
mounts.shrink_to_fit();
return true;
}
Mount* Mounts::getMountByID(uint8_t id)
{
auto it = std::find_if(mounts.begin(), mounts.end(), [id](const Mount& mount) {
return mount.id == id;
});
return it != mounts.end() ? &*it : nullptr;
}
Mount* Mounts::getMountByName(const std::string& name) {
auto mountName = name.c_str();
for (auto& it : mounts) {
if (strcasecmp(mountName, it.name.c_str()) == 0) {
return &it;
}
}
return nullptr;
}
Mount* Mounts::getMountByClientID(uint16_t clientId)
{
auto it = std::find_if(mounts.begin(), mounts.end(), [clientId](const Mount& mount) {
return mount.clientId == clientId;
});
return it != mounts.end() ? &*it : nullptr;
}

52
src/mounts.h Normal file
View File

@@ -0,0 +1,52 @@
/**
* The Forgotten Server - a free and open-source MMORPG server emulator
* Copyright (C) 2019 Mark Samman <mark.samman@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef FS_MOUNTS_H_73716D11906A4C5C9F4A7B68D34C9BA6
#define FS_MOUNTS_H_73716D11906A4C5C9F4A7B68D34C9BA6
struct Mount
{
Mount(uint8_t id, uint16_t clientId, std::string name, int32_t speed, bool premium) :
name(std::move(name)), speed(speed), clientId(clientId), id(id), premium(premium) {}
std::string name;
int32_t speed;
uint16_t clientId;
uint8_t id;
bool premium;
};
class Mounts
{
public:
bool reload();
bool loadFromXml();
Mount* getMountByID(uint8_t id);
Mount* getMountByName(const std::string& name);
Mount* getMountByClientID(uint16_t clientId);
const std::vector<Mount>& getMounts() const {
return mounts;
}
private:
std::vector<Mount> mounts;
};
#endif

View File

@@ -51,7 +51,7 @@ Position NetworkMessage::getPosition()
void NetworkMessage::addString(const std::string& value)
{
size_t stringLen = value.length();
if (!canAdd(stringLen + 2) || stringLen > 8192) {
if (!canAdd(stringLen + 2)) {
return;
}
@@ -69,7 +69,7 @@ void NetworkMessage::addDouble(double value, uint8_t precision/* = 2*/)
void NetworkMessage::addBytes(const char* bytes, size_t size)
{
if (!canAdd(size) || size > 8192) {
if (!canAdd(size)) {
return;
}

View File

@@ -43,7 +43,14 @@ public:
add_header(info.length);
}
void addCryptoHeader() {
void addCryptoHeader(bool addChecksum, bool compression = false) {
if (compression) {
add_header<uint32_t>(0);
}
else if (addChecksum) {
//add_header(adlerChecksum(buffer + outputBufferStart, info.length));
}
writeMessageLength();
}

View File

@@ -528,6 +528,9 @@ void Player::addStorageValue(const uint32_t key, const int32_t value, const bool
);
return;
}
else if (IS_IN_KEYRANGE(key, MOUNTS_RANGE) || IS_IN_KEYRANGE(key, WINGS_RANGE) || IS_IN_KEYRANGE(key, AURAS_RANGE)) {
// do nothing
}
else {
std::cout << "Warning: unknown reserved key: " << key << " player: " << getName() << std::endl;
return;
@@ -684,6 +687,31 @@ void Player::sendPing()
}
}
void Player::autoOpenContainers()
{
for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; i++) {
Item* item = inventory[i];
if (!item) {
continue;
}
if (Container* container = item->getContainer()) {
if (container->getAutoOpen() >= 0) {
addContainer(container->getAutoOpen(), container);
onSendContainer(container);
}
for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) {
if (Container* subContainer = (*it)->getContainer()) {
if (subContainer->getAutoOpen() >= 0) {
addContainer(subContainer->getAutoOpen(), subContainer);
onSendContainer(subContainer);
}
}
}
}
}
}
Item* Player::getWriteItem(uint32_t& windowTextId, uint16_t& maxWriteLen)
{
windowTextId = this->windowTextId;
@@ -3034,6 +3062,11 @@ void Player::updateItemsLight(bool internal /*=false*/)
void Player::onAddCondition(ConditionType_t type)
{
Creature::onAddCondition(type);
if (type == CONDITION_OUTFIT) {
dismount();
}
sendIcons();
}
@@ -3825,6 +3858,256 @@ void Player::sendClosePrivate(uint16_t channelId)
}
}
uint8_t Player::getCurrentMount() const
{
int32_t value;
if (getStorageValue(PSTRG_MOUNTS_CURRENTMOUNT, value)) {
return value;
}
return 0;
}
void Player::setCurrentMount(uint8_t mountId)
{
addStorageValue(PSTRG_MOUNTS_CURRENTMOUNT, mountId);
}
bool Player::toggleMount(bool mount)
{
if ((OTSYS_TIME() - lastToggleMount) < 3000 && !wasMounted) {
sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED);
return false;
}
if (mount) {
if (isMounted()) {
return false;
}
if (!group->access && tile->hasFlag(TILESTATE_PROTECTIONZONE)) {
sendCancelMessage(RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE);
return false;
}
const Outfit* playerOutfit = Outfits::getInstance().getOutfitByLookType(getSex(), defaultOutfit.lookType);
if (!playerOutfit) {
return false;
}
uint8_t currentMountId = getCurrentMount();
if (currentMountId == 0) {
sendOutfitWindow();
return false;
}
Mount* currentMount = g_game.mounts.getMountByID(currentMountId);
if (!currentMount) {
return false;
}
if (!hasMount(currentMount)) {
setCurrentMount(0);
sendOutfitWindow();
return false;
}
if (currentMount->premium && !isPremium()) {
sendCancelMessage(RETURNVALUE_YOUNEEDPREMIUMACCOUNT);
return false;
}
if (hasCondition(CONDITION_OUTFIT)) {
sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
return false;
}
defaultOutfit.lookMount = currentMount->clientId;
if (currentMount->speed != 0) {
g_game.changeSpeed(this, currentMount->speed);
}
}
else {
if (!isMounted()) {
return false;
}
dismount();
}
g_game.internalCreatureChangeOutfit(this, defaultOutfit);
lastToggleMount = OTSYS_TIME();
return true;
}
bool Player::tameMount(uint8_t mountId)
{
if (!g_game.mounts.getMountByID(mountId)) {
return false;
}
const uint8_t tmpMountId = mountId - 1;
const uint32_t key = PSTRG_MOUNTS_RANGE_START + (tmpMountId / 31);
int32_t value;
if (getStorageValue(key, value)) {
value |= (1 << (tmpMountId % 31));
}
else {
value = (1 << (tmpMountId % 31));
}
addStorageValue(key, value);
return true;
}
bool Player::untameMount(uint8_t mountId)
{
if (!g_game.mounts.getMountByID(mountId)) {
return false;
}
const uint8_t tmpMountId = mountId - 1;
const uint32_t key = PSTRG_MOUNTS_RANGE_START + (tmpMountId / 31);
int32_t value;
if (!getStorageValue(key, value)) {
return true;
}
value &= ~(1 << (tmpMountId % 31));
addStorageValue(key, value);
if (getCurrentMount() == mountId) {
if (isMounted()) {
dismount();
g_game.internalCreatureChangeOutfit(this, defaultOutfit);
}
setCurrentMount(0);
}
return true;
}
bool Player::hasMount(const Mount* mount) const
{
if (isAccessPlayer()) {
return true;
}
if (mount->premium && !isPremium()) {
return false;
}
const uint8_t tmpMountId = mount->id - 1;
int32_t value;
if (!getStorageValue(PSTRG_MOUNTS_RANGE_START + (tmpMountId / 31), value)) {
return false;
}
return ((1 << (tmpMountId % 31)) & value) != 0;
}
void Player::dismount()
{
Mount* mount = g_game.mounts.getMountByID(getCurrentMount());
if (mount && mount->speed > 0) {
g_game.changeSpeed(this, -mount->speed);
}
defaultOutfit.lookMount = 0;
}
bool Player::hasWing(const Wing* wing) const
{
if (isAccessPlayer()) {
return true;
}
if (wing->premium && !isPremium()) {
return false;
}
const uint8_t tmpWingId = wing->id - 1;
int32_t value;
if (!getStorageValue(PSTRG_WINGS_RANGE_START + (tmpWingId / 31), value)) {
return false;
}
return ((1 << (tmpWingId % 31)) & value) != 0;
}
uint8_t Player::getCurrentWing() const
{
int32_t value;
if (getStorageValue(PSTRG_WINGS_CURRENTWINGS, value)) {
return value;
}
return 0;
}
void Player::setCurrentWing(uint8_t wingId)
{
addStorageValue(PSTRG_WINGS_CURRENTWINGS, wingId);
}
bool Player::hasAura(const Aura* aura) const
{
if (isAccessPlayer()) {
return true;
}
if (aura->premium && !isPremium()) {
return false;
}
const uint8_t tmpAuraId = aura->id - 1;
int32_t value;
if (!getStorageValue(PSTRG_AURAS_RANGE_START + (tmpAuraId / 31), value)) {
return false;
}
return ((1 << (tmpAuraId % 31)) & value) != 0;
}
uint8_t Player::getCurrentAura() const
{
int32_t value;
if (getStorageValue(PSTRG_AURAS_CURRENTAURA, value)) {
return value;
}
return 0;
}
void Player::setCurrentAura(uint8_t auraId)
{
addStorageValue(PSTRG_AURAS_CURRENTAURA, auraId);
}
bool Player::hasShader(const Shader* shader) const
{
if (isAccessPlayer()) {
return true;
}
if (shader->premium && !isPremium()) {
return false;
}
const uint8_t tmpShaderId = shader->id - 1;
int32_t value;
if (!getStorageValue(PSTRG_SHADERS_RANGE_START + (tmpShaderId / 31), value)) {
return false;
}
return ((1 << (tmpShaderId % 31)) & value) != 0;
}
bool Player::addOfflineTrainingTries(skills_t skill, uint64_t tries)
{
if (tries == 0 || skill == SKILL_LEVEL) {

View File

@@ -33,6 +33,10 @@
#include "guild.h"
#include "groups.h"
#include "town.h"
#include "mounts.h"
#include "auras.h"
#include "wings.h"
#include "shaders.h"
class BehaviourDatabase;
class House;
@@ -131,6 +135,42 @@ class Player final : public Creature, public Cylinder
}
std::string getDescription(int32_t lookDistance) const final;
uint8_t getCurrentMount() const;
void setCurrentMount(uint8_t mountId);
bool isMounted() const
{
return defaultOutfit.lookMount != 0;
}
bool hasMount() const
{
return defaultOutfit.lookMount != 0;
}
bool hasAura() const
{
return defaultOutfit.lookAura != 0;
}
bool hasWings() const
{
return defaultOutfit.lookWings != 0;
}
bool hasShader() const
{
return defaultOutfit.lookShader != 0;
}
bool toggleMount(bool mount);
bool tameMount(uint8_t mountId);
bool untameMount(uint8_t mountId);
bool hasMount(const Mount* mount) const;
void dismount();
bool hasWing(const Wing* wing) const;
uint8_t getCurrentAura() const;
void setCurrentAura(uint8_t auraId);
bool hasAura(const Aura* aura) const;
uint8_t getCurrentWing() const;
void setCurrentWing(uint8_t wingId);
bool hasShader(const Shader* shader) const;
void setGUID(uint32_t guid) {
this->guid = guid;
}
@@ -761,6 +801,7 @@ class Player final : public Creature, public Cylinder
client->sendInventoryItem(slot, item);
}
}
void autoOpenContainers();
//event methods
void onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem,
@@ -997,10 +1038,39 @@ class Player final : public Creature, public Cylinder
}
}
void sendProgressbar(uint32_t id, uint32_t duration, bool ltr = true) {
if (client) {
client->sendProgressbar(id, duration, ltr);
}
}
void receivePing() {
lastPong = OTSYS_TIME();
}
void setFPS(uint16_t value)
{
fps = value;
}
void setLocalPing(uint16_t value)
{
localPing = value;
}
uint16_t getFPS() const
{
return fps;
}
uint16_t getLocalPing() const
{
return localPing;
}
uint16_t getOTCv8Version() const
{
if (client)
return client->otclientV8;
return 0;
}
void onThink(uint32_t interval) final;
void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) final;
@@ -1025,6 +1095,10 @@ class Player final : public Creature, public Cylinder
void learnInstantSpell(const std::string& spellName);
void forgetInstantSpell(const std::string& spellName);
bool hasLearnedInstantSpell(const std::string& spellName) const;
const std::map<uint8_t, OpenContainer>& getOpenContainers() const
{
return openContainers;
}
protected:
std::forward_list<Condition*> getMuteConditions() const;
@@ -1115,6 +1189,7 @@ class Player final : public Creature, public Cylinder
int64_t lastPong;
int64_t nextAction = 0;
int64_t earliestAttackTime = 0;
int64_t lastToggleMount = 0;
BedItem* bedItem = nullptr;
Guild* guild = nullptr;
@@ -1162,6 +1237,8 @@ class Player final : public Creature, public Cylinder
uint16_t staminaMinutes = 3360;
uint16_t maxWriteLen = 0;
uint16_t localPing = 0;
uint16_t fps = 0;
int16_t lastDepotId = -1;
uint8_t soul = 0;
@@ -1179,6 +1256,7 @@ class Player final : public Creature, public Cylinder
bool secureMode = false;
bool inMarket = false;
bool wasMounted = false;
bool ghostMode = false;
bool pzLocked = false;
bool isConnecting = false;

View File

@@ -25,14 +25,27 @@
extern RSA g_RSA;
Protocol::~Protocol()
{
if (compression) {
deflateEnd(&zstream);
}
}
void Protocol::onSendMessage(const OutputMessage_ptr& msg) const
{
if (!rawMessages) {
bool compressed = false;
if (compression && msg->getLength() > 64) {
compress(*msg);
compressed = true;
}
msg->writeMessageLength();
if (encryptionEnabled) {
XTEA_encrypt(*msg);
msg->addCryptoHeader();
msg->addCryptoHeader(checksumEnabled, compressed);
}
}
}
@@ -153,3 +166,34 @@ uint32_t Protocol::getIP() const
return 0;
}
void Protocol::enableCompression()
{
if (compression)
return;
if (deflateInit2(&zstream, 6, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY) != Z_OK) {
std::cerr << "ZLIB initialization error: " << (zstream.msg ? zstream.msg : "unknown") << std::endl;
}
compression = true;
}
void Protocol::compress(OutputMessage& msg) const
{
static thread_local std::vector<uint8_t> buffer(NETWORKMESSAGE_MAXSIZE);
zstream.next_in = msg.getOutputBuffer();
zstream.avail_in = msg.getLength();
zstream.next_out = buffer.data();
zstream.avail_out = buffer.size();
if (deflate(&zstream, Z_SYNC_FLUSH) != Z_OK) {
std::cerr << "ZLIB deflate error: " << (zstream.msg ? zstream.msg : "unknown") << std::endl;
return;
}
int finalSize = buffer.size() - zstream.avail_out - 4;
if (finalSize < 0) {
std::cerr << "Packet compression error: " << (zstream.msg ? zstream.msg : "unknown") << std::endl;
return;
}
msg.reset();
msg.addBytes((const char*)buffer.data(), finalSize);
}

View File

@@ -21,12 +21,13 @@
#define FS_PROTOCOL_H_D71405071ACF4137A4B1203899DE80E1
#include "connection.h"
#include <zlib.h>
class Protocol : public std::enable_shared_from_this<Protocol>
{
public:
explicit Protocol(Connection_ptr connection) : connection(connection), key(), encryptionEnabled(false), rawMessages(false) {}
virtual ~Protocol() = default;
virtual ~Protocol();
// non-copyable
Protocol(const Protocol&) = delete;
@@ -74,6 +75,9 @@ protected:
void setXTEAKey(const uint32_t* key) {
memcpy(this->key, key, sizeof(*key) * 4);
}
void disableChecksum() {
checksumEnabled = false;
}
void XTEA_encrypt(OutputMessage& msg) const;
bool XTEA_decrypt(NetworkMessage& msg) const;
@@ -82,16 +86,20 @@ protected:
void setRawMessages(bool value) {
rawMessages = value;
}
void enableCompression();
virtual void release() {}
friend class Connection;
OutputMessage_ptr outputBuffer;
private:
void compress(OutputMessage& msg) const;
const ConnectionWeak_ptr connection;
uint32_t key[4];
bool encryptionEnabled;
bool rawMessages;
bool encryptionEnabled = false;
bool checksumEnabled = true;
bool rawMessages = false;
bool compression = false;
mutable z_stream zstream = { 0 };
};
#endif

View File

@@ -56,6 +56,17 @@ void ProtocolGame::release()
void ProtocolGame::login(const std::string& name, uint32_t accountId, OperatingSystem_t operatingSystem, bool isFake)
{
// OTCv8 features and extended opcodes
if (otclientV8 || operatingSystem >= CLIENTOS_OTCLIENT_LINUX) {
if (otclientV8)
sendFeatures();
NetworkMessage opcodeMessage;
opcodeMessage.addByte(0x32);
opcodeMessage.addByte(0x00);
opcodeMessage.add<uint16_t>(0x00);
writeToOutputBuffer(opcodeMessage);
}
//dispatcher thread
Player* foundPlayer = g_game.getPlayerByName(name);
if (!foundPlayer || g_config.getBoolean(ConfigManager::ALLOW_CLONES)) {
@@ -145,6 +156,8 @@ void ProtocolGame::login(const std::string& name, uint32_t accountId, OperatingS
}
}
player->autoOpenContainers();
if (operatingSystem >= CLIENTOS_OTCLIENT_LINUX) {
player->registerCreatureEvent("ExtendedOpcode");
}
@@ -197,6 +210,7 @@ void ProtocolGame::connect(uint32_t playerId, OperatingSystem_t operatingSystem)
player->client = getThis();
sendAddCreature(player, player->getPosition(), 0, false);
player->autoOpenContainers();
player->lastIP = player->getIP();
player->lastLoginSaved = std::max<time_t>(time(nullptr), player->lastLoginSaved + 1);
acceptPackets = true;
@@ -263,20 +277,18 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg)
enableXTEAEncryption();
setXTEAKey(key);
if (operatingSystem >= CLIENTOS_OTCLIENT_LINUX) {
NetworkMessage opcodeMessage;
opcodeMessage.addByte(0x32);
opcodeMessage.addByte(0x00);
opcodeMessage.add<uint16_t>(0x00);
writeToOutputBuffer(opcodeMessage);
}
msg.skipBytes(1); // gamemaster flag
uint32_t accountNumber = msg.get<uint32_t>();
std::string characterName = msg.getString();
std::string password = msg.getString();
// OTCv8 version detection
uint16_t otcV8StringLength = msg.get<uint16_t>();
if (otcV8StringLength == 5 && msg.getString(5) == "OTCv8") {
otclientV8 = msg.get<uint16_t>(); // 253, 260, 261, ...
}
/*if (version < CLIENT_VERSION_MIN || version > CLIENT_VERSION_MAX) {
//sendUpdateRequest();
disconnectClient("Use Tibia 7.72 to login!");
@@ -338,8 +350,31 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg)
void ProtocolGame::onConnect()
{
// Checksum missimplementation
/* auto output = OutputMessagePool::getOutputMessage();
static std::random_device rd;
static std::ranlux24 generator(rd());
static std::uniform_int_distribution<uint16_t> randNumber(0x00, 0xFF);
// Skip checksum
output->skipBytes(sizeof(uint32_t));
// Packet length & type
output->add<uint16_t>(0x0006);
output->addByte(0x1F);
// Add timestamp & random number
challengeTimestamp = static_cast<uint32_t>(time(nullptr));
output->add<uint32_t>(challengeTimestamp);
challengeRandom = randNumber(generator);
output->addByte(challengeRandom);
// Go back and write checksum
output->skipBytes(-12);
output->add<uint32_t>(adlerChecksum(output->getOutputBuffer() + sizeof(uint32_t), 8));
send(output); */
}
void ProtocolGame::disconnectClient(const std::string& message) const
@@ -390,6 +425,7 @@ void ProtocolGame::parsePacket(NetworkMessage& msg)
case 0x1D: addGameTask(&Game::playerReceivePingBack, player->getID()); break;
case 0x1E: addGameTask(&Game::playerReceivePing, player->getID()); break;
case 0x32: parseExtendedOpcode(msg); break; //otclient extended opcode
case 0x40: parseNewPing(msg); break;
case 0x64: parseAutoWalk(msg); break;
case 0x65: addGameTask(&Game::playerMove, player->getID(), DIRECTION_NORTH); break;
case 0x66: addGameTask(&Game::playerMove, player->getID(), DIRECTION_EAST); break;
@@ -445,6 +481,7 @@ void ProtocolGame::parsePacket(NetworkMessage& msg)
case 0xCC: parseSeekInContainer(msg); break;
case 0xD2: addGameTask(&Game::playerRequestOutfit, player->getID()); break;
case 0xD3: parseSetOutfit(msg); break;
case 0xD4: parseToggleMount(msg); break;
case 0xDC: parseAddVip(msg); break;
case 0xDD: parseRemoveVip(msg); break;
case 0xE6: parseBugReport(msg); break;
@@ -735,9 +772,27 @@ void ProtocolGame::parseSetOutfit(NetworkMessage& msg)
newOutfit.lookLegs = msg.getByte();
newOutfit.lookFeet = msg.getByte();
newOutfit.lookAddons = msg.getByte();
newOutfit.lookMount = msg.get<uint16_t>();
newOutfit.lookWings = otclientV8 ? msg.get<uint16_t>() : 0;
newOutfit.lookAura = otclientV8 ? msg.get<uint16_t>() : 0;
std::string shaderName = otclientV8 ? msg.getString() : "";
Shader* shader = g_game.shaders.getShaderByName(shaderName);
newOutfit.lookShader = shader ? shader->id : 0;
addGameTask(&Game::playerChangeOutfit, player->getID(), newOutfit);
}
void ProtocolGame::parseToggleMount(NetworkMessage& msg)
{
int mount = msg.get<int8_t>();
int wings = -1, aura = -1, shader = -1;
if (otclientV8 >= 254) {
wings = msg.get<int8_t>();
aura = msg.get<int8_t>();
shader = msg.get<int8_t>();
}
addGameTask(&Game::playerToggleOutfitExtension, player->getID(), mount, wings, aura, shader);
}
void ProtocolGame::parseUseItem(NetworkMessage& msg)
{
Position pos = msg.getPosition();
@@ -2127,9 +2182,13 @@ void ProtocolGame::sendOutfitWindow()
msg.addByte(0xC8);
Outfit_t currentOutfit = player->getDefaultOutfit();
Mount* currentMount = g_game.mounts.getMountByID(player->getCurrentMount());
if (currentMount) {
currentOutfit.lookMount = currentMount->clientId;
}
AddOutfit(msg, currentOutfit);
const ClientVersion_t clientVersion = g_game.getClientVersion();
std::vector<ProtocolOutfit> protocolOutfits;
if (player->isAccessPlayer()) {
static const std::string gamemasterOutfitName = "Gamemaster";
@@ -2145,22 +2204,72 @@ void ProtocolGame::sendOutfitWindow()
}
protocolOutfits.emplace_back(outfit.name, outfit.lookType, addons);
if (CLIENT_VERSION_780 <= clientVersion && clientVersion <= CLIENT_VERSION_792) {
if (protocolOutfits.size() == 20) { // Game client doesn't allow more than 15 outfits in 780-792
break;
}
if (protocolOutfits.size() == 100) { // Game client doesn't allow more than 100 outfits
break;
}
}
msg.addByte(protocolOutfits.size());
for (const ProtocolOutfit& outfit : protocolOutfits) {
msg.add<uint16_t>(outfit.lookType);
if (clientVersion > CLIENT_VERSION_781) {
msg.addString(outfit.name);
}
msg.addString(outfit.name);
msg.addByte(outfit.addons);
}
std::vector<const Mount*> mounts;
for (const Mount& mount : g_game.mounts.getMounts()) {
if (player->hasMount(&mount)) {
mounts.push_back(&mount);
}
}
msg.addByte(mounts.size());
for (const Mount* mount : mounts) {
msg.add<uint16_t>(mount->clientId);
msg.addString(mount->name);
}
if (otclientV8) {
std::vector<const Wing*> wings;
for (const Wing& wing : g_game.wings.getWings()) {
if (player->hasWing(&wing)) {
wings.push_back(&wing);
}
}
msg.addByte(wings.size());
for (const Wing* wing : wings) {
msg.add<uint16_t>(wing->clientId);
msg.addString(wing->name);
}
std::vector<const Aura*> auras;
for (const Aura& aura : g_game.auras.getAuras()) {
if (player->hasAura(&aura)) {
auras.push_back(&aura);
}
}
msg.addByte(auras.size());
for (const Aura* aura : auras) {
msg.add<uint16_t>(aura->clientId);
msg.addString(aura->name);
}
std::vector<const Shader*> shaders;
for (const Shader& shader : g_game.shaders.getShaders()) {
if (player->hasShader(&shader)) {
shaders.push_back(&shader);
}
}
msg.addByte(shaders.size());
for (const Shader* shader : shaders) {
msg.add<uint16_t>(shader->id);
msg.addString(shader->name);
}
}
writeToOutputBuffer(msg);
}
@@ -2284,9 +2393,18 @@ void ProtocolGame::AddOutfit(NetworkMessage& msg, const Outfit_t& outfit)
msg.addByte(outfit.lookLegs);
msg.addByte(outfit.lookFeet);
msg.addByte(outfit.lookAddons);
} else {
}
else {
msg.addItemId(outfit.lookTypeEx);
}
msg.add<uint16_t>(outfit.lookMount);
if (otclientV8) {
msg.add<uint16_t>(outfit.lookWings);
msg.add<uint16_t>(outfit.lookAura);
Shader* shader = g_game.shaders.getShaderByID(outfit.lookShader);
msg.addString(shader ? shader->name : "");
}
}
void ProtocolGame::AddWorldLight(NetworkMessage& msg, const LightInfo& lightInfo)
@@ -2413,4 +2531,76 @@ void ProtocolGame::parseExtendedOpcode(NetworkMessage& msg)
// process additional opcodes via lua script event
addGameTask(&Game::parsePlayerExtendedOpcode, player->getID(), opcode, buffer);
}
// OTCv8
void ProtocolGame::sendFeatures()
{
if (!otclientV8)
return;
std::map<GameFeature, bool> features;
// place for non-standard OTCv8 features
//features[GameExtendedOpcode] = true;
//features[GameChangeMapAwareRange] = true;
//features[GameNewWalking] = true;
//features[GameEnvironmentEffect] = false; // disable it, useless 2 byte with every tile
//features[GameExtendedClientPing] = true;
//features[GameItemTooltip] = true; // fully available from version 2.6
//features[GameWingsAndAura] = true;
//features[GameOutfitShaders] = true;
// packet compression
// we don't send feature, because feature assumes all packets are compressed
// if adler32 is enabled then compression can be detected automaticly, just adlre32 must be 0
if (g_config.getBoolean(ConfigManager::PACKET_COMPRESSION)) {
enableCompression();
}
if (features.empty())
return;
auto msg = getOutputBuffer(1024);
msg->addByte(0x43);
msg->add<uint16_t>(features.size());
for (auto& feature : features) {
msg->addByte((uint8_t)feature.first);
msg->addByte(feature.second ? 1 : 0);
}
send(std::move(getCurrentBuffer())); // send this packet immediately
}
void ProtocolGame::parseNewPing(NetworkMessage& msg)
{
uint32_t pingId = msg.get<uint32_t>();
uint16_t localPing = msg.get<uint16_t>();
uint16_t fps = msg.get<uint16_t>();
addGameTask(&Game::playerReceiveNewPing, player->getID(), localPing, fps);
g_dispatcher.addTask(createTask(std::bind(&ProtocolGame::sendNewPing, getThis(), pingId)));
}
void ProtocolGame::sendNewPing(uint32_t pingId)
{
if (!otclientV8)
return;
NetworkMessage msg;
msg.addByte(0x40);
msg.add<uint32_t>(pingId);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendProgressbar(uint32_t id, uint32_t duration, bool ltr)
{
if (!otclientV8 || otclientV8 < 260)
return;
NetworkMessage msg;
msg.addByte(0x3b);
msg.add<uint32_t>(id);
msg.add<uint32_t>(duration);
msg.addByte(ltr);
writeToOutputBuffer(msg);
}

View File

@@ -52,7 +52,8 @@ class ProtocolGame final : public Protocol
// static protocol information
enum { server_sends_first = true };
enum { protocol_identifier = 0 }; // Not required as we send first
enum { use_checksum = true };
static const char* protocol_name() {
return "gameworld protocol";
}
@@ -90,6 +91,7 @@ class ProtocolGame final : public Protocol
//Parse methods
void parseAutoWalk(NetworkMessage& msg);
void parseSetOutfit(NetworkMessage& msg);
void parseToggleMount(NetworkMessage& msg);
void parseSay(NetworkMessage& msg);
void parseLookAt(NetworkMessage& msg);
void parseLookInBattleList(NetworkMessage& msg);
@@ -265,6 +267,12 @@ class ProtocolGame final : public Protocol
//otclient
void parseExtendedOpcode(NetworkMessage& msg);
//OTCv8
void sendFeatures();
void sendProgressbar(uint32_t id, uint32_t duration, bool ltr = true);
void parseNewPing(NetworkMessage& msg);
void sendNewPing(uint32_t pingId);
friend class Player;
// Helpers so we don't need to bind every time
@@ -289,6 +297,7 @@ class ProtocolGame final : public Protocol
bool debugAssertSent = false;
bool acceptPackets = false;
uint16_t otclientV8 = 0;
};
#endif

View File

@@ -193,6 +193,13 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg)
return;
}
// OTCv8 version detection
uint16_t otclientV8 = 0;
uint16_t otcV8StringLength = msg.get<uint16_t>();
if (otcV8StringLength == 5 && msg.getString(5) == "OTCv8") {
otclientV8 = msg.get<uint16_t>(); // 253, 260, 261, ...
}
auto thisPtr = std::static_pointer_cast<ProtocolLogin>(shared_from_this());
g_dispatcher.addTask(createTask(std::bind(&ProtocolLogin::getCharacterList, thisPtr, accountNumber, password, version)));
}

View File

@@ -32,6 +32,7 @@ class ProtocolLogin : public Protocol
// static protocol information
enum {server_sends_first = false};
enum {protocol_identifier = 0x01};
enum { use_checksum = true };
static const char* protocol_name() {
return "login protocol";
}

View File

@@ -29,6 +29,7 @@ class ProtocolStatus final : public Protocol
// static protocol information
enum {server_sends_first = false};
enum {protocol_identifier = 0xFF};
enum { use_checksum = false };
static const char* protocol_name() {
return "status protocol";
}

52
src/shaders.cpp Normal file
View File

@@ -0,0 +1,52 @@
#include "otpch.h"
#include "shaders.h"
#include "pugicast.h"
#include "tools.h"
bool Shaders::reload()
{
shaders.clear();
return loadFromXml();
}
bool Shaders::loadFromXml()
{
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file("data/XML/shaders.xml");
if (!result) {
printXMLError("Error - Shaders::loadFromXml", "data/XML/shaders.xml", result);
return false;
}
for (auto shaderNode : doc.child("shaders").children()) {
shaders.emplace_back(
static_cast<uint8_t>(pugi::cast<uint16_t>(shaderNode.attribute("id").value())),
shaderNode.attribute("name").as_string(),
shaderNode.attribute("premium").as_bool()
);
}
shaders.shrink_to_fit();
return true;
}
Shader* Shaders::getShaderByID(uint8_t id)
{
auto it = std::find_if(shaders.begin(), shaders.end(), [id](const Shader& shader) {
return shader.id == id;
});
return it != shaders.end() ? &*it : nullptr;
}
Shader* Shaders::getShaderByName(const std::string& name) {
auto shaderName = name.c_str();
for (auto& it : shaders) {
if (strcasecmp(shaderName, it.name.c_str()) == 0) {
return &it;
}
}
return nullptr;
}

30
src/shaders.h Normal file
View File

@@ -0,0 +1,30 @@
#ifndef FS_SHADERS_H
#define FS_SHADERS_H
struct Shader
{
Shader(uint8_t id, std::string name, bool premium) :
name(std::move(name)), id(id), premium(premium) {}
uint8_t id;
std::string name;
bool premium;
};
class Shaders
{
public:
bool reload();
bool loadFromXml();
Shader* getShaderByID(uint8_t id);
Shader* getShaderByName(const std::string& name);
const std::vector<Shader>& getShaders() const {
return shaders;
}
private:
std::vector<Shader> shaders;
};
#endif

View File

@@ -890,6 +890,31 @@ std::string getSkillName(uint8_t skillid)
}
}
uint32_t adlerChecksum(const uint8_t* data, size_t length)
{
if (length > NETWORKMESSAGE_MAXSIZE) {
return 0;
}
const uint16_t adler = 65521;
uint32_t a = 1, b = 0;
while (length > 0) {
size_t tmp = length > 5552 ? 5552 : length;
length -= tmp;
do {
a += *data++;
b += a;
} while (--tmp);
a %= adler;
b %= adler;
}
return (b << 16) | a;
}
std::string ucfirst(std::string str)
{

View File

@@ -85,6 +85,8 @@ std::string getCombatName(CombatType_t combatType);
std::string getSkillName(uint8_t skillid);
uint32_t adlerChecksum(const uint8_t* data, size_t length);
std::string ucfirst(std::string str);
std::string ucwords(std::string str);
bool booleanString(const std::string& str);

63
src/wings.cpp Normal file
View File

@@ -0,0 +1,63 @@
#include "otpch.h"
#include "wings.h"
#include "pugicast.h"
#include "tools.h"
bool Wings::reload()
{
wings.clear();
return loadFromXml();
}
bool Wings::loadFromXml()
{
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file("data/XML/wings.xml");
if (!result) {
printXMLError("Error - Wings::loadFromXml", "data/XML/wings.xml", result);
return false;
}
for (auto wingNode : doc.child("wings").children()) {
wings.emplace_back(
static_cast<uint8_t>(pugi::cast<uint16_t>(wingNode.attribute("id").value())),
pugi::cast<uint16_t>(wingNode.attribute("clientid").value()),
wingNode.attribute("name").as_string(),
pugi::cast<int32_t>(wingNode.attribute("speed").value()),
wingNode.attribute("premium").as_bool()
);
}
wings.shrink_to_fit();
return true;
}
Wing* Wings::getWingByID(uint8_t id)
{
auto it = std::find_if(wings.begin(), wings.end(), [id](const Wing& wing) {
return wing.id == id;
});
return it != wings.end() ? &*it : nullptr;
}
Wing* Wings::getWingByName(const std::string& name) {
auto wingName = name.c_str();
for (auto& it : wings) {
if (strcasecmp(wingName, it.name.c_str()) == 0) {
return &it;
}
}
return nullptr;
}
Wing* Wings::getWingByClientID(uint16_t clientId)
{
auto it = std::find_if(wings.begin(), wings.end(), [clientId](const Wing& wing) {
return wing.clientId == clientId;
});
return it != wings.end() ? &*it : nullptr;
}

33
src/wings.h Normal file
View File

@@ -0,0 +1,33 @@
#ifndef FS_WINGS_H
#define FS_WINGS_H
struct Wing
{
Wing(uint8_t id, uint16_t clientId, std::string name, int32_t speed, bool premium) :
name(std::move(name)), speed(speed), clientId(clientId), id(id), premium(premium) {}
std::string name;
int32_t speed;
uint16_t clientId;
uint8_t id;
bool premium;
};
class Wings
{
public:
bool reload();
bool loadFromXml();
Wing* getWingByID(uint8_t id);
Wing* getWingByName(const std::string& name);
Wing* getWingByClientID(uint16_t clientId);
const std::vector<Wing>& getWings() const {
return wings;
}
private:
std::vector<Wing> wings;
};
#endif