Rigs of Rods 2026.01
Soft-body Physics Simulation
Loading...
Searching...
No Matches
ServerScriptSequencer.cpp
Go to the documentation of this file.
1
2// =============================================================================
3// This file is adopted from rorserver at commit 4a7109ae2d9a081ccfdad8cc696dc54efe49acb3
4// Changes from the original are marked with "//RIGSOFRODS"
5// =============================================================================
6
7/*
8This file is part of "Rigs of Rods Server" (Relay mode)
9
10Copyright 2007 Pierre-Michel Ricordel
11Copyright 2014+ Rigs of Rods Community
12
13"Rigs of Rods Server" is free software: you can redistribute it
14and/or modify it under the terms of the GNU General Public License
15as published by the Free Software Foundation, either version 3
16of the License, or (at your option) any later version.
17
18"Rigs of Rods Server" is distributed in the hope that it will
19be useful, but WITHOUT ANY WARRANTY; without even the implied
20warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
21See the GNU General Public License for more details.
22
23You should have received a copy of the GNU General Public License
24along with Foobar. If not, see <http://www.gnu.org/licenses/>.
25*/
26
28
29#include "ServerScriptLogger.h"
30#include "ServerScriptConfig.h"
31#include "ServerScriptEngine.h"
32#include "ScriptEngine.h" // RIGSOFRODS: for QueueStringForExecution()
33#include "Utils.h"
34
35#include <stdio.h>
36#include <time.h>
37#include <chrono>
38#include <string>
39#include <iostream>
40#include <stdexcept>
41#include <sstream>
42#include <memory>
43
44using namespace RoR;
45
46
48 m_start_time = static_cast<int>(time(nullptr));
49}
50
54
55int ServerScriptSequencer::Initialize(const std::string& script_filename) {
56 m_script_engine = std::unique_ptr<ServerScriptEngine>(new ServerScriptEngine(this));
57 return m_script_engine->loadScript(script_filename);
58}
59
61 // stop the script engine
62 m_script_engine->StopTimerThread();
63 m_script_engine.reset();
64 // delete all clients
65 for (unsigned int i = 0; i < m_clients.size(); i++) {
66 delete m_clients[i];
67 }
68 m_clients.clear();
69}
70
72 // WARNING: be sure that this is only called within a clients_mutex lock!
73
74 // check for duplicate names
75 for (unsigned int i = 0; i < m_clients.size(); i++) {
76 if (nick == SanitizeUtf8String(m_clients[i]->user.username)) {
77 return true;
78 }
79 }
80 return false;
81}
82
83
85 // WARNING: be sure that this is only called within a clients_mutex lock!
86
87 int col = 0;
88 for (;;) // TODO: How many colors ARE there?
89 {
90 bool collision = false;
91 for (unsigned int i = 0; i < m_clients.size(); i++) {
92 if (m_clients[i]->user.colournum == col) {
93 collision = true;
94 break;
95 }
96 }
97 if (!collision) {
98 return col;
99 }
100 col++;
101 }
102}
103
105 //we have a confirmed client that wants to play
106 //try to find a place for him
107 Logger::Log(LOG_DEBUG, "got instance in createClient()");
108
109 std::lock_guard<std::mutex> scoped_lock(m_clients_mutex);
110
111 std::string nick = SanitizeUtf8String(user.username);
112
113 if (nick.empty())
114 {
115 nick = "Anonymous";
116 strncpy(user.username, nick.c_str(), RORNET_MAX_USERNAME_LEN - 1);
117 }
119 Logger::Log(LOG_WARN, std::string("found duplicate nick, getting new one: ") + nick);
120
121 // shorten username so the number will fit (only if its too long already)
122 std::string new_nick_base = nick.substr(0, RORNET_MAX_USERNAME_LEN - 4) + "-";
123
124 for (int i = 2; i < 99; i++) {
125 nick = new_nick_base + std::to_string(i);
127 Logger::Log(LOG_WARN, std::string("New username was composed: ") + nick);
128 strncpy(user.username, nick.c_str(), RORNET_MAX_USERNAME_LEN - 1);
129 break;
130 }
131 }
132 }
133
134 // assign unique userid
137
138 // assign color
140
141 //okay, create the client slot
143 to_add->user = user;
144 m_clients.push_back(to_add);
145
146 // Do script callback
147#ifdef WITH_ANGELSCRIPT
148 if (m_script_engine != nullptr) {
149 m_script_engine->playerAdded(client_id);
150 }
151#endif //WITH_ANGELSCRIPT
152
153 // done!
154 Logger::Log(LOG_VERBOSE, "ServerScriptSequencer: New client added");
155}
156
158 std::lock_guard<std::mutex> scoped_lock(m_clients_mutex);
159 return (int) m_clients.size();
160}
161
163
164 ServerScriptClient *client = this->FindClientById(static_cast<unsigned int>(uid));
165 if (client == nullptr) {
167 "ServerScriptSequencer::QueueClientForDisconnect() Internal error, got non-existent user ID: %d", uid);
168 return;
169 }
170
171 // RIGSOFRODS: We always call 'playerDeleted' callback externally - even rorserver does it mostly so.
172
173 EraseIf(m_clients, [uid](ServerScriptClient* client) { return client->user.uniqueid == uid; });
174}
175
176void ServerScriptSequencer::sendGameCommand(int uid, std::string cmd) {
177 const char *data = cmd.c_str();
178 int size = cmd.size();
179
180 // RIGSOFRODS: only forward commands for the player, bots currently don't have any means to deal with them.
181
182 if (uid == TO_ALL || uid == App::GetNetwork()->GetLocalUserData().uniqueid)
183 {
185 }
186}
187
188// this does not lock the clients_mutex, make sure it is locked before hand
189// note: uid==-1==TO_ALL = broadcast your message to all players
190void ServerScriptSequencer::serverSay(std::string msg, int uid, int type) {
191 switch (type) {
192 case FROM_SERVER:
193 msg = std::string("SERVER: ") + msg;
194 break;
195
196 case FROM_HOST:
197 if (uid == -1) {
198 msg = std::string("Host(general): ") + msg;
199 } else {
200 msg = std::string("Host(private): ") + msg;
201 }
202 break;
203
204 case FROM_RULES:
205 msg = std::string("Rules: ") + msg;
206 break;
207
208 case FROM_MOTD:
209 msg = std::string("MOTD: ") + msg;
210 break;
211 }
212
213 std::string msg_valid = SanitizeUtf8String(msg);
214 auto itor = m_clients.begin();
215 auto endi = m_clients.end();
216 for (; itor != endi; ++itor) {
217 ServerScriptClient *client = *itor;
218 if ((uid == TO_ALL || ((int) client->user.uniqueid) == uid)) {
219 // RIGSOFRODS: for the local player (AUTH_ADMIN), just
220 }
221 }
222}
223
225 ServerScriptClient *kicked_client = this->FindClientById(static_cast<unsigned int>(kuid));
226 if (kicked_client == nullptr) {
227 return false;
228 }
229
230 this->QueueClientForDisconnect(kicked_client->user.uniqueid);
231 return true;
232}
233
234// RIGSOFRODS: chops of `queueMessage()` in rorserver.
235
237{
238 std::lock_guard<std::mutex> scoped_lock(m_clients_mutex);
239 ServerScriptClient *client = this->FindClientById(static_cast<unsigned int>(uid));
240 if (client == nullptr) {
241 return;
242 }
243
244 int publishMode = BROADCAST_NORMAL;
245
246 // Do a script callback
247 if (m_script_engine) {
248 int scriptpub = m_script_engine->streamAdded(client->user.uniqueid, reg);
249
250 // We only support blocking and normal at the moment. Other modes are not supported.
251 switch (scriptpub) {
252 case BROADCAST_AUTO:
253 break;
254
255 case BROADCAST_BLOCK:
256 publishMode = BROADCAST_BLOCK;
257 break;
258
259 case BROADCAST_NORMAL:
260 publishMode = BROADCAST_NORMAL;
261 break;
262
263 default:
264 Logger::Log(LOG_ERROR, "Stream broadcasting mode not supported.");
265 break;
266 }
267 }
268
269 int streamid = reg->origin_streamid;
270 if (publishMode != BROADCAST_BLOCK) {
271 // Add the stream
272 reg->name[127] = 0;
273 Logger::Log(LOG_VERBOSE, " * new stream registered: %d:%d, type: %d, name: '%s', status: %d",
274 client->user.uniqueid, streamid, reg->type, reg->name, reg->status);
275 client->streams[streamid] = *reg;
276 }
277 else
278 {
279 Logger::Log(LOG_INFO, " * stream reg blocked: %d:%d, type: %d, name: '%s', status: %d",
280 client->user.uniqueid, streamid, reg->type, reg->name, reg->status);
281 }
282}
283
284void ServerScriptSequencer::queueMessageStreamUnregister(int uid, unsigned int streamid) {
285 std::lock_guard<std::mutex> scoped_lock(m_clients_mutex);
286
287 ServerScriptClient *client = this->FindClientById(static_cast<unsigned int>(uid));
288 if (client == nullptr) {
289 return;
290 }
291
292 int publishMode = BROADCAST_NORMAL;
293
294 // Remove the stream
295 if (client->streams.erase(streamid) > 0) {
296 Logger::Log(LOG_VERBOSE, " * stream deregistered: %d:%d", client->user.uniqueid, streamid);
297 publishMode = BROADCAST_ALL;
298 }
299}
300
302 std::lock_guard<std::mutex> scoped_lock(m_clients_mutex);
303
304 ServerScriptClient *client = this->FindClientById(static_cast<unsigned int>(uid));
305 if (client == nullptr) {
306 return;
307 }
308
309 int publishMode = BROADCAST_NORMAL;
310
311 // from client
312 Logger::Log(LOG_INFO, "User disconnects on request: " + SanitizeUtf8String(client->user.username));
314 // RIGSOFRODS: RoRserver doesn't seem to invoke 'playerDeleted' on clean disconnect by player, but we do it for consistency.
315 m_script_engine->playerDeleted(client->user.uniqueid, 0);
316}
317
318void ServerScriptSequencer::queueMessagePlayerChat(int uid, const std::string & data) {
319 std::lock_guard<std::mutex> scoped_lock(m_clients_mutex);
320
321 ServerScriptClient *client = this->FindClientById(static_cast<unsigned int>(uid));
322 if (client == nullptr) {
323 return;
324 }
325
326 int publishMode = BROADCAST_NORMAL;
327
328 std::string str = SanitizeUtf8String(data);
329 Logger::Log(LOG_INFO, "CHAT| %s: %s", SanitizeUtf8String(client->user.username).c_str(), str.c_str());
330
331 publishMode = BROADCAST_ALL;
332 if (str[0] == '!') {
333 publishMode = BROADCAST_BLOCK; // no broadcast of server commands!
334 }
335
336 // Script callback
337 if (m_script_engine) {
338 int scriptpub = m_script_engine->playerChat(client->user.uniqueid, str);
339 if (scriptpub != BROADCAST_AUTO) publishMode = scriptpub;
340 }
341
342 if (str == "!help") {
343 serverSay(std::string("builtin commands:"), uid);
344 serverSay(std::string("!help !version, !list, !say, !kick"), uid);
345 }
346
347 if (str == "!version") {;
348 serverSay("Local script", uid);
349 } else if (str == "!list") {
351 {
352 serverSay(std::string(" uid | auth | nick | ip"), uid);
353 }
354 else
355 {
356 serverSay(std::string(" uid | auth | nick"), uid);
357 }
358
359 for (unsigned int i = 0; i < m_clients.size(); i++) {
360 if (i >= m_clients.size())
361 break;
362 char authst[10] = "";
363 if (m_clients[i]->user.authstatus & RoRnet::AUTH_ADMIN) strcat(authst, "A");
364 if (m_clients[i]->user.authstatus & RoRnet::AUTH_MOD) strcat(authst, "M");
365 if (m_clients[i]->user.authstatus & RoRnet::AUTH_RANKED) strcat(authst, "R");
366 if (m_clients[i]->user.authstatus & RoRnet::AUTH_BOT) strcat(authst, "B");
367 if (m_clients[i]->user.authstatus & RoRnet::AUTH_BANNED) strcat(authst, "X");\
368
369 char tmp2[256] = "";
370
371 sprintf(tmp2, "% 3d | %-6s | %-20s", m_clients[i]->user.uniqueid, authst,
372 SanitizeUtf8String(m_clients[i]->user.username).c_str());
373
374 serverSay(std::string(tmp2), uid);
375 }
376
377 } else if (str.substr(0, 6) == "!kick ") {
379 int kuid = -1;
380 char kickmsg_tmp[256] = "";
381 int res = sscanf(str.substr(6).c_str(), "%d %s", &kuid, kickmsg_tmp);
382 std::string kickMsg = std::string(kickmsg_tmp);
383 Ogre::StringUtil::trim(kickMsg);
384 if (res != 2 || kuid == -1 || !kickMsg.size()) {
385 serverSay(std::string("usage: !kick <uid> <message>"), uid);
386 serverSay(std::string("example: !kick 3 bye!"), uid);
387 } else {
388 bool kicked = Kick(kuid);
389 if (!kicked)
390 serverSay(std::string("kick not successful: uid not found!"), uid);
391 }
392 } else {
393 // not allowed
394 serverSay(std::string("You are not authorized to kick people!"), uid);
395 }
396
397 } else if (str.substr(0, 5) == "!say ") {
399 int kuid = -2;
400 char saymsg_tmp[256] = "";
401 int res = sscanf(str.substr(5).c_str(), "%d %s", &kuid, saymsg_tmp);
402 std::string sayMsg = std::string(saymsg_tmp);
403 Ogre::StringUtil::trim(sayMsg);
404 if (res != 2 || kuid < -1 || !sayMsg.size()) {
405 serverSay(std::string("usage: !say <uid> <message> (use uid -1 for general broadcast)"), uid);
406 serverSay(std::string("example: !say 3 Wecome to this server!"), uid);
407 } else {
408 serverSay(sayMsg, kuid, FROM_HOST);
409 }
410
411 } else {
412 // not allowed
413 serverSay(std::string("You are not authorized to use this command!"), uid);
414 }
415 }
416}
417
418void ServerScriptSequencer::queueMessageGameCmd(int uid, const std::string & data) {
419 std::lock_guard<std::mutex> scoped_lock(m_clients_mutex);
420
421 ServerScriptClient *client = this->FindClientById(static_cast<unsigned int>(uid));
422 if (client == nullptr) {
423 return;
424 }
425
426 // script message
427 if (m_script_engine) m_script_engine->gameCmd(client->user.uniqueid, std::string(data));
428}
429
433
435 return this->FindClientById(static_cast<unsigned int>(uid));
436}
437
438
440{
441
442 // All script callbacks must be invoked while clients-mutex is locked
443 std::lock_guard<std::mutex> scoped_lock(m_clients_mutex);
444 m_script_engine->frameStep(dt);
445
446}
447
448// clients_mutex needs to be locked wen calling this method
449// Invoked either from ServerScriptSequencer or ServerScript
451 auto itor = m_clients.begin();
452 auto endi = m_clients.end();
453 for (; itor != endi; ++itor) {
454 ServerScriptClient *client = *itor;
455 if (client->user.uniqueid == client_id) {
456 return client;
457 }
458 }
459 return nullptr;
460}
461
@ LOG_ERROR
@ LOG_INFO
@ LOG_DEBUG
@ LOG_VERBOSE
@ LOG_WARN
void queueStringForExecution(const Ogre::String command)
Queues a string for execution.
std::vector< ServerScriptClient * > m_clients
void sendGameCommand(int uid, std::string cmd)
void queueMessageGameCmd(int uid, const std::string &data)
ServerScriptClient * getClient(int uid)
std::mutex m_clients_mutex
guards access to m_clients and execution of script callbacks.
int Initialize(const std::string &script_filename)
bool CheckNickIsUnique(std::string &nick)
void serverSay(std::string msg, int uid=-1, int type=0)
void queueMessageStreamUnregister(int uid, unsigned int streamid)
void queueMessageStreamRegister(int uid, RoRnet::StreamRegister *reg)
ServerScriptClient * FindClientById(unsigned int client_id)
void queueMessagePlayerChat(int uid, const std::string &data)
void createClient(RoRnet::UserInfo &user)
void QueueClientForDisconnect(int client_id)
std::unique_ptr< ServerScriptEngine > m_script_engine
void Log(ServerLogLevel level, const char *format,...)
ScriptEngine * GetScriptEngine()
Network * GetNetwork()
void EraseIf(std::vector< T, A > &c, Predicate pred)
Definition Utils.h:75
std::string SanitizeUtf8String(std::string const &str_in)
Definition Utils.cpp:120
static const int TO_ALL
@ AUTH_MOD
moderator status
Definition RoRnet.h:80
@ AUTH_BANNED
banned
Definition RoRnet.h:82
@ AUTH_BOT
bot status
Definition RoRnet.h:81
@ AUTH_RANKED
ranked status
Definition RoRnet.h:79
@ AUTH_ADMIN
admin on the server
Definition RoRnet.h:78
#define RORNET_MAX_USERNAME_LEN
bytes.
Definition RoRnet.h:33
RoRnet::UserInfo user
user information
std::map< unsigned int, RoRnet::StreamRegister > streams
< Sent from the client to server and vice versa, to broadcast a new stream
Definition RoRnet.h:160
int32_t type
0 = Actor, 1 = Character, 3 = ChatSystem
Definition RoRnet.h:161
int32_t origin_streamid
origin streamid
Definition RoRnet.h:164
char name[128]
file name
Definition RoRnet.h:165
int32_t status
initial stream status
Definition RoRnet.h:162
int32_t authstatus
auth status set by server: AUTH_*
Definition RoRnet.h:192
char username[RORNET_MAX_USERNAME_LEN]
the nickname of the user (UTF-8)
Definition RoRnet.h:196
int32_t colournum
colour set by server
Definition RoRnet.h:194
uint32_t uniqueid
user unique id
Definition RoRnet.h:191