diff --git a/src/otclient/game.cpp b/src/otclient/game.cpp
index 524074a7..6bdb59f6 100644
--- a/src/otclient/game.cpp
+++ b/src/otclient/game.cpp
@@ -131,13 +131,14 @@ void Game::processGameStart()
disableBotCall();
if(g_game.getFeature(Otc::GameClientPing)) {
+ m_protocolGame->sendPing();
m_pingEvent = g_dispatcher.cycleEvent([this] {
if(m_protocolGame && m_protocolGame->isConnected()) {
enableBotCall();
m_protocolGame->sendPing();
disableBotCall();
}
- }, 1000);
+ }, 2000);
}
}
diff --git a/src/otclient/protocolgame.h b/src/otclient/protocolgame.h
index 2fbcd64b..31996282 100644
--- a/src/otclient/protocolgame.h
+++ b/src/otclient/protocolgame.h
@@ -222,7 +222,7 @@ private:
std::string m_accountName;
std::string m_accountPassword;
std::string m_characterName;
- Timer m_pingTimer;
+ stdext::timer m_pingTimer;
LocalPlayerPtr m_localPlayer;
};
diff --git a/src/otclient/protocolgameparse.cpp b/src/otclient/protocolgameparse.cpp
index ac82d452..34765464 100644
--- a/src/otclient/protocolgameparse.cpp
+++ b/src/otclient/protocolgameparse.cpp
@@ -379,7 +379,7 @@ void ProtocolGame::parsePing(const InputMessagePtr& msg)
void ProtocolGame::parsePingBack(const InputMessagePtr& msg)
{
- g_game.processPingBack(m_pingTimer.ticksElapsed());
+ g_game.processPingBack(m_pingTimer.elapsed_millis());
}
void ProtocolGame::parseChallange(const InputMessagePtr& msg)
@@ -1379,7 +1379,7 @@ void ProtocolGame::parseExtendedOpcode(const InputMessagePtr& msg)
if(opcode == 0)
m_enableSendExtendedOpcode = true;
else
- callLuaField("onExtendedOpcode", opcode, buffer);
+ callLuaField("onExtendedOpcode", opcode, buffer);
}
void ProtocolGame::setMapDescription(const InputMessagePtr& msg, int x, int y, int z, int width, int height)
diff --git a/tools/tfs_extendedopcode.patch b/tools/tfs_extendedopcode.patch
new file mode 100644
index 00000000..b9054951
--- /dev/null
+++ b/tools/tfs_extendedopcode.patch
@@ -0,0 +1,396 @@
+diff --git a/const.h b/const.h
+index cd479e4..8d46b23 100644
+--- a/const.h
++++ b/const.h
+@@ -21,9 +21,12 @@
+
+ enum OperatingSystem_t
+ {
+- CLIENTOS_LINUX = 0x01,
+- CLIENTOS_WINDOWS = 0x02,
+- CLIENTOS_FLASH = 0x03
++ CLIENTOS_LINUX = 0x01,
++ CLIENTOS_WINDOWS = 0x02,
++ CLIENTOS_FLASH = 0x03,
++ CLIENTOS_OTCLIENT_LINUX = 0x0A,
++ CLIENTOS_OTCLIENT_WINDOWS = 0x0B,
++ CLIENTOS_OTCLIENT_MAC = 0x0C,
+ };
+
+ enum ChannelEvent_t
+diff --git a/creatureevent.cpp b/creatureevent.cpp
+index 842b237..e9b2200 100644
+--- a/creatureevent.cpp
++++ b/creatureevent.cpp
+@@ -211,6 +211,8 @@ CreatureEventType_t CreatureEvents::getType(const std::string& type)
+ _type = CREATURE_EVENT_DEATH;
+ else if(type == "preparedeath")
+ _type = CREATURE_EVENT_PREPAREDEATH;
++ else if(type == "extendedopcode")
++ _type = CREATURE_EVENT_EXTENDED_OPCODE;
+
+ return _type;
+ }
+@@ -330,6 +332,8 @@ std::string CreatureEvent::getScriptEventName() const
+ return "onDeath";
+ case CREATURE_EVENT_PREPAREDEATH:
+ return "onPrepareDeath";
++ case CREATURE_EVENT_EXTENDED_OPCODE:
++ return "onExtendedOpcode";
+ case CREATURE_EVENT_NONE:
+ default:
+ break;
+@@ -401,6 +405,8 @@ std::string CreatureEvent::getScriptEventParams() const
+ return "cid, corpse, deathList";
+ case CREATURE_EVENT_PREPAREDEATH:
+ return "cid, deathList";
++ case CREATURE_EVENT_EXTENDED_OPCODE:
++ return "cid, opcode, buffer";
+ case CREATURE_EVENT_NONE:
+ default:
+ break;
+@@ -2145,3 +2151,57 @@ uint32_t CreatureEvent::executeAction(Creature* creature, Creature* target)
+ return 0;
+ }
+ }
++
++uint32_t CreatureEvent::executeExtendedOpcode(Creature* creature, uint8_t opcode, const std::string& buffer)
++{
++ //onExtendedOpcode(cid, opcode, buffer)
++ if(m_interface->reserveEnv())
++ {
++ ScriptEnviroment* env = m_interface->getEnv();
++ if(m_scripted == EVENT_SCRIPT_BUFFER)
++ {
++ env->setRealPos(creature->getPosition());
++ std::stringstream scriptstream;
++ scriptstream << "local cid = " << env->addThing(creature) << std::endl;
++ scriptstream << "local opcode = " << (int)opcode << std::endl;
++ scriptstream << "local buffer = " << buffer.c_str() << std::endl;
++
++ scriptstream << m_scriptData;
++ bool result = true;
++ if(m_interface->loadBuffer(scriptstream.str()))
++ {
++ lua_State* L = m_interface->getState();
++ result = m_interface->getGlobalBool(L, "_result", true);
++ }
++
++ m_interface->releaseEnv();
++ return result;
++ }
++ else
++ {
++ #ifdef __DEBUG_LUASCRIPTS__
++ char desc[35];
++ sprintf(desc, "%s", player->getName().c_str());
++ env->setEvent(desc);
++ #endif
++
++ env->setScriptId(m_scriptId, m_interface);
++ env->setRealPos(creature->getPosition());
++
++ lua_State* L = m_interface->getState();
++ m_interface->pushFunction(m_scriptId);
++ lua_pushnumber(L, env->addThing(creature));
++ lua_pushnumber(L, opcode);
++ lua_pushlstring(L, buffer.c_str(), buffer.length());
++
++ bool result = m_interface->callFunction(3);
++ m_interface->releaseEnv();
++ return result;
++ }
++ }
++ else
++ {
++ std::cout << "[Error - CreatureEvent::executeRemoved] Call stack overflow." << std::endl;
++ return 0;
++ }
++}
+diff --git a/creatureevent.h b/creatureevent.h
+index f1ff4b2..cc5171c 100644
+--- a/creatureevent.h
++++ b/creatureevent.h
+@@ -57,7 +57,8 @@ enum CreatureEventType_t
+ CREATURE_EVENT_CAST,
+ CREATURE_EVENT_KILL,
+ CREATURE_EVENT_DEATH,
+- CREATURE_EVENT_PREPAREDEATH
++ CREATURE_EVENT_PREPAREDEATH,
++ CREATURE_EVENT_EXTENDED_OPCODE // otclient additional network opcodes
+ };
+
+ enum StatsChange_t
+@@ -150,6 +151,7 @@ class CreatureEvent : public Event
+ uint32_t executeKill(Creature* creature, Creature* target, const DeathEntry& entry);
+ uint32_t executeDeath(Creature* creature, Item* corpse, DeathList deathList);
+ uint32_t executePrepareDeath(Creature* creature, DeathList deathList);
++ uint32_t executeExtendedOpcode(Creature* creature, uint8_t opcode, const std::string& buffer);
+ //
+
+ protected:
+diff --git a/data/creaturescripts/creaturescripts.xml b/data/creaturescripts/creaturescripts.xml
+index 363c62b..c706f10 100644
+--- a/data/creaturescripts/creaturescripts.xml
++++ b/data/creaturescripts/creaturescripts.xml
+@@ -14,4 +14,6 @@
+
+
+
++
++
+
+diff --git a/game.cpp b/game.cpp
+index 2e4dc2c..7508591 100644
+--- a/game.cpp
++++ b/game.cpp
+@@ -6951,3 +6951,12 @@ void Game::checkExpiredMarketOffers()
+
+ Scheduler::getInstance().addEvent(createSchedulerTask(checkExpiredMarketOffersEachMinutes * 60 * 1000, boost::bind(&Game::checkExpiredMarketOffers, this)));
+ }
++
++void Game::parsePlayerExtendedOpcode(Player *player, uint8_t opcode, const std::string& buffer)
++{
++ if(player) {
++ CreatureEventList extendedOpcodeEvents = player->getCreatureEvents(CREATURE_EVENT_EXTENDED_OPCODE);
++ for(CreatureEventList::iterator it = extendedOpcodeEvents.begin(); it != extendedOpcodeEvents.end(); ++it)
++ (*it)->executeExtendedOpcode(player, opcode, buffer);
++ }
++}
+diff --git a/game.h b/game.h
+index 51fa397..7192549 100644
+--- a/game.h
++++ b/game.h
+@@ -646,6 +646,8 @@ class Game
+ std::map- grounds;
+ #endif
+
++ void parsePlayerExtendedOpcode(Player *player, uint8_t opcode, const std::string& buffer);
++
+ protected:
+ bool playerWhisper(Player* player, const std::string& text, uint32_t statementId);
+ bool playerYell(Player* player, const std::string& text, uint32_t statementId);
+diff --git a/luascript.cpp b/luascript.cpp
+index 4cb8c8d..4ed9391 100644
+--- a/luascript.cpp
++++ b/luascript.cpp
+@@ -2476,6 +2476,12 @@ void LuaInterface::registerFunctions()
+ //getConfigFile()
+ lua_register(m_luaState, "getConfigFile", LuaInterface::luaGetConfigFile);
+
++ //isPlayerUsingOtclient(cid)
++ lua_register(m_luaState, "isPlayerUsingOtclient", LuaInterface::luaIsPlayerUsingOtclient);
++
++ //doSendPlayerExtendedOpcode(cid, opcode, buffer)
++ lua_register(m_luaState, "doSendPlayerExtendedOpcode", LuaInterface::luaDoSendPlayerExtendedOpcode);
++
+ //getConfigValue(key)
+ lua_register(m_luaState, "getConfigValue", LuaInterface::luaGetConfigValue);
+
+@@ -9471,6 +9477,32 @@ int32_t LuaInterface::luaGetMountInfo(lua_State* L)
+ return 1;
+ }
+
++int32_t LuaInterface::luaIsPlayerUsingOtclient(lua_State* L)
++{
++ //isPlayerUsingOtclient(cid)
++ ScriptEnviroment* env = getEnv();
++ if(Player* player = env->getPlayerByUID(popNumber(L))) {
++ lua_pushboolean(L, player->isUsingOtclient());
++ }
++ lua_pushboolean(L, false);
++ return 1;
++}
++
++int32_t LuaInterface::luaDoSendPlayerExtendedOpcode(lua_State* L)
++{
++ //doSendPlayerExtendedOpcode(cid, opcode, buffer)
++ std::string buffer = popString(L);
++ int opcode = popNumber(L);
++
++ ScriptEnviroment* env = getEnv();
++ if(Player* player = env->getPlayerByUID(popNumber(L))) {
++ player->sendExtendedOpcode(opcode, buffer);
++ lua_pushboolean(L, true);
++ }
++ lua_pushboolean(L, false);
++ return 1;
++}
++
+ int32_t LuaInterface::luaGetPartyMembers(lua_State* L)
+ {
+ //getPartyMembers(cid)
+diff --git a/luascript.h b/luascript.h
+index 234091a..0a0046f 100644
+--- a/luascript.h
++++ b/luascript.h
+@@ -697,6 +697,9 @@ class LuaInterface
+ static int32_t luaDoPlayerSetMounted(lua_State* L);
+ static int32_t luaGetMountInfo(lua_State* L);
+
++ static int32_t luaIsPlayerUsingOtclient(lua_State* L);
++ static int32_t luaDoSendPlayerExtendedOpcode(lua_State* L);
++
+ static int32_t luaL_errors(lua_State* L);
+ static int32_t luaL_loadmodlib(lua_State* L);
+ static int32_t luaL_domodlib(lua_State* L);
+diff --git a/networkmessage.cpp b/networkmessage.cpp
+index 917e36a..3671750 100644
+--- a/networkmessage.cpp
++++ b/networkmessage.cpp
+@@ -171,16 +171,16 @@ Position NetworkMessage::getPosition()
+ return pos;
+ }
+
+-void NetworkMessage::putString(const char* value, bool addSize/* = true*/)
++void NetworkMessage::putString(const char* value, int length, bool addSize/* = true*/)
+ {
+- uint32_t size = (uint32_t)strlen(value);
++ uint32_t size = (uint32_t)length;
+ if(!hasSpace(size + (addSize ? 2 : 0)) || size > 8192)
+ return;
+
+ if(addSize)
+ put(size);
+
+- strcpy((char*)(m_buffer + m_position), value);
++ memcpy((char*)(m_buffer + m_position), value, length);
+ m_position += size;
+ m_size += size;
+ }
+diff --git a/networkmessage.h b/networkmessage.h
+index 6cf8ee1..615f094 100644
+--- a/networkmessage.h
++++ b/networkmessage.h
+@@ -80,8 +80,8 @@ class NetworkMessage
+ m_size += sizeof(T);
+ }
+
+- void putString(const std::string& value, bool addSize = true) {putString(value.c_str(), addSize);}
+- void putString(const char* value, bool addSize = true);
++ void putString(const std::string& value, bool addSize = true) {putString(value.c_str(), value.length(), addSize);}
++ void putString(const char* value, int length, bool addSize = true);
+
+ void putPadding(uint32_t amount);
+
+diff --git a/player.h b/player.h
+index 63e9183..e003e63 100644
+--- a/player.h
++++ b/player.h
+@@ -228,6 +228,7 @@ class Player : public Creature, public Cylinder
+ bool hasPVPBlessing() const {return pvpBlessing;}
+ uint16_t getBlessings() const;
+
++ bool isUsingOtclient() const { return operatingSystem >= CLIENTOS_OTCLIENT_LINUX; }
+ OperatingSystem_t getOperatingSystem() const {return operatingSystem;}
+ void setOperatingSystem(OperatingSystem_t os) {operatingSystem = os;}
+ uint32_t getClientVersion() const {return clientVersion;}
+@@ -400,7 +401,7 @@ class Player : public Creature, public Cylinder
+ onSell = saleCallback;
+ return shopOwner;
+ }
+-
++
+ //Quest functions
+ void onUpdateQuest();
+
+@@ -580,6 +581,9 @@ class Player : public Creature, public Cylinder
+ void sendSpellGroupCooldown(SpellGroup_t groupId, uint32_t cooldown)
+ {if(client) client->sendSpellGroupCooldown(groupId, cooldown);}
+
++ void sendExtendedOpcode(uint8_t opcode, const std::string& buffer)
++ {if(client) client->sendExtendedOpcode(opcode, buffer);}
++
+ //container
+ void sendAddContainerItem(const Container* container, const Item* item);
+ void sendUpdateContainerItem(const Container* container, uint8_t slot, const Item* oldItem, const Item* newItem);
+diff --git a/protocolgame.cpp b/protocolgame.cpp
+index b980be0..c75654e 100644
+--- a/protocolgame.cpp
++++ b/protocolgame.cpp
+@@ -263,6 +263,11 @@ bool ProtocolGame::login(const std::string& name, uint32_t id, const std::string
+ return false;
+ }
+
++ if(player->isUsingOtclient())
++ {
++ player->registerCreatureEvent("ExtendedOpcode");
++ }
++
+ player->lastIP = player->getIP();
+ player->lastLoad = OTSYS_TIME();
+ player->lastLogin = std::max(time(NULL), player->lastLogin + 1);
+@@ -427,6 +432,10 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg)
+ enableXTEAEncryption();
+ setXTEAKey(key);
+
++ // notifies to otclient that this server can receive extended game protocol opcodes
++ if(operatingSystem >= CLIENTOS_OTCLIENT_LINUX)
++ sendExtendedOpcode(0x00, std::string());
++
+ bool gamemaster = (msg.get() != (char)0);
+ std::string name = msg.getString(), character = msg.getString(), password = msg.getString();
+
+@@ -578,6 +587,10 @@ void ProtocolGame::parsePacket(NetworkMessage &msg)
+ parseReceivePing(msg);
+ break;
+
++ case 0x32: // otclient extended opcode
++ parseExtendedOpcode(msg);
++ break;
++
+ case 0x64: // move with steps
+ parseAutoWalk(msg);
+ break;
+@@ -2411,7 +2424,7 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId)
+ }
+ else
+ msg->put(0x00);
+-
++
+ if((statistics = IOMarket::getInstance()->getSaleStatistics(itemId)))
+ {
+ msg->put(0x01);
+@@ -3705,3 +3718,28 @@ void ProtocolGame::AddShopItem(NetworkMessage_ptr msg, const ShopInfo& item)
+ msg->put(item.buyPrice);
+ msg->put(item.sellPrice);
+ }
++
++void ProtocolGame::parseExtendedOpcode(NetworkMessage& msg)
++{
++ uint8_t opcode = msg.get();
++ std::string buffer = msg.getString();
++
++ // process additional opcodes via lua script event
++ addGameTask(&Game::parsePlayerExtendedOpcode, player, opcode, buffer);
++}
++
++void ProtocolGame::sendExtendedOpcode(uint8_t opcode, const std::string& buffer)
++{
++ // extended opcodes can only be send to players using otclient, cipsoft's tibia can't understand them
++ if(player && !player->isUsingOtclient())
++ return;
++
++ NetworkMessage_ptr msg = getOutputBuffer();
++ if(msg)
++ {
++ TRACK_MESSAGE(msg);
++ msg->put(0x32);
++ msg->put(opcode);
++ msg->putString(buffer);
++ }
++}
+diff --git a/protocolgame.h b/protocolgame.h
+index 7691174..48b9bf1 100644
+--- a/protocolgame.h
++++ b/protocolgame.h
+@@ -326,6 +326,9 @@ class ProtocolGame : public Protocol
+ //shop
+ void AddShopItem(NetworkMessage_ptr msg, const ShopInfo& item);
+
++ void parseExtendedOpcode(NetworkMessage& msg);
++ void sendExtendedOpcode(uint8_t opcode, const std::string& buffer);
++
+ #define addGameTask(f, ...) addGameTaskInternal(0, boost::bind(f, &g_game, __VA_ARGS__))
+ #define addGameTaskTimed(delay, f, ...) addGameTaskInternal(delay, boost::bind(f, &g_game, __VA_ARGS__))
+ template