Mercurial > hg > openttd
changeset 18775:26ffd3067724 draft
(svn r23623) -Add: allow bi-directional communication with the AdminPort and GameScript
line wrap: on
line diff
--- a/projects/openttd_vs100.vcxproj +++ b/projects/openttd_vs100.vcxproj @@ -926,6 +926,7 @@ <ClCompile Include="..\src\game\game_scanner.cpp" /> <ClInclude Include="..\src\game\game_scanner.hpp" /> <ClInclude Include="..\src\script\api\script_accounting.hpp" /> + <ClInclude Include="..\src\script\api\script_admin.hpp" /> <ClInclude Include="..\src\script\api\script_airport.hpp" /> <ClInclude Include="..\src\script\api\script_base.hpp" /> <ClInclude Include="..\src\script\api\script_basestation.hpp" /> @@ -980,6 +981,7 @@ <ClInclude Include="..\src\script\api\script_waypoint.hpp" /> <ClInclude Include="..\src\script\api\script_waypointlist.hpp" /> <ClCompile Include="..\src\script\api\script_accounting.cpp" /> + <ClCompile Include="..\src\script\api\script_admin.cpp" /> <ClCompile Include="..\src\script\api\script_airport.cpp" /> <ClCompile Include="..\src\script\api\script_base.cpp" /> <ClCompile Include="..\src\script\api\script_basestation.cpp" />
--- a/projects/openttd_vs100.vcxproj.filters +++ b/projects/openttd_vs100.vcxproj.filters @@ -2007,6 +2007,9 @@ <ClInclude Include="..\src\script\api\script_accounting.hpp"> <Filter>Script API</Filter> </ClInclude> + <ClInclude Include="..\src\script\api\script_admin.hpp"> + <Filter>Script API</Filter> + </ClInclude> <ClInclude Include="..\src\script\api\script_airport.hpp"> <Filter>Script API</Filter> </ClInclude> @@ -2169,6 +2172,9 @@ <ClCompile Include="..\src\script\api\script_accounting.cpp"> <Filter>Script API Implementation</Filter> </ClCompile> + <ClCompile Include="..\src\script\api\script_admin.cpp"> + <Filter>Script API Implementation</Filter> + </ClCompile> <ClCompile Include="..\src\script\api\script_airport.cpp"> <Filter>Script API Implementation</Filter> </ClCompile>
--- a/projects/openttd_vs80.vcproj +++ b/projects/openttd_vs80.vcproj @@ -3039,6 +3039,10 @@ > </File> <File + RelativePath=".\..\src\script\api\script_admin.hpp" + > + </File> + <File RelativePath=".\..\src\script\api\script_airport.hpp" > </File> @@ -3259,6 +3263,10 @@ > </File> <File + RelativePath=".\..\src\script\api\script_admin.cpp" + > + </File> + <File RelativePath=".\..\src\script\api\script_airport.cpp" > </File>
--- a/projects/openttd_vs90.vcproj +++ b/projects/openttd_vs90.vcproj @@ -3036,6 +3036,10 @@ > </File> <File + RelativePath=".\..\src\script\api\script_admin.hpp" + > + </File> + <File RelativePath=".\..\src\script\api\script_airport.hpp" > </File> @@ -3256,6 +3260,10 @@ > </File> <File + RelativePath=".\..\src\script\api\script_admin.cpp" + > + </File> + <File RelativePath=".\..\src\script\api\script_airport.cpp" > </File>
--- a/source.list +++ b/source.list @@ -704,6 +704,7 @@ # Script API script/api/script_accounting.hpp +script/api/script_admin.hpp script/api/script_airport.hpp script/api/script_base.hpp script/api/script_basestation.hpp @@ -760,6 +761,7 @@ # Script API Implementation script/api/script_accounting.cpp +script/api/script_admin.cpp script/api/script_airport.cpp script/api/script_base.cpp script/api/script_basestation.cpp
--- a/src/game/game_instance.cpp +++ b/src/game/game_instance.cpp @@ -24,6 +24,7 @@ /* Convert all Game related classes to Squirrel data. * Note: this line is a marker in squirrel_export.sh. Do not change! */ #include "../script/api/game/game_accounting.hpp.sq" +#include "../script/api/game/game_admin.hpp.sq" #include "../script/api/game/game_airport.hpp.sq" #include "../script/api/game/game_base.hpp.sq" #include "../script/api/game/game_basestation.hpp.sq" @@ -92,6 +93,7 @@ /* Register all classes */ SQGSList_Register(this->engine); SQGSAccounting_Register(this->engine); + SQGSAdmin_Register(this->engine); SQGSAirport_Register(this->engine); SQGSBase_Register(this->engine); SQGSBaseStation_Register(this->engine); @@ -110,6 +112,7 @@ SQGSEngineList_Register(this->engine); SQGSError_Register(this->engine); SQGSEvent_Register(this->engine); + SQGSEventAdminPort_Register(this->engine); SQGSEventCompanyBankrupt_Register(this->engine); SQGSEventCompanyInTrouble_Register(this->engine); SQGSEventCompanyMerger_Register(this->engine);
--- a/src/network/core/config.h +++ b/src/network/core/config.h @@ -48,6 +48,7 @@ static const uint NETWORK_CLIENTS_LENGTH = 200; ///< The maximum length for the list of clients that controls a company, in bytes including '\0' static const uint NETWORK_CLIENT_NAME_LENGTH = 25; ///< The maximum length of a client's name, in bytes including '\0' static const uint NETWORK_RCONCOMMAND_LENGTH = 500; ///< The maximum length of a rconsole command, in bytes including '\0' +static const uint NETWORK_GAMESCRIPT_JSON_LENGTH = 1450; ///< The maximum length of a gamescript json string, in bytes including '\0' static const uint NETWORK_CHAT_LENGTH = 900; ///< The maximum length of a chat message, in bytes including '\0' static const uint NETWORK_GRF_NAME_LENGTH = 80; ///< Maximum length of the name of a GRF
--- a/src/network/core/tcp_admin.cpp +++ b/src/network/core/tcp_admin.cpp @@ -60,6 +60,7 @@ case ADMIN_PACKET_ADMIN_POLL: return this->Receive_ADMIN_POLL(p); case ADMIN_PACKET_ADMIN_CHAT: return this->Receive_ADMIN_CHAT(p); case ADMIN_PACKET_ADMIN_RCON: return this->Receive_ADMIN_RCON(p); + case ADMIN_PACKET_ADMIN_GAMESCRIPT: return this->Receive_ADMIN_GAMESCRIPT(p); case ADMIN_PACKET_SERVER_FULL: return this->Receive_SERVER_FULL(p); case ADMIN_PACKET_SERVER_BANNED: return this->Receive_SERVER_BANNED(p); @@ -134,6 +135,7 @@ NetworkRecvStatus NetworkAdminSocketHandler::Receive_ADMIN_POLL(Packet *p) { return this->ReceiveInvalidPacket(ADMIN_PACKET_ADMIN_POLL); } NetworkRecvStatus NetworkAdminSocketHandler::Receive_ADMIN_CHAT(Packet *p) { return this->ReceiveInvalidPacket(ADMIN_PACKET_ADMIN_CHAT); } NetworkRecvStatus NetworkAdminSocketHandler::Receive_ADMIN_RCON(Packet *p) { return this->ReceiveInvalidPacket(ADMIN_PACKET_ADMIN_RCON); } +NetworkRecvStatus NetworkAdminSocketHandler::Receive_ADMIN_GAMESCRIPT(Packet *p) { return this->ReceiveInvalidPacket(ADMIN_PACKET_ADMIN_GAMESCRIPT); } NetworkRecvStatus NetworkAdminSocketHandler::Receive_SERVER_FULL(Packet *p) { return this->ReceiveInvalidPacket(ADMIN_PACKET_SERVER_FULL); } NetworkRecvStatus NetworkAdminSocketHandler::Receive_SERVER_BANNED(Packet *p) { return this->ReceiveInvalidPacket(ADMIN_PACKET_SERVER_BANNED); }
--- a/src/network/core/tcp_admin.h +++ b/src/network/core/tcp_admin.h @@ -32,6 +32,7 @@ ADMIN_PACKET_ADMIN_POLL, ///< The admin explicitly polls for a piece of information. ADMIN_PACKET_ADMIN_CHAT, ///< The admin sends a chat message to be distributed. ADMIN_PACKET_ADMIN_RCON, ///< The admin sends a remote console command. + ADMIN_PACKET_ADMIN_GAMESCRIPT, ///< The admin sends a JSON string for the GameScript. ADMIN_PACKET_SERVER_FULL = 100, ///< The server tells the admin it cannot accept the admin. ADMIN_PACKET_SERVER_BANNED, ///< The server tells the admin it is banned. @@ -58,6 +59,7 @@ ADMIN_PACKET_SERVER_CONSOLE, ///< The server gives the admin the data that got printed to its console. ADMIN_PACKET_SERVER_CMD_NAMES, ///< The server sends out the names of the DoCommands to the admins. ADMIN_PACKET_SERVER_CMD_LOGGING, ///< The server gives the admin copies of incoming command packets. + ADMIN_PACKET_SERVER_GAMESCRIPT, ///< The server gives the admin information from the GameScript in JSON. INVALID_ADMIN_PACKET = 0xFF, ///< An invalid marker for admin packets. }; @@ -80,6 +82,7 @@ ADMIN_UPDATE_CONSOLE, ///< The admin would like to have console messages. ADMIN_UPDATE_CMD_NAMES, ///< The admin would like a list of all DoCommand names. ADMIN_UPDATE_CMD_LOGGING, ///< The admin would like to have DoCommand information. + ADMIN_UPDATE_GAMESCRIPT, ///< The admin would like to have gamescript messages. ADMIN_UPDATE_END, ///< Must ALWAYS be on the end of this list!! (period) }; @@ -170,6 +173,14 @@ virtual NetworkRecvStatus Receive_ADMIN_RCON(Packet *p); /** + * Send a JSON string to the current active GameScript. + * json JSON string for the GameScript. + * @param p The packet that was just received. + * @return The state the network should have. + */ + virtual NetworkRecvStatus Receive_ADMIN_GAMESCRIPT(Packet *p); + + /** * The server is full (connection gets closed). * @param p The packet that was just received. * @return The state the network should have.
--- a/src/network/network_admin.cpp +++ b/src/network/network_admin.cpp @@ -23,6 +23,7 @@ #include "../core/pool_func.hpp" #include "../map_func.h" #include "../rev.h" +#include "../game/game.hpp" /* This file handles all the admin network commands. */ @@ -52,6 +53,7 @@ ADMIN_FREQUENCY_AUTOMATIC, ///< ADMIN_UPDATE_CONSOLE ADMIN_FREQUENCY_POLL, ///< ADMIN_UPDATE_CMD_NAMES ADMIN_FREQUENCY_AUTOMATIC, ///< ADMIN_UPDATE_CMD_LOGGING + ADMIN_FREQUENCY_AUTOMATIC, ///< ADMIN_UPDATE_GAMESCRIPT }; /** Sanity check. */ assert_compile(lengthof(_admin_update_type_frequencies) == ADMIN_UPDATE_END); @@ -510,6 +512,20 @@ return NETWORK_RECV_STATUS_OKAY; } +NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_GAMESCRIPT(Packet *p) +{ + if (this->status == ADMIN_STATUS_INACTIVE) return this->SendError(NETWORK_ERROR_NOT_EXPECTED); + + char json[NETWORK_GAMESCRIPT_JSON_LENGTH]; + + p->Recv_string(json, sizeof(json)); + + DEBUG(net, 2, "[admin] GameScript JSON from '%s' (%s): '%s'", this->admin_name, this->admin_version, json); + + Game::NewEvent(new ScriptEventAdminPort(json)); + return NETWORK_RECV_STATUS_OKAY; +} + /** * Send console output of other clients. * @param origin The origin of the string. @@ -532,6 +548,25 @@ return NETWORK_RECV_STATUS_OKAY; } +/** + * Send GameScript JSON output. + * @param json The JSON string. + */ +NetworkRecvStatus ServerNetworkAdminSocketHandler::SendGameScript(const char *json) +{ + /* At the moment we cannot transmit anything larger than MTU. So the string + * has to be no longer than the length of the json + '\0' + 3 bytes of the + * packet header. */ + if (strlen(json) + 1 + 3 >= SEND_MTU) return NETWORK_RECV_STATUS_OKAY; + + Packet *p = new Packet(ADMIN_PACKET_SERVER_GAMESCRIPT); + + p->Send_string(json); + this->SendPacket(p); + + return NETWORK_RECV_STATUS_OKAY; +} + /** Send the names of the commands. */ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCmdNames() { @@ -896,6 +931,20 @@ } /** + * Send GameScript JSON to the admin network (if they did opt in for the respective update). + * @param json The JSON data as received from the GameScript. + */ +void NetworkAdminGameScript(const char *json) +{ + ServerNetworkAdminSocketHandler *as; + FOR_ALL_ACTIVE_ADMIN_SOCKETS(as) { + if (as->update_frequency[ADMIN_UPDATE_GAMESCRIPT] & ADMIN_FREQUENCY_AUTOMATIC) { + as->SendGameScript(json); + } + } +} + +/** * Distribute CommandPacket details over the admin network for logging purposes. * @param owner The owner of the CommandPacket (who sent us the CommandPacket). * @param cp The CommandPacket to be distributed.
--- a/src/network/network_admin.h +++ b/src/network/network_admin.h @@ -34,6 +34,7 @@ virtual NetworkRecvStatus Receive_ADMIN_POLL(Packet *p); virtual NetworkRecvStatus Receive_ADMIN_CHAT(Packet *p); virtual NetworkRecvStatus Receive_ADMIN_RCON(Packet *p); + virtual NetworkRecvStatus Receive_ADMIN_GAMESCRIPT(Packet *p); NetworkRecvStatus SendProtocol(); public: @@ -65,6 +66,7 @@ NetworkRecvStatus SendChat(NetworkAction action, DestType desttype, ClientID client_id, const char *msg, int64 data); NetworkRecvStatus SendRcon(uint16 colour, const char *command); NetworkRecvStatus SendConsole(const char *origin, const char *command); + NetworkRecvStatus SendGameScript(const char *json); NetworkRecvStatus SendCmdNames(); NetworkRecvStatus SendCmdLogging(ClientID client_id, const CommandPacket *cp); @@ -116,6 +118,7 @@ void NetworkAdminUpdate(AdminUpdateFrequency freq); void NetworkServerSendAdminRcon(AdminIndex admin_index, TextColour colour_code, const char *string); void NetworkAdminConsole(const char *origin, const char *string); +void NetworkAdminGameScript(const char *json); void NetworkAdminCmdLogging(const NetworkClientSocket *owner, const CommandPacket *cp); #endif /* ENABLE_NETWORK */
--- a/src/script/api/ai/ai_event.hpp.sq +++ b/src/script/api/ai/ai_event.hpp.sq @@ -45,6 +45,7 @@ SQAIEvent.DefSQConst(engine, ScriptEvent::ET_DISASTER_ZEPPELINER_CLEARED, "ET_DISASTER_ZEPPELINER_CLEARED"); SQAIEvent.DefSQConst(engine, ScriptEvent::ET_TOWN_FOUNDED, "ET_TOWN_FOUNDED"); SQAIEvent.DefSQConst(engine, ScriptEvent::ET_AIRCRAFT_DEST_TOO_FAR, "ET_AIRCRAFT_DEST_TOO_FAR"); + SQAIEvent.DefSQConst(engine, ScriptEvent::ET_ADMIN_PORT, "ET_ADMIN_PORT"); SQAIEvent.DefSQMethod(engine, &ScriptEvent::GetEventType, "GetEventType", 1, "x");
new file mode 100644 --- /dev/null +++ b/src/script/api/game/game_admin.hpp.sq @@ -0,0 +1,27 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * OpenTTD 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, version 2. + * OpenTTD 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 OpenTTD. If not, see <http://www.gnu.org/licenses/>. + */ + +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "../script_admin.hpp" +#include "../template/template_admin.hpp.sq" + + +template <> const char *GetClassName<ScriptAdmin, ST_GS>() { return "GSAdmin"; } + +void SQGSAdmin_Register(Squirrel *engine) +{ + DefSQClass<ScriptAdmin, ST_GS> SQGSAdmin("GSAdmin"); + SQGSAdmin.PreRegister(engine); + SQGSAdmin.AddConstructor<void (ScriptAdmin::*)(), 1>(engine, "x"); + + SQGSAdmin.DefSQAdvancedStaticMethod(engine, &ScriptAdmin::Send, "Send"); + + SQGSAdmin.PostRegister(engine); +}
--- a/src/script/api/game/game_event.hpp.sq +++ b/src/script/api/game/game_event.hpp.sq @@ -45,6 +45,7 @@ SQGSEvent.DefSQConst(engine, ScriptEvent::ET_DISASTER_ZEPPELINER_CLEARED, "ET_DISASTER_ZEPPELINER_CLEARED"); SQGSEvent.DefSQConst(engine, ScriptEvent::ET_TOWN_FOUNDED, "ET_TOWN_FOUNDED"); SQGSEvent.DefSQConst(engine, ScriptEvent::ET_AIRCRAFT_DEST_TOO_FAR, "ET_AIRCRAFT_DEST_TOO_FAR"); + SQGSEvent.DefSQConst(engine, ScriptEvent::ET_ADMIN_PORT, "ET_ADMIN_PORT"); SQGSEvent.DefSQMethod(engine, &ScriptEvent::GetEventType, "GetEventType", 1, "x");
--- a/src/script/api/game/game_event_types.hpp.sq +++ b/src/script/api/game/game_event_types.hpp.sq @@ -217,3 +217,18 @@ SQGSEventTownFounded.PostRegister(engine); } + + +template <> const char *GetClassName<ScriptEventAdminPort, ST_GS>() { return "GSEventAdminPort"; } + +void SQGSEventAdminPort_Register(Squirrel *engine) +{ + DefSQClass<ScriptEventAdminPort, ST_GS> SQGSEventAdminPort("GSEventAdminPort"); + SQGSEventAdminPort.PreRegister(engine, "GSEvent"); + + SQGSEventAdminPort.DefSQStaticMethod(engine, &ScriptEventAdminPort::Convert, "Convert", 2, ".x"); + + SQGSEventAdminPort.DefSQAdvancedMethod(engine, &ScriptEventAdminPort::GetObject, "GetObject"); + + SQGSEventAdminPort.PostRegister(engine); +}
new file mode 100644 --- /dev/null +++ b/src/script/api/script_admin.cpp @@ -0,0 +1,151 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * OpenTTD 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, version 2. + * OpenTTD 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 OpenTTD. If not, see <http://www.gnu.org/licenses/>. + */ + +/** @file script_admin.cpp Implementation of ScriptAdmin. */ + +#include "../../stdafx.h" +#include "script_admin.hpp" +#include "script_log.hpp" +#include "../../network/network_admin.h" +#include "../script_instance.hpp" +#include "../../game/game.hpp" + +/* static */ bool ScriptAdmin::MakeJSON(HSQUIRRELVM vm, SQInteger index, int max_depth, std::string &data) +{ + if (max_depth == 0) { + ScriptLog::Error("Send parameters can only be nested to 25 deep. No data sent."); // SQUIRREL_MAX_DEPTH = 25 + return false; + } + + switch (sq_gettype(vm, index)) { + case OT_INTEGER: { + SQInteger res; + sq_getinteger(vm, index, &res); + + char buf[10]; + snprintf(buf, sizeof(buf), "%d", (int32)res); + data = buf; + return true; + } + + case OT_STRING: { + const SQChar *res; + sq_getstring(vm, index, &res); + + /* @bug if a string longer than 512 characters is given to SQ2OTTD, the + * internal buffer overflows. */ + const char *buf = SQ2OTTD(res); + size_t len = strlen(buf) + 1; + if (len >= 255) { + ScriptLog::Error("Maximum string length is 254 chars. No data sent."); + return false; + } + + data = std::string("\"") + buf + "\""; + return true; + } + + case OT_ARRAY: { + data = "[ "; + + bool first = true; + sq_pushnull(vm); + while (SQ_SUCCEEDED(sq_next(vm, index - 1))) { + if (!first) data += ", "; + if (first) first = false; + + std::string tmp; + + bool res = MakeJSON(vm, -1, max_depth - 1, tmp); + sq_pop(vm, 2); + if (!res) { + sq_pop(vm, 1); + return false; + } + data += tmp; + } + sq_pop(vm, 1); + data += " ]"; + return true; + } + + case OT_TABLE: { + data = "{ "; + + bool first = true; + sq_pushnull(vm); + while (SQ_SUCCEEDED(sq_next(vm, index - 1))) { + if (!first) data += ", "; + if (first) first = false; + + std::string key; + std::string value; + + /* Store the key + value */ + bool res = MakeJSON(vm, -2, max_depth - 1, key) && MakeJSON(vm, -1, max_depth - 1, value); + sq_pop(vm, 2); + if (!res) { + sq_pop(vm, 1); + return false; + } + data += key + ": " + value; + } + sq_pop(vm, 1); + data += " }"; + return true; + } + + case OT_BOOL: { + SQBool res; + sq_getbool(vm, index, &res); + + if (res) { + data = "true"; + return true; + } + + data = "false"; + return true; + } + + case OT_NULL: { + data = "null"; + return true; + } + + default: + ScriptLog::Error("You tried to send an unsupported type. No data sent."); + return false; + } +} + +/* static */ SQInteger ScriptAdmin::Send(HSQUIRRELVM vm) +{ + if (sq_gettop(vm) - 1 != 1) return sq_throwerror(vm, _SC("wrong number of parameters")); + + if (sq_gettype(vm, 2) != OT_TABLE) { + return sq_throwerror(vm, _SC("ScriptAdmin::Send requires a table as first parameter. No data sent.")); + } + + std::string json; + ScriptAdmin::MakeJSON(vm, -1, SQUIRREL_MAX_DEPTH, json); + +#ifdef ENABLE_NETWORK + if (json.length() > NETWORK_GAMESCRIPT_JSON_LENGTH) { + ScriptLog::Error("You are trying to send a table that is too large to the AdminPort. No data sent."); + sq_pushinteger(vm, 0); + return 1; + } + + NetworkAdminGameScript(json.c_str()); +#endif /* ENABLE_NETWORK */ + + sq_pushinteger(vm, 1); + return 1; +}
new file mode 100644 --- /dev/null +++ b/src/script/api/script_admin.hpp @@ -0,0 +1,53 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * OpenTTD 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, version 2. + * OpenTTD 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 OpenTTD. If not, see <http://www.gnu.org/licenses/>. + */ + +/** @file script_admin.hpp Everything to communicate with the AdminPort. */ + +#ifndef SCRIPT_ADMIN_HPP +#define SCRIPT_ADMIN_HPP + +#include <string> +#include "script_object.hpp" + +/** + * Class that handles communication with the AdminPort. + * @api game + */ +class ScriptAdmin : public ScriptObject { +public: +#ifndef DOXYGEN_API + /** + * Internal representation of the Send function. + */ + static SQInteger Send(HSQUIRRELVM vm); +#else + /** + * Send information to the AdminPort. The information can be anything + * as long as it isn't a class or instance thereof. + * @param table The information to send, in a table. For example: { param = "param" }. + * @return True if and only if the data was successfully converted to JSON + * and send to the AdminPort. + * @note If the resulting JSON of your table is larger than 1450 bytes, + * nothing will be sent (and false will be returned). + */ + static bool Send(table); +#endif /* DOXYGEN_API */ + +private: + /** + * Convert a Squirrel structure into a JSON string. + * @param vm The VM to operate on. + * @param index The index we are currently working for. + * @param max_depth The maximal depth to follow the squirrel struct. + * @param data The resulting json string. + */ + static bool MakeJSON(HSQUIRRELVM vm, SQInteger index, int max_depth, std::string &data); +}; + +#endif /* SCRIPT_ADMIN_HPP */
--- a/src/script/api/script_event.hpp +++ b/src/script/api/script_event.hpp @@ -50,6 +50,7 @@ ET_DISASTER_ZEPPELINER_CLEARED, ET_TOWN_FOUNDED, ET_AIRCRAFT_DEST_TOO_FAR, + ET_ADMIN_PORT, }; /**
--- a/src/script/api/script_event_types.cpp +++ b/src/script/api/script_event_types.cpp @@ -12,6 +12,7 @@ #include "../../stdafx.h" #include "script_event_types.hpp" #include "script_vehicle.hpp" +#include "script_log.hpp" #include "../../command_type.h" #include "../../strings_func.h" #include "../../settings_type.h" @@ -119,3 +120,185 @@ { return ScriptObject::DoCommand(0, this->owner, 0, CMD_BUY_COMPANY); } + +#define SKIP_EMPTY(p) while (*(p) == ' ' || *(p) == '\n' || *(p) == '\r') (p)++; +#define RETURN_ERROR(stack) { ScriptLog::Error("Received invalid JSON data from AdminPort."); if (stack != 0) sq_pop(vm, stack); return NULL; } + +SQInteger ScriptEventAdminPort::GetObject(HSQUIRRELVM vm) +{ + char *p = this->json; + + if (this->ReadTable(vm, p) == NULL) { + sq_pushnull(vm); + return 1; + } + + return 1; +} + +char *ScriptEventAdminPort::ReadString(HSQUIRRELVM vm, char *p) +{ + char *value = p; + + bool escape = false; + for (;;) { + if (*p == '\\') { + escape = true; + p++; + continue; + } + if (*p == '"' && escape) { + escape = false; + p++; + continue; + } + escape = false; + + if (*p == '"') break; + if (*p == '\0') RETURN_ERROR(0); + + p++; + } + + *p = '\0'; + sq_pushstring(vm, OTTD2SQ(value), -1); + *p++ = '"'; + + return p; +} + +char *ScriptEventAdminPort::ReadTable(HSQUIRRELVM vm, char *p) +{ + sq_newtable(vm); + + SKIP_EMPTY(p); + if (*p++ != '{') RETURN_ERROR(1); + + for (;;) { + SKIP_EMPTY(p); + if (*p++ != '"') RETURN_ERROR(1); + + p = ReadString(vm, p); + if (p == NULL) { + sq_pop(vm, 1); + return NULL; + } + + SKIP_EMPTY(p); + if (*p++ != ':') RETURN_ERROR(2); + + p = this->ReadValue(vm, p); + if (p == NULL) { + sq_pop(vm, 2); + return NULL; + } + + sq_rawset(vm, -3); + /* The key (-2) and value (-1) are popped from the stack by squirrel. */ + + SKIP_EMPTY(p); + if (*p == ',') { + p++; + continue; + } + break; + } + + SKIP_EMPTY(p); + if (*p++ != '}') RETURN_ERROR(1); + + return p; +} + +char *ScriptEventAdminPort::ReadValue(HSQUIRRELVM vm, char *p) +{ + SKIP_EMPTY(p); + + if (strncmp(p, "false", 5) == 0) { + sq_pushinteger(vm, 0); + return p + 5; + } + if (strncmp(p, "true", 4) == 0) { + sq_pushinteger(vm, 1); + return p + 4; + } + if (strncmp(p, "null", 4) == 0) { + sq_pushnull(vm); + return p + 4; + } + + switch (*p) { + case '"': { + /* String */ + p = ReadString(vm, ++p); + if (p == NULL) return NULL; + + break; + } + + case '{': { + /* Table */ + p = this->ReadTable(vm, p); + if (p == NULL) return NULL; + + break; + } + + case '[': { + /* Array */ + sq_newarray(vm, 0); + + while (*p++ != ']') { + p = this->ReadValue(vm, p); + if (p == NULL) { + sq_pop(vm, 1); + return NULL; + } + sq_arrayappend(vm, -2); + + SKIP_EMPTY(p); + if (*p == ',') continue; + if (*p == ']') break; + RETURN_ERROR(1); + } + + p++; + + break; + } + + case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': case '0': + case '-': { + /* Integer */ + + const char *value = p++; + for (;;) { + switch (*p++) { + case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': case '0': + continue; + + default: + break; + } + + p--; + break; + } + + int res = atoi(value); + sq_pushinteger(vm, (SQInteger)res); + + break; + } + + default: + RETURN_ERROR(0); + } + + return p; +} + +#undef SKIP_EMPTY +#undef RETURN_ERROR
--- a/src/script/api/script_event_types.hpp +++ b/src/script/api/script_event_types.hpp @@ -829,4 +829,60 @@ VehicleID vehicle_id; ///< The vehicle aircraft whose destination is too far away. }; +/** + * Event Admin Port, indicating the admin port is sending you information. + * @api game + */ +class ScriptEventAdminPort : public ScriptEvent { +public: + /** + * @param json The JSON string which got sent. + */ + ScriptEventAdminPort(const char *json) : + ScriptEvent(ET_ADMIN_PORT), + json(strdup(json)) + {} + + ~ScriptEventAdminPort() + { + free(this->json); + } + + /** + * Convert an ScriptEvent to the real instance. + * @param instance The instance to convert. + * @return The converted instance. + */ + static ScriptEventAdminPort *Convert(ScriptEvent *instance) { return (ScriptEventAdminPort *)instance; } + + /** + * Get the information that was sent to you back as Squirrel object. + */ + SQInteger GetObject(HSQUIRRELVM vm); + +private: + char *json; ///< The JSON string. + + /** + * Read a table from a JSON string. + * @param vm The VM used. + * @param p The (part of the) JSON string reading. + */ + char *ReadTable(HSQUIRRELVM vm, char *p); + + /** + * Read a value from a JSON string. + * @param vm The VM used. + * @param p The (part of the) JSON string reading. + */ + char *ReadValue(HSQUIRRELVM vm, char *p); + + /** + * Read a string from a JSON string. + * @param vm The VM used. + * @param p The (part of the) JSON string reading. + */ + char *ReadString(HSQUIRRELVM vm, char *p); +}; + #endif /* SCRIPT_EVENT_TYPES_HPP */
--- a/src/script/api/squirrel_export.awk +++ b/src/script/api/squirrel_export.awk @@ -385,7 +385,11 @@ if (mlen <= length(static_methods[i, 0])) mlen = length(static_methods[i, 0]) } for (i = 1; i <= static_method_size; i++) { - print " SQ" api_cls ".DefSQStaticMethod(engine, &" cls "::" static_methods[i, 0] ", " substr(spaces, 1, mlen - length(static_methods[i, 0])) "\"" static_methods[i, 0] "\", " substr(spaces, 1, mlen - length(static_methods[i, 0])) "" static_methods[i, 1] ", \"" static_methods[i, 2] "\");" + if (static_methods[i, 2] == "v") { + print " SQ" api_cls ".DefSQAdvancedStaticMethod(engine, &" cls "::" static_methods[i, 0] ", " substr(spaces, 1, mlen - length(static_methods[i, 0]) - 8) "\"" static_methods[i, 0] "\");" + } else { + print " SQ" api_cls ".DefSQStaticMethod(engine, &" cls "::" static_methods[i, 0] ", " substr(spaces, 1, mlen - length(static_methods[i, 0])) "\"" static_methods[i, 0] "\", " substr(spaces, 1, mlen - length(static_methods[i, 0])) "" static_methods[i, 1] ", \"" static_methods[i, 2] "\");" + } delete static_methods[i] } if (static_method_size != 0) print ""
new file mode 100644 --- /dev/null +++ b/src/script/api/template/template_admin.hpp.sq @@ -0,0 +1,21 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * OpenTTD 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, version 2. + * OpenTTD 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 OpenTTD. If not, see <http://www.gnu.org/licenses/>. + */ + +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "../script_admin.hpp" + +namespace SQConvert { + /* Allow ScriptAdmin to be used as Squirrel parameter */ + template <> inline ScriptAdmin *GetParam(ForceType<ScriptAdmin *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (ScriptAdmin *)instance; } + template <> inline ScriptAdmin &GetParam(ForceType<ScriptAdmin &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(ScriptAdmin *)instance; } + template <> inline const ScriptAdmin *GetParam(ForceType<const ScriptAdmin *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (ScriptAdmin *)instance; } + template <> inline const ScriptAdmin &GetParam(ForceType<const ScriptAdmin &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(ScriptAdmin *)instance; } + template <> inline int Return<ScriptAdmin *>(HSQUIRRELVM vm, ScriptAdmin *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "Admin", res, NULL, DefSQDestructorCallback<ScriptAdmin>, true); return 1; } +} // namespace SQConvert
--- a/src/script/api/template/template_event_types.hpp.sq +++ b/src/script/api/template/template_event_types.hpp.sq @@ -212,3 +212,12 @@ template <> inline const ScriptEventAircraftDestTooFar &GetParam(ForceType<const ScriptEventAircraftDestTooFar &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(ScriptEventAircraftDestTooFar *)instance; } template <> inline int Return<ScriptEventAircraftDestTooFar *>(HSQUIRRELVM vm, ScriptEventAircraftDestTooFar *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "EventAircraftDestTooFar", res, NULL, DefSQDestructorCallback<ScriptEventAircraftDestTooFar>, true); return 1; } } // namespace SQConvert + +namespace SQConvert { + /* Allow ScriptEventAdminPort to be used as Squirrel parameter */ + template <> inline ScriptEventAdminPort *GetParam(ForceType<ScriptEventAdminPort *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (ScriptEventAdminPort *)instance; } + template <> inline ScriptEventAdminPort &GetParam(ForceType<ScriptEventAdminPort &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(ScriptEventAdminPort *)instance; } + template <> inline const ScriptEventAdminPort *GetParam(ForceType<const ScriptEventAdminPort *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (ScriptEventAdminPort *)instance; } + template <> inline const ScriptEventAdminPort &GetParam(ForceType<const ScriptEventAdminPort &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(ScriptEventAdminPort *)instance; } + template <> inline int Return<ScriptEventAdminPort *>(HSQUIRRELVM vm, ScriptEventAdminPort *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "EventAdminPort", res, NULL, DefSQDestructorCallback<ScriptEventAdminPort>, true); return 1; } +} // namespace SQConvert
--- a/src/script/script_instance.cpp +++ b/src/script/script_instance.cpp @@ -297,12 +297,10 @@ SLE_END() }; -static const uint SCRIPTSAVE_MAX_DEPTH = 25; ///< The maximum recursive depth for items stored in the savegame. - /* static */ bool ScriptInstance::SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test) { if (max_depth == 0) { - ScriptLog::Error("Savedata can only be nested to 25 deep. No data saved."); + ScriptLog::Error("Savedata can only be nested to 25 deep. No data saved."); // SQUIRREL_MAX_DEPTH = 25 return false; } @@ -439,7 +437,7 @@ _script_sl_byte = 1; SlObject(NULL, _script_byte); /* Save the data that was just loaded. */ - SaveObject(vm, -1, SCRIPTSAVE_MAX_DEPTH, false); + SaveObject(vm, -1, SQUIRREL_MAX_DEPTH, false); } else if (!this->is_started) { SaveEmpty(); return; @@ -478,10 +476,10 @@ return; } sq_pushobject(vm, savedata); - if (SaveObject(vm, -1, SCRIPTSAVE_MAX_DEPTH, true)) { + if (SaveObject(vm, -1, SQUIRREL_MAX_DEPTH, true)) { _script_sl_byte = 1; SlObject(NULL, _script_byte); - SaveObject(vm, -1, SCRIPTSAVE_MAX_DEPTH, false); + SaveObject(vm, -1, SQUIRREL_MAX_DEPTH, false); this->is_save_data_on_stack = true; } else { SaveEmpty();
--- a/src/script/script_instance.hpp +++ b/src/script/script_instance.hpp @@ -17,6 +17,8 @@ #include "../command_type.h" +static const uint SQUIRREL_MAX_DEPTH = 25; ///< The maximum recursive depth for items stored in the savegame. + /** Runtime information about a script like a pointer to the squirrel vm and the current state. */ class ScriptInstance { public:
--- a/src/script/squirrel_class.hpp +++ b/src/script/squirrel_class.hpp @@ -72,6 +72,16 @@ } /** + * This defines a static method inside a class for Squirrel, which has access to the 'engine' (experts only!). + */ + template <typename Func> + void DefSQAdvancedStaticMethod(Squirrel *engine, Func function_proc, const char *function_name) + { + using namespace SQConvert; + engine->AddMethod(function_name, DefSQAdvancedStaticCallback<CL, Func>, 0, NULL, &function_proc, sizeof(function_proc)); + } + + /** * This defines a static method inside a class for Squirrel with defined params. * @note If you define nparam, make sure that he first param is always 'x', * which is the 'this' inside the function. This is hidden from the rest
--- a/src/script/squirrel_helper.hpp +++ b/src/script/squirrel_helper.hpp @@ -831,6 +831,28 @@ } } + + /** + * A general template for all static advanced method callbacks from Squirrel. + * In here the function_proc is recovered, and the SQCall is called that + * can handle this exact amount of params. + */ + template <typename Tcls, typename Tmethod> + inline SQInteger DefSQAdvancedStaticCallback(HSQUIRRELVM vm) + { + /* Find the amount of params we got */ + int nparam = sq_gettop(vm); + SQUserPointer ptr = NULL; + + /* Get the real function pointer */ + sq_getuserdata(vm, nparam, &ptr, 0); + /* Remove the userdata from the stack */ + sq_pop(vm, 1); + + /* Call the function, which its only param is always the VM */ + return (SQInteger)(*(*(Tmethod *)ptr))(vm); + } + /** * A general template for the destructor of SQ instances. This is needed * here as it has to be in the same scope as DefSQConstructorCallback.