1 //
2 //
3 
4 #include "base.h"
5 
6 #include "globalincs/version.h"
7 
8 #include "freespace.h"
9 
10 #include "gamesequence/gamesequence.h"
11 #include "network/multi.h"
12 #include "parse/parselo.h"
13 #include "pilotfile/pilotfile.h"
14 #include "playerman/player.h"
15 #include "scripting/api/objs/bytearray.h"
16 #include "scripting/api/objs/control_info.h"
17 #include "scripting/api/objs/enums.h"
18 #include "scripting/api/objs/gameevent.h"
19 #include "scripting/api/objs/gamestate.h"
20 #include "scripting/api/objs/player.h"
21 #include "scripting/api/objs/vecmath.h"
22 #include "scripting/util/LuaValueDeserializer.h"
23 #include "scripting/util/LuaValueSerializer.h"
24 #include "utils/Random.h"
25 
26 namespace scripting {
27 namespace api {
28 using Random = ::util::Random;
29 
30 //**********LIBRARY: Base
31 ADE_LIB(l_Base, "Base", "ba", "Base FreeSpace 2 functions");
32 
33 ADE_FUNC(print, l_Base, "string Message", "Prints a string", NULL, NULL)
34 {
35 	mprintf(("%s", lua_tostring(L, -1)));
36 
37 	return ADE_RETURN_NIL;
38 }
39 
40 ADE_FUNC(warning, l_Base, "string Message", "Displays a FreeSpace warning (debug build-only) message with the string provided", NULL, NULL)
41 {
42 	Warning(LOCATION, "%s", lua_tostring(L, -1));
43 
44 	return ADE_RETURN_NIL;
45 }
46 
47 ADE_FUNC(error, l_Base, "string Message", "Displays a FreeSpace error message with the string provided", NULL, NULL)
48 {
49 	Error(LOCATION, "%s", lua_tostring(L, -1));
50 
51 	return ADE_RETURN_NIL;
52 }
53 
54 ADE_FUNC(rand32,
55 	l_Base,
56 	"[number a, number b]",
57 	"Calls FSO's Random::next() function, which is higher-quality than Lua's ANSI C math.random().  If called with no arguments, returns a random integer from [0, 0x7fffffff].  If called with one argument, returns an integer from [0, a).  If called with two arguments, returns an integer from [a, b].",
58 	"number",
59 	"A random integer")
60 {
61 	int a, b;
62 	int numargs = ade_get_args(L, "|ii", &a, &b);
63 
64 	int result;
65 	if (numargs == 2) {
66 		if (a <= b) {
67 			result = Random::next(a, b);
68 		} else {
69 			LuaError(L, "rand32() script function was passed an invalid range (%d ... %d)!", a, b);
70 			result = a; // match behavior of rand_sexp()
71 		}
72 	} else if (numargs == 1) {
73 		if (a > 0) {
74 			result = Random::next(a);
75 		} else {
76 			LuaError(L, "rand32() script function was passed an invalid modulus (%d)!", a);
77 			result = 0;
78 		}
79 	} else {
80 		result = Random::next();
81 	}
82 
83 	return ade_set_error(L, "i", result);
84 }
85 
86 ADE_FUNC(rand32f,
87 	l_Base,
88 	"[number max]",
89 	"Calls FSO's Random::next() function and transforms the result to a float.  If called with no arguments, returns a random float from [0.0, 1.0).  If called with one argument, returns a float from [0.0, max).",
90 	"number",
91 	"A random float")
92 {
93 	float _max;
94 	int numargs = ade_get_args(L, "|f", &_max);
95 
96 	float result = (float)Random::next() * Random::INV_F_MAX_VALUE;
97 
98 	if (numargs > 0)
99 		result *= _max;
100 
101 	return ade_set_error(L, "f", _max);
102 }
103 
104 ADE_FUNC(createOrientation,
105 	l_Base,
106 	ade_overload_list({nullptr,
107 		"number p, number b, number h",
108 		"number r1c1, number r1c2, number r1c3, number r2c1, number r2c2, number r2c3, number r3c1, number r3c2, "
109 		"number r3c3"}),
110 	"Given 0 arguments, creates an identity orientation; 3 arguments, creates an orientation from pitch/bank/heading (in radians); 9 arguments, creates an orientation from a 3x3 row-major order matrix.",
111 	"orientation",
112 	"New orientation object, or the identity orientation on failure")
113 {
114 	matrix m;
115 	int numargs = ade_get_args(L, "|fffffffff", &m.a1d[0], &m.a1d[1], &m.a1d[2], &m.a1d[3], &m.a1d[4], &m.a1d[5], &m.a1d[6], &m.a1d[7], &m.a1d[8]);
116 	if(!numargs)
117 	{
118 		return ade_set_args(L, "o", l_Matrix.Set( matrix_h(&vmd_identity_matrix) ));
119 	}
120 	else if(numargs == 3)
121 	{
122 		angles a = {m.a1d[0], m.a1d[1], m.a1d[2]};
123 		return ade_set_args(L, "o", l_Matrix.Set(matrix_h(&a)));
124 	}
125 	else if(numargs == 9)
126 	{
127 		return ade_set_args(L, "o", l_Matrix.Set(matrix_h(&m)));
128 	}
129 
130 	return ade_set_error(L, "o", l_Matrix.Set(matrix_h()));
131 }
132 
133 ADE_FUNC(createOrientationFromVectors, l_Base, "[vector fvec, vector uvec, vector rvec]",
134 	"Given 0 to 3 arguments, creates an orientation object from 0 to 3 vectors.  (This is essentially a wrapper for the vm_vector_2_matrix function.)  If supplied 0 arguments, this will return the identity orientation.  The first vector, if supplied, must be non-null.",
135 	"orientation",
136 	"New orientation object, or the identity orientation on failure")
137 {
138 	vec3d *fvec = nullptr, *uvec = nullptr, *rvec = nullptr;
139 	int numargs = ade_get_args(L, "|ooo", l_Vector.GetPtr(&fvec), l_Vector.GetPtr(&uvec), l_Vector.GetPtr(&rvec));
140 	if (!numargs)
141 	{
142 		return ade_set_args(L, "o", l_Matrix.Set(matrix_h(&vmd_identity_matrix)));
143 	}
144 	else
145 	{
146 		// if we have any vectors, the first one should be non-null
147 		if (fvec == nullptr)
148 			return ade_set_error(L, "o", l_Matrix.Set(matrix_h()));
149 
150 		matrix m;
151 		vm_vector_2_matrix(&m, fvec, uvec, rvec);
152 		return ade_set_args(L, "o", l_Matrix.Set(matrix_h(&m)));
153 	}
154 }
155 
156 ADE_FUNC(createVector, l_Base, "[number x, number y, number z]", "Creates a vector object", "vector", "Vector object")
157 {
158 	vec3d v3 = vmd_zero_vector;
159 	ade_get_args(L, "|fff", &v3.xyz.x, &v3.xyz.y, &v3.xyz.z);
160 
161 	return ade_set_args(L, "o", l_Vector.Set(v3));
162 }
163 
164 ADE_FUNC(createRandomVector, l_Base, nullptr, "Creates a fairly random normalized vector object.", "vector", "Vector object")
165 {
166 	vec3d v3;
167 	vm_vec_rand_vec(&v3);
168 	return ade_set_args(L, "o", l_Vector.Set(v3));
169 }
170 
171 ADE_FUNC(createSurfaceNormal,
172 	l_Base,
173 	"vector point1, vector point2, vector point3",
174 	"Determines the surface normal of the plane defined by three points.  Returns a normalized vector.",
175 	"vector",
176 	"The surface normal, or NIL if a handle is invalid")
177 {
178 	vec3d *p0 = nullptr, *p1 = nullptr, *p2 = nullptr;
179 	if (!ade_get_args(L, "ooo", l_Vector.GetPtr(&p0), l_Vector.GetPtr(&p1), l_Vector.GetPtr(&p2)))
180 		return ADE_RETURN_NIL;
181 
182 	vec3d dest;
183 	vm_vec_normal(&dest, p0, p1, p2);
184 	return ade_set_args(L, "o", l_Vector.Set(dest));
185 }
186 
187 ADE_FUNC(findIntersection,
188 	l_Base,
189 	"vector line1_point1, vector line1_point2, vector line2_point1, vector line2_point2",
190 	"Determines the point at which two lines intersect.  (The lines are assumed to extend infinitely in both directions; the intersection will not necessarily be between the points.)",
191 	"vector, number",
192 	"Returns two arguments.  The first is the point of intersection, if it exists and is unique (otherwise it will be NIL).  The second is the find_intersection return value: 0 for a unique intersection, -1 if the lines are colinear, and -2 if the lines do not intersect.")
193 {
194 	vec3d *p0 = nullptr, *p0_end = nullptr, *p1 = nullptr, *p1_end = nullptr;
195 	if (!ade_get_args(L, "oooo", l_Vector.GetPtr(&p0), l_Vector.GetPtr(&p0_end), l_Vector.GetPtr(&p1), l_Vector.GetPtr(&p1_end)))
196 		return ADE_RETURN_NIL;
197 
198 	// note: we must translate from this API's two-points method to the code's API of a reference point and a direction vector
199 	vec3d v0, v1;
200 	vm_vec_sub(&v0, p0_end, p0);
201 	vm_vec_sub(&v1, p1_end, p1);
202 
203 	float scalar;
204 	int retval = find_intersection(&scalar, p0, p1, &v0, &v1);
205 
206 	if (retval == 0)
207 	{
208 		// per comments:
209 		// If you want the coords of the intersection, scale v0 by s, then add p0.
210 		vm_vec_scale(&v0, scalar);
211 		vm_vec_add2(&v0, p0);
212 
213 		return ade_set_args(L, "oi", l_Vector.Set(v0), retval);
214 	}
215 	else
216 		return ade_set_args(L, "*i", retval);
217 }
218 
219 ADE_FUNC(findPointOnLineNearestSkewLine,
220 	l_Base,
221 	"vector line1_point1, vector line1_point2, vector line2_point1, vector line2_point2",
222 	"Determines the point on line 1 closest to line 2 when the lines are skew (non-intersecting in 3D space).  (The lines are assumed to extend infinitely in both directions; the point will not necessarily be between the other points.)",
223 	"vector",
224 	"The closest point, or NIL if a handle is invalid")
225 {
226 	vec3d *p0 = nullptr, *p0_end = nullptr, *p1 = nullptr, *p1_end = nullptr;
227 	if (!ade_get_args(L, "oooo", l_Vector.GetPtr(&p0), l_Vector.GetPtr(&p0_end), l_Vector.GetPtr(&p1), l_Vector.GetPtr(&p1_end)))
228 		return ADE_RETURN_NIL;
229 
230 	// note: we must translate from this API's two-points method to the code's API of a reference point and a direction vector
231 	vec3d v0, v1;
232 	vm_vec_sub(&v0, p0_end, p0);
233 	vm_vec_sub(&v1, p1_end, p1);
234 
235 	vec3d dest;
236 	find_point_on_line_nearest_skew_line(&dest, p0, &v0, p1, &v1);
237 	return ade_set_args(L, "o", l_Vector.Set(dest));
238 }
239 
240 ADE_FUNC(getFrametimeOverall, l_Base, NULL, "The overall frame time in seconds since the engine has started", "number", "Overall time (seconds)")
241 {
242 	return ade_set_args(L, "x", game_get_overall_frametime());
243 }
244 
245 ADE_FUNC(getMissionFrametime, l_Base, nullptr, "Gets how long this frame is calculated to take. Use it to for animations, physics, etc to make incremental changes. Increased or decreased based on current time compression", "number", "Frame time (seconds)")
246 {
247 	return ade_set_args(L, "f", flFrametime);
248 }
249 
250 ADE_FUNC(getRealFrametime, l_Base, nullptr, "Gets how long this frame is calculated to take in real time. Not affected by time compression.", "number", "Frame time (seconds)")
251 {
252 	return ade_set_args(L, "f", flRealframetime);
253 }
254 
255 ADE_FUNC_DEPRECATED(getFrametime, l_Base,
256 	"[boolean adjustForTimeCompression]",
257 	"Gets how long this frame is calculated to take. Use it to for animations, physics, etc to make incremental changes.",
258 	"number", "Frame time (seconds)",
259 	gameversion::version(20, 2, 0, 0),
260 	"The parameter of this function is inverted from the naming (passing true returns non-adjusted time). Please use either getMissionFrametime() or getRealFrametime().")
261 {
262 	bool b=false;
263 	ade_get_args(L, "|b", &b);
264 
265 	return ade_set_args(L, "f", b ? flRealframetime : flFrametime);
266 }
267 
268 ADE_FUNC(getCurrentGameState, l_Base, "[number depth]", "Gets current FreeSpace state; if a depth is specified, the state at that depth is returned. (IE at the in-game options game, a depth of 1 would give you the game state, while the function defaults to 0, which would be the options screen.", "gamestate", "Current game state at specified depth, or invalid handle if no game state is active yet")
269 {
270 	int depth = 0;
271 	ade_get_args(L, "|i", &depth);
272 
273 	if(depth > gameseq_get_depth())
274 		return ade_set_args(L, "o", l_GameState.Set(gamestate_h()));
275 
276 	return ade_set_args(L, "o", l_GameState.Set(gamestate_h(gameseq_get_state(depth))));
277 }
278 
279 ADE_FUNC(getCurrentMPStatus, l_Base, nullptr, "Gets this computers current MP status", "string", "Current MP status" )
280 {
281 	if ( MULTIPLAYER_MASTER )
282 		return ade_set_args(L, "s", "MULTIPLAYER_MASTER");
283 
284 	if ( MULTIPLAYER_HOST )
285 		return ade_set_args(L, "s", "MULTIPLAYER_HOST");
286 
287 	if ( MULTIPLAYER_CLIENT )
288 		return ade_set_args(L, "s", "MULTIPLAYER_CLIENT");
289 
290 	if ( MULTIPLAYER_STANDALONE )
291 		return ade_set_args(L, "s", "MULTIPLAYER_STANDALONE");
292 
293 	return ade_set_args(L, "s", "SINGLEPLAYER");
294 }
295 
296 ADE_FUNC(getCurrentPlayer, l_Base, NULL, "Gets a handle of the currently used player.<br><b>Note:</b> If there is no current player then the first player will be returned, check the game state to make sure you have a valid player handle.", "player", "Player handle")
297 {
298 	return ade_set_args(L, "o", l_Player.Set(player_h(&Players[Player_num])));
299 }
300 
301 ADE_FUNC(loadPlayer, l_Base, "string callsign", "Loads the player with the specified callsign.", "player",
302          "Player handle or invalid handle on load failure")
303 {
304 	const char* callsign;
305 	if (!ade_get_args(L, "s", &callsign)) {
306 		return ade_set_error(L, "o", l_Player.Set(player_h()));
307 	}
308 
309 	player plr;
310 	plr.reset();
311 	pilotfile loader;
312 	if (!loader.load_player(callsign, &plr)) {
313 		return ade_set_error(L, "o", l_Player.Set(player_h()));
314 	}
315 
316 	return ade_set_args(L, "o", l_Player.Set(player_h(plr)));
317 }
318 
319 ADE_FUNC(savePlayer, l_Base, "player plr", "Saves the specified player.", "boolean",
320          "true of successfull, false otherwise")
321 {
322 	player_h* plh;
323 	if (!ade_get_args(L, "o", l_Player.GetPtr(&plh))) {
324 		return ADE_RETURN_FALSE;
325 	}
326 
327 	pilotfile loader;
328 	return ade_set_args(L, "b", loader.save_player(plh->get()));
329 }
330 
331 ADE_FUNC(setControlMode,
332 	l_Base,
333 	"nil|enumeration mode /* LE_*_CONTROL */",
334 	"Sets the current control mode for the game.",
335 	"string",
336 	"Current control mode")
337 {
338 	enum_h *e = NULL;
339 	if (!(ade_get_args(L, "|o", l_Enum.GetPtr(&e)))) {
340 		if (lua_game_control & LGC_NORMAL)
341 			return ade_set_args(L, "s", "NORMAL");
342 		else if (lua_game_control & LGC_STEERING)
343 			return ade_set_args(L, "s", "STEERING");
344 		else if (lua_game_control & LGC_FULL)
345 			return ade_set_args(L, "s", "FULL");
346 		else
347 			return ade_set_error(L, "s", "");
348 	}
349 
350 	if (!e) {
351 		return ade_set_error(L, "s", "");
352 	}
353 
354 	switch (e->index) {
355 		case LE_NORMAL_CONTROLS:
356 			lua_game_control |= LGC_NORMAL;
357 			lua_game_control &= ~(LGC_STEERING|LGC_FULL);
358 			return ade_set_args(L, "s", "NORMAL CONTROLS");
359 		case LE_LUA_STEERING_CONTROLS:
360 			lua_game_control |= LGC_STEERING;
361 			lua_game_control &= ~(LGC_NORMAL|LGC_FULL);
362 			return ade_set_args(L, "s", "LUA STEERING CONTROLS");
363 		case LE_LUA_FULL_CONTROLS:
364 			lua_game_control |= LGC_FULL;
365 			lua_game_control &= ~(LGC_STEERING|LGC_NORMAL);
366 			return ade_set_args(L, "s", "LUA FULL CONTROLS");
367 		default:
368 			return ade_set_error(L, "s", "");
369 	}
370 }
371 
372 ADE_FUNC(setButtonControlMode,
373 	l_Base,
374 	"nil|enumeration mode /* LE_*_BUTTON_CONTROL */",
375 	"Sets the current control mode for the game.",
376 	"string",
377 	"Current control mode")
378 {
379 	enum_h *e = NULL;
380 	if (!(ade_get_args(L, "|o", l_Enum.GetPtr(&e)))) {
381 		if (lua_game_control & LGC_B_NORMAL)
382 			return ade_set_args(L, "s", "NORMAL");
383 		else if (lua_game_control & LGC_B_OVERRIDE)
384 			return ade_set_args(L, "s", "OVERRIDE");
385 		else if (lua_game_control & LGC_B_ADDITIVE)
386 			return ade_set_args(L, "s", "ADDITIVE");
387 		else
388 			return ade_set_error(L, "s", "");
389 	}
390 
391 	if (!e) {
392 		return ade_set_error(L, "s", "");
393 	}
394 
395 	switch (e->index) {
396 		case LE_NORMAL_BUTTON_CONTROLS:
397 			lua_game_control |= LGC_B_NORMAL;
398 			lua_game_control &= ~(LGC_B_ADDITIVE|LGC_B_OVERRIDE);
399 			return ade_set_args(L, "s", "NORMAL BUTTON CONTROL");
400 		case LE_LUA_ADDITIVE_BUTTON_CONTROL:
401 			lua_game_control |= LGC_B_ADDITIVE;
402 			lua_game_control &= ~(LGC_B_NORMAL|LGC_B_OVERRIDE);
403 			return ade_set_args(L, "s", "LUA ADDITIVE BUTTON CONTROL");
404 		case LE_LUA_OVERRIDE_BUTTON_CONTROL:
405 			lua_game_control |= LGC_B_OVERRIDE;
406 			lua_game_control &= ~(LGC_B_ADDITIVE|LGC_B_NORMAL);
407 			return ade_set_args(L, "s", "LUA OVERRIDE BUTTON CONTROL");
408 		default:
409 			return ade_set_error(L, "s", "");
410 	}
411 }
412 
413 ADE_FUNC(getControlInfo, l_Base, nullptr, "Gets the control info handle.", "control_info", "control info handle")
414 {
415 	return ade_set_args(L, "o", l_Control_Info.Set(1));
416 }
417 
418 ADE_FUNC(setTips, l_Base, "boolean", "Sets whether to display tips of the day the next time the current pilot enters the mainhall.", nullptr, nullptr)
419 {
420 	if (Player == NULL)
421 		return ADE_RETURN_NIL;
422 
423 	bool tips = false;
424 
425 	ade_get_args(L, "b", &tips);
426 
427 	if (tips)
428 		Player->tips = 1;
429 	else
430 		Player->tips = 0;
431 
432 	return ADE_RETURN_NIL;
433 }
434 
435 ADE_FUNC(getGameDifficulty, l_Base, nullptr,
436          "Returns the difficulty level from 1-5, 1 being the lowest, (Very Easy) and 5 being the highest (Insane)",
437          "number", "Difficulty level as integer")
438 {
439 	return ade_set_args(L, "i", Game_skill_level+1);
440 }
441 
442 ADE_FUNC(postGameEvent, l_Base, "gameevent Event", "Sets current game event. Note that you can crash FreeSpace 2 by posting an event at an improper time, so test extensively if you use it.", "boolean", "True if event was posted, false if passed event was invalid")
443 {
444 	gameevent_h *gh = NULL;
445 	if(!ade_get_args(L, "o", l_GameEvent.GetPtr(&gh)))
446 		return ade_set_error(L, "b", false);
447 
448 	if(!gh->IsValid())
449 		return ade_set_error(L, "b", false);
450 
451 	gameseq_post_event(gh->Get());
452 
453 	return ADE_RETURN_TRUE;
454 }
455 
456 ADE_FUNC(XSTR,
457 		 l_Base,
458 		 "string text, number id",
459 		 "Gets the translated version of text with the given id. "
460 			 "The uses the tstrings table for performing the translation. Passing -1 as the id will always return the given text.",
461 		 "string",
462 		 "The translated text") {
463 	const char* text = nullptr;
464 	int id = -1;
465 
466 	if (!ade_get_args(L, "si", &text, &id)) {
467 		return ADE_RETURN_NIL;
468 	}
469 
470 	SCP_string xstr;
471 	sprintf(xstr, "XSTR(\"%s\", %d)", text, id);
472 
473 	SCP_string translated;
474 	lcl_ext_localize(xstr, translated);
475 
476 	return ade_set_args(L, "s", translated.c_str());
477 }
478 
479 ADE_FUNC(inMissionEditor, l_Base, nullptr, "Determine if the current script is running in the mission editor (e.g. FRED2). This should be used to control which code paths will be executed even if running in the editor.", "boolean", "true when we are in the mission editor, false otherwise") {
480 	return ade_set_args(L, "b", Fred_running != 0);
481 }
482 
483 ADE_FUNC(isEngineVersionAtLeast,
484 		 l_Base,
485 		 "number major, number minor, number build, [number revision = 0]",
486 		 "Checks if the current version of the engine is at least the specified version. This can be used to check if a feature introduced in a later version of the engine is available.",
487 		 "boolean",
488 		 "true if the version is at least the specified version. false otherwise.") {
489 	int major = 0;
490 	int minor = 0;
491 	int build = 0;
492 	int revision = 0;
493 
494 	if (!ade_get_args(L, "iii|i", &major, &minor, &build, &revision)) {
495 		return ade_set_error(L, "b", false);
496 	}
497 
498 	auto version = gameversion::version(major, minor, build, revision);
499 
500 	return ade_set_args(L, "b", gameversion::check_at_least(version));
501 }
502 
503 ADE_FUNC(getCurrentLanguage,
504 		 l_Base,
505 		 nullptr,
506 		 "Determines the language that is being used by the engine. This returns the full name of the language (e.g. \"English\").",
507 		 "string",
508 		 "The current game language")
509 {
510 	const char *lang_name;
511 	if (Lcl_current_lang == LCL_UNTRANSLATED)
512 		lang_name = "UNTRANSLATED";
513 	else if (Lcl_current_lang == LCL_RETAIL_HYBRID)
514 		lang_name = "RETAIL HYBRID";
515 	else
516 		lang_name = Lcl_languages[lcl_get_current_lang_index()].lang_name;
517 
518 	return ade_set_args(L, "s", lang_name);
519 }
520 
521 ADE_FUNC(getCurrentLanguageExtension,
522 		 l_Base,
523 		 nullptr,
524 		 "Determines the file extension of the language that is being used by the engine. "
525 			 "This returns a short code for the current language that can be used for creating language specific file names (e.g. \"gr\" when the current language is German). "
526 			 "This will return an empty string for the default language.",
527 		 "string",
528 		 "The current game language")
529 {
530 	int lang = lcl_get_current_lang_index();
531 	return ade_set_args(L, "s", Lcl_languages[lang].lang_ext);
532 }
533 
534 ADE_FUNC(getVersionString, l_Base, nullptr,
535          "Returns a string describing the version of the build that is currently running. This is mostly intended to "
536          "be displayed to the user and not processed by a script so don't rely on the exact format of the string.",
537          "string", "The version information")
538 {
539 	auto str = gameversion::get_version_string();
540 	return ade_set_args(L, "s", str.c_str());
541 }
542 
543 ADE_VIRTVAR(MultiplayerMode, l_Base, "boolean", "Determines if the game is currently in single- or multiplayer mode",
544             "boolean",
545             "true if in multiplayer mode, false if in singleplayer. If neither is the case (e.g. on game init) nil "
546             "will be returned")
547 {
548 	bool b;
549 	if (!ade_get_args(L, "*|b", &b)) {
550 		return ADE_RETURN_NIL;
551 	}
552 
553 	if (ADE_SETTING_VAR) {
554 		if (b) {
555 			Game_mode &= ~GM_NORMAL;
556 			Game_mode |= GM_MULTIPLAYER;
557 		} else {
558 			Game_mode &= ~GM_MULTIPLAYER;
559 			Game_mode |= GM_NORMAL;
560 		}
561 	}
562 
563 	if (Game_mode & GM_MULTIPLAYER) {
564 		return ADE_RETURN_TRUE;
565 	} else if (Game_mode & GM_NORMAL) {
566 		return ADE_RETURN_FALSE;
567 	} else {
568 		return ADE_RETURN_NIL;
569 	}
570 }
571 
572 ADE_FUNC(serializeValue,
573 	l_Base,
574 	"any value",
575 	"Serializes the specified value so that it can be stored and restored consistently later. The actual format of the "
576 	"returned data is implementation specific but will be deserializable by at least this engine version and following "
577 	"versions.",
578 	"bytearray",
579 	"The serialized representation of the value or nil on error.")
580 {
581 	luacpp::LuaValue value;
582 	if (!ade_get_args(L, "a", &value)) {
583 		return ADE_RETURN_NIL;
584 	}
585 
586 	try {
587 		util::LuaValueSerializer serializer(std::move(value));
588 		auto serialized = serializer.serialize();
589 
590 		return ade_set_args(L, "o", l_Bytearray.Set(bytearray_h(std::move(serialized))));
591 	} catch (const std::exception& e) {
592 		LuaError(L, "Failed to serialize value: %s", e.what());
593 		return ADE_RETURN_NIL;
594 	}
595 }
596 
597 ADE_FUNC(deserializeValue,
598 	l_Base,
599 	"bytearray serialized",
600 	"Deserializes a previously serialized Lua value.",
601 	"any",
602 	"The deserialized Lua value.")
603 {
604 	bytearray_h* array = nullptr;
605 	if (!ade_get_args(L, "o", l_Bytearray.GetPtr(&array))) {
606 		return ade_set_args(L, "o", l_Bytearray.Set(bytearray_h()));
607 	}
608 
609 	try {
610 		util::LuaValueDeserializer deserializer(L);
611 		auto deserialized = deserializer.deserialize(array->data());
612 
613 		return ade_set_args(L, "a", deserialized);
614 	} catch (const std::exception& e) {
615 		LuaError(L, "Failed to deserialize value: %s", e.what());
616 		return ADE_RETURN_NIL;
617 	}
618 }
619 
620 //**********SUBLIBRARY: Base/Events
621 ADE_LIB_DERIV(l_Base_Events, "GameEvents", NULL, "Freespace 2 game events", l_Base);
622 
623 ADE_INDEXER(l_Base_Events, "number/string IndexOrName", "Array of game events", "gameevent", "Game event, or invalid gameevent handle if index is invalid")
624 {
625 	const char* name;
626 	if(!ade_get_args(L, "*s", &name))
627 		return ade_set_error(L, "o", l_GameEvent.Set(gameevent_h()));
628 
629 	int idx = gameseq_get_event_idx(name);
630 
631 	if(idx < 0)
632 	{
633 		idx = atoi(name);
634 
635 		//Lua-->FS2
636 		idx--;
637 
638 		if(idx < 0 || idx >= Num_gs_event_text)
639 			return ade_set_error(L, "o", l_GameEvent.Set(gameevent_h()));
640 	}
641 
642 	return ade_set_args(L, "o", l_GameEvent.Set(gameevent_h(idx)));
643 }
644 
645 ADE_FUNC(__len, l_Base_Events, NULL, "Number of events", "number", "Number of events")
646 {
647 	return ade_set_args(L, "i", Num_gs_event_text);
648 }
649 
650 //**********SUBLIBRARY: Base/States
651 ADE_LIB_DERIV(l_Base_States, "GameStates", NULL, "Freespace 2 states", l_Base);
652 
653 ADE_INDEXER(l_Base_States, "number/string IndexOrName", "Array of game states", "gamestate", "Game state, or invalid gamestate handle if index is invalid")
654 {
655 	const char* name;
656 	if(!ade_get_args(L, "*s", &name))
657 		return ade_set_error(L, "o", l_GameState.Set(gamestate_h()));
658 
659 	int idx = gameseq_get_state_idx(name);
660 
661 	if(idx < 0)
662 	{
663 		idx = atoi(name);
664 
665 		//Lua-->FS2
666 		idx--;
667 
668 		if(idx < 0 || idx >= Num_gs_state_text)
669 			return ade_set_error(L, "o", l_GameState.Set(gamestate_h()));
670 	}
671 
672 	return ade_set_args(L, "o", l_GameState.Set(gamestate_h(idx)));
673 }
674 
675 ADE_FUNC(__len, l_Base_States, NULL, "Number of states", "number", "Number of states")
676 {
677 	return ade_set_args(L, "i", Num_gs_state_text);
678 }
679 
680 
681 }
682 }
683 
684