1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "common/savefile.h"
24 #include "ags/lib/std/math.h"
25 #include "ags/shared/core/platform.h"
26 #include "ags/shared/ac/audio_clip_type.h"
27 #include "ags/engine/ac/global_game.h"
28 #include "ags/shared/ac/common.h"
29 #include "ags/shared/ac/view.h"
30 #include "ags/engine/ac/character.h"
31 #include "ags/engine/ac/draw.h"
32 #include "ags/engine/ac/dynamic_sprite.h"
33 #include "ags/engine/ac/event.h"
34 #include "ags/engine/ac/game.h"
35 #include "ags/engine/ac/game_setup.h"
36 #include "ags/shared/ac/game_setup_struct.h"
37 #include "ags/engine/ac/game_state.h"
38 #include "ags/engine/ac/global_character.h"
39 #include "ags/engine/ac/global_gui.h"
40 #include "ags/engine/ac/global_hotspot.h"
41 #include "ags/engine/ac/global_inventory_item.h"
42 #include "ags/engine/ac/global_translation.h"
43 #include "ags/engine/ac/gui.h"
44 #include "ags/engine/ac/hotspot.h"
45 #include "ags/shared/ac/keycode.h"
46 #include "ags/engine/ac/mouse.h"
47 #include "ags/engine/ac/object.h"
48 #include "ags/engine/ac/path_helper.h"
49 #include "ags/engine/ac/sys_events.h"
50 #include "ags/engine/ac/room.h"
51 #include "ags/engine/ac/room_status.h"
52 #include "ags/engine/ac/string.h"
53 #include "ags/engine/ac/system.h"
54 #include "ags/engine/debugging/debugger.h"
55 #include "ags/engine/debugging/debug_log.h"
56 #include "ags/engine/gui/gui_dialog.h"
57 #include "ags/engine/main/engine.h"
58 #include "ags/engine/main/game_run.h"
59 #include "ags/engine/main/graphics_mode.h"
60 #include "ags/engine/main/game_start.h"
61 #include "ags/engine/script/script.h"
62 #include "ags/engine/script/script_runtime.h"
63 #include "ags/shared/ac/sprite_cache.h"
64 #include "ags/shared/gfx/bitmap.h"
65 #include "ags/engine/gfx/graphics_driver.h"
66 #include "ags/shared/core/asset_manager.h"
67 #include "ags/engine/main/config.h"
68 #include "ags/engine/main/game_file.h"
69 #include "ags/shared/util/path.h"
70 #include "ags/shared/util/string_utils.h"
71 #include "ags/engine/media/audio/audio_system.h"
72 #include "ags/engine/platform/base/sys_main.h"
73 #include "ags/ags.h"
74 #include "ags/globals.h"
75 
76 namespace AGS3 {
77 
78 using namespace AGS::Shared;
79 
GiveScore(int amnt)80 void GiveScore(int amnt) {
81 	GUI::MarkSpecialLabelsForUpdate(kLabelMacro_AllScore);
82 	_GP(play).score += amnt;
83 
84 	if ((amnt > 0) && (_GP(play).score_sound >= 0))
85 		play_audio_clip_by_index(_GP(play).score_sound);
86 
87 	run_on_event(GE_GOT_SCORE, RuntimeScriptValue().SetInt32(amnt));
88 }
89 
restart_game()90 void restart_game() {
91 	can_run_delayed_command();
92 	if (_G(inside_script)) {
93 		_G(curscript)->queue_action(ePSARestartGame, 0, "RestartGame");
94 		return;
95 	}
96 	try_restore_save(RESTART_POINT_SAVE_GAME_NUMBER);
97 }
98 
RestoreGameSlot(int slnum)99 void RestoreGameSlot(int slnum) {
100 	if (_G(displayed_room) < 0)
101 		quit("!RestoreGameSlot: a game cannot be restored from within game_start");
102 
103 	can_run_delayed_command();
104 	if (_G(inside_script)) {
105 		_G(curscript)->queue_action(ePSARestoreGame, slnum, "RestoreGameSlot");
106 		return;
107 	}
108 	try_restore_save(slnum);
109 }
110 
DeleteSaveSlot(int slnum)111 void DeleteSaveSlot(int slnum) {
112 	String nametouse;
113 	nametouse = get_save_game_path(slnum);
114 	Shared::File::DeleteFile(nametouse);
115 }
116 
PauseGame()117 void PauseGame() {
118 	_G(game_paused)++;
119 	debug_script_log("Game paused");
120 }
UnPauseGame()121 void UnPauseGame() {
122 	if (_G(game_paused) > 0)
123 		_G(game_paused)--;
124 	debug_script_log("Game UnPaused, pause level now %d", _G(game_paused));
125 }
126 
127 
IsGamePaused()128 int IsGamePaused() {
129 	if (_G(game_paused) > 0) return 1;
130 	return 0;
131 }
132 
GetSaveSlotDescription(int slnum,String & description)133 bool GetSaveSlotDescription(int slnum, String &description) {
134 	if (read_savedgame_description(get_save_game_path(slnum), description))
135 		return true;
136 	description.Format("INVALID SLOT %d", slnum);
137 	return false;
138 }
139 
GetSaveSlotDescription(int slnum,char * desbuf)140 int GetSaveSlotDescription(int slnum, char *desbuf) {
141 	VALIDATE_STRING(desbuf);
142 	String description;
143 	bool res = GetSaveSlotDescription(slnum, description);
144 	snprintf(desbuf, MAX_MAXSTRLEN, "%s", description.GetCStr());
145 	return res ? 1 : 0;
146 }
147 
LoadSaveSlotScreenshot(int slnum,int width,int height)148 int LoadSaveSlotScreenshot(int slnum, int width, int height) {
149 	int gotSlot;
150 	data_to_game_coords(&width, &height);
151 
152 	if (!read_savedgame_screenshot(get_save_game_path(slnum), gotSlot))
153 		return 0;
154 
155 	if (gotSlot == 0)
156 		return 0;
157 
158 	if ((_GP(game).SpriteInfos[gotSlot].Width == width) && (_GP(game).SpriteInfos[gotSlot].Height == height))
159 		return gotSlot;
160 
161 	// resize the sprite to the requested size
162 	Bitmap *newPic = BitmapHelper::CreateBitmap(width, height, _GP(spriteset)[gotSlot]->GetColorDepth());
163 	newPic->StretchBlt(_GP(spriteset)[gotSlot],
164 	                   RectWH(0, 0, _GP(game).SpriteInfos[gotSlot].Width, _GP(game).SpriteInfos[gotSlot].Height),
165 	                   RectWH(0, 0, width, height));
166 
167 	update_polled_stuff_if_runtime();
168 
169 	// replace the bitmap in the sprite set
170 	free_dynamic_sprite(gotSlot);
171 	add_dynamic_sprite(gotSlot, newPic);
172 
173 	return gotSlot;
174 }
175 
FillSaveList(std::vector<SaveListItem> & saves,size_t max_count)176 void FillSaveList(std::vector<SaveListItem> &saves, size_t max_count) {
177 	if (max_count == 0)
178 		return; // duh
179 
180 	String svg_dir = get_save_game_directory();
181 	String svg_suff = get_save_game_suffix();
182 	String searchPath = Path::ConcatPaths(svg_dir, String::FromFormat("agssave.???%s", svg_suff.GetCStr()));
183 	time_t time = 0;
184 
185 	SaveStateList saveList = ::AGS::g_vm->listSaves();
186 	for (uint idx = 0; idx < saveList.size(); ++idx) {
187 		int saveGameSlot = saveList[idx].getSaveSlot();
188 
189 		// only list games .000 to .099 (to allow higher slots for other perposes)
190 		if (saveGameSlot > 99)
191 			continue;
192 
193 		String description;
194 		GetSaveSlotDescription(saveGameSlot, description);
195 		saves.push_back(SaveListItem(saveGameSlot, description, time));
196 		if (saves.size() >= max_count)
197 			break;
198 	}
199 }
200 
SetGlobalInt(int index,int valu)201 void SetGlobalInt(int index, int valu) {
202 	if ((index < 0) | (index >= MAXGSVALUES))
203 		quit("!SetGlobalInt: invalid index");
204 
205 	if (_GP(play).globalscriptvars[index] != valu) {
206 		debug_script_log("GlobalInt %d set to %d", index, valu);
207 	}
208 
209 	_GP(play).globalscriptvars[index] = valu;
210 }
211 
212 
GetGlobalInt(int index)213 int GetGlobalInt(int index) {
214 	if ((index < 0) | (index >= MAXGSVALUES))
215 		quit("!GetGlobalInt: invalid index");
216 	return _GP(play).globalscriptvars[index];
217 }
218 
SetGlobalString(int index,const char * newval)219 void SetGlobalString(int index, const char *newval) {
220 	if ((index < 0) | (index >= MAXGLOBALSTRINGS))
221 		quit("!SetGlobalString: invalid index");
222 	debug_script_log("GlobalString %d set to '%s'", index, newval);
223 	strncpy(_GP(play).globalstrings[index], newval, MAX_MAXSTRLEN);
224 	// truncate it to 200 chars, to be sure
225 	_GP(play).globalstrings[index][MAX_MAXSTRLEN - 1] = 0;
226 }
227 
GetGlobalString(int index,char * strval)228 void GetGlobalString(int index, char *strval) {
229 	if ((index < 0) | (index >= MAXGLOBALSTRINGS))
230 		quit("!GetGlobalString: invalid index");
231 	strcpy(strval, _GP(play).globalstrings[index]);
232 }
233 
234 // TODO: refactor this method, and use same shared procedure at both normal stop/startup and in RunAGSGame
RunAGSGame(const String & newgame,unsigned int mode,int data)235 int RunAGSGame(const String &newgame, unsigned int mode, int data) {
236 
237 	can_run_delayed_command();
238 
239 	int AllowedModes = RAGMODE_PRESERVEGLOBALINT | RAGMODE_LOADNOW;
240 
241 	if ((mode & (~AllowedModes)) != 0)
242 		quit("!RunAGSGame: mode value unknown");
243 
244 	if (_G(editor_debugging_enabled)) {
245 		quit("!RunAGSGame cannot be used while running the game from within the AGS Editor. You must build the game EXE and run it from there to use this function.");
246 	}
247 
248 	if ((mode & RAGMODE_LOADNOW) == 0) {
249 		_GP(ResPaths).GamePak.Path = PathFromInstallDir(newgame);
250 		_GP(ResPaths).GamePak.Name = newgame;
251 		_GP(play).takeover_data = data;
252 		_G(load_new_game_restore) = -1;
253 
254 		if (_G(inside_script)) {
255 			_G(curscript)->queue_action(ePSARunAGSGame, mode | RAGMODE_LOADNOW, "RunAGSGame");
256 			ccInstance::GetCurrentInstance()->Abort();
257 		} else
258 			_G(load_new_game) = mode | RAGMODE_LOADNOW;
259 
260 		return 0;
261 	}
262 
263 	int ee;
264 
265 	unload_old_room();
266 	_G(displayed_room) = -10;
267 
268 #if defined (AGS_AUTO_WRITE_USER_CONFIG)
269 	save_config_file(); // save current user config in case engine fails to run new game
270 #endif // AGS_AUTO_WRITE_USER_CONFIG
271 	unload_game_file();
272 
273 	// Adjust config (NOTE: normally, RunAGSGame would need a redesign to allow separate config etc per each game)
274 	_GP(usetup).translation = ""; // reset to default, prevent from trying translation file of game A in game B
275 
276 	_GP(AssetMgr)->RemoveAllLibraries();
277 
278 	// TODO: refactor and share same code with the startup!
279 	if (_GP(AssetMgr)->AddLibrary(_GP(ResPaths).GamePak.Path) != Shared::kAssetNoError)
280 		quitprintf("!RunAGSGame: unable to load new game file '%s'", _GP(ResPaths).GamePak.Path.GetCStr());
281 	engine_assign_assetpaths();
282 
283 	show_preload();
284 
285 	HError err = load_game_file();
286 	if (!err)
287 		quitprintf("!RunAGSGame: error loading new game file:\n%s", err->FullMessage().GetCStr());
288 
289 	_GP(spriteset).Reset();
290 	err = _GP(spriteset).InitFile(SpriteFile::DefaultSpriteFileName, SpriteFile::DefaultSpriteIndexName);
291 	if (!err)
292 		quitprintf("!RunAGSGame: error loading new sprites:\n%s", err->FullMessage().GetCStr());
293 
294 	if ((mode & RAGMODE_PRESERVEGLOBALINT) == 0) {
295 		// reset GlobalInts
296 		for (ee = 0; ee < MAXGSVALUES; ee++)
297 			_GP(play).globalscriptvars[ee] = 0;
298 	}
299 
300 	engine_init_game_settings();
301 	_GP(play).screen_is_faded_out = 1;
302 
303 	if (_G(load_new_game_restore) >= 0) {
304 		try_restore_save(_G(load_new_game_restore));
305 		_G(load_new_game_restore) = -1;
306 	} else
307 		start_game();
308 
309 	return 0;
310 }
311 
GetGameParameter(int parm,int data1,int data2,int data3)312 int GetGameParameter(int parm, int data1, int data2, int data3) {
313 	switch (parm) {
314 	case GP_SPRITEWIDTH:
315 		return Game_GetSpriteWidth(data1);
316 	case GP_SPRITEHEIGHT:
317 		return Game_GetSpriteHeight(data1);
318 	case GP_NUMLOOPS:
319 		return Game_GetLoopCountForView(data1);
320 	case GP_NUMFRAMES:
321 		return Game_GetFrameCountForLoop(data1, data2);
322 	case GP_FRAMESPEED:
323 	case GP_FRAMEIMAGE:
324 	case GP_FRAMESOUND:
325 	case GP_ISFRAMEFLIPPED: {
326 		if ((data1 < 1) || (data1 > _GP(game).numviews)) {
327 			quitprintf("!GetGameParameter: invalid view specified (v: %d, l: %d, f: %d)", data1, data2, data3);
328 		}
329 		if ((data2 < 0) || (data2 >= _G(views)[data1 - 1].numLoops)) {
330 			quitprintf("!GetGameParameter: invalid loop specified (v: %d, l: %d, f: %d)", data1, data2, data3);
331 		}
332 		if ((data3 < 0) || (data3 >= _G(views)[data1 - 1].loops[data2].numFrames)) {
333 			quitprintf("!GetGameParameter: invalid frame specified (v: %d, l: %d, f: %d)", data1, data2, data3);
334 		}
335 
336 		ViewFrame *pvf = &_G(views)[data1 - 1].loops[data2].frames[data3];
337 
338 		if (parm == GP_FRAMESPEED)
339 			return pvf->speed;
340 		else if (parm == GP_FRAMEIMAGE)
341 			return pvf->pic;
342 		else if (parm == GP_FRAMESOUND)
343 			return get_old_style_number_for_sound(pvf->sound);
344 		else if (parm == GP_ISFRAMEFLIPPED)
345 			return (pvf->flags & VFLG_FLIPSPRITE) ? 1 : 0;
346 		else
347 			quit("GetGameParameter internal error");
348 		break;
349 	}
350 	case GP_ISRUNNEXTLOOP:
351 		return Game_GetRunNextSettingForLoop(data1, data2);
352 	case GP_NUMGUIS:
353 		return _GP(game).numgui;
354 	case GP_NUMOBJECTS:
355 		return _G(croom)->numobj;
356 	case GP_NUMCHARACTERS:
357 		return _GP(game).numcharacters;
358 	case GP_NUMINVITEMS:
359 		return _GP(game).numinvitems;
360 	default:
361 		quit("!GetGameParameter: unknown parameter specified");
362 	}
363 	return 0;
364 }
365 
QuitGame(int dialog)366 void QuitGame(int dialog) {
367 	if (dialog) {
368 		int rcode;
369 		setup_for_dialog();
370 		rcode = quitdialog();
371 		restore_after_dialog();
372 		if (rcode == 0) return;
373 	}
374 	quit("|You have exited.");
375 }
376 
377 
378 
379 
SetRestartPoint()380 void SetRestartPoint() {
381 	save_game(RESTART_POINT_SAVE_GAME_NUMBER, "Restart Game Auto-Save");
382 }
383 
384 
385 
SetGameSpeed(int newspd)386 void SetGameSpeed(int newspd) {
387 	newspd += _GP(play).game_speed_modifier;
388 	if (newspd > 1000) newspd = 1000;
389 	if (newspd < 10) newspd = 10;
390 	set_game_speed(newspd);
391 	debug_script_log("Game speed set to %d", newspd);
392 }
393 
GetGameSpeed()394 int GetGameSpeed() {
395 	return ::lround(get_current_fps()) - _GP(play).game_speed_modifier;
396 }
397 
SetGameOption(int opt,int setting)398 int SetGameOption(int opt, int setting) {
399 	if (((opt < 1) || (opt > OPT_HIGHESTOPTION)) && (opt != OPT_LIPSYNCTEXT))
400 		quit("!SetGameOption: invalid option specified");
401 
402 	if (opt == OPT_ANTIGLIDE) {
403 		for (int i = 0; i < _GP(game).numcharacters; i++) {
404 			if (setting)
405 				_GP(game).chars[i].flags |= CHF_ANTIGLIDE;
406 			else
407 				_GP(game).chars[i].flags &= ~CHF_ANTIGLIDE;
408 		}
409 	}
410 
411 	if ((opt == OPT_CROSSFADEMUSIC) && (_GP(game).audioClipTypes.size() > AUDIOTYPE_LEGACY_MUSIC)) {
412 		// legacy compatibility -- changing crossfade speed here also
413 		// updates the new audio clip type style
414 		_GP(game).audioClipTypes[AUDIOTYPE_LEGACY_MUSIC].crossfadeSpeed = setting;
415 	}
416 
417 	int oldval = _GP(game).options[opt];
418 	_GP(game).options[opt] = setting;
419 
420 	if (opt == OPT_DUPLICATEINV)
421 		update_invorder();
422 	else if (opt == OPT_DISABLEOFF) {
423 		_G(gui_disabled_style) = convert_gui_disabled_style(_GP(game).options[OPT_DISABLEOFF]);
424 		// If GUI was disabled at this time then also update it, as visual style could've changed
425 		if (_GP(play).disabled_user_interface > 0) {
426 			GUI::MarkAllGUIForUpdate();
427 		}
428 	} else if (opt == OPT_PORTRAITSIDE) {
429 		if (setting == 0)  // set back to Left
430 			_GP(play).swap_portrait_side = 0;
431 	}
432 
433 	return oldval;
434 }
435 
GetGameOption(int opt)436 int GetGameOption(int opt) {
437 	if (((opt < 1) || (opt > OPT_HIGHESTOPTION)) && (opt != OPT_LIPSYNCTEXT))
438 		quit("!GetGameOption: invalid option specified");
439 
440 	return _GP(game).options[opt];
441 }
442 
SkipUntilCharacterStops(int cc)443 void SkipUntilCharacterStops(int cc) {
444 	if (!is_valid_character(cc))
445 		quit("!SkipUntilCharacterStops: invalid character specified");
446 	if (_GP(game).chars[cc].room != _G(displayed_room))
447 		quit("!SkipUntilCharacterStops: specified character not in current room");
448 
449 	// if they are not currently moving, do nothing
450 	if (!_GP(game).chars[cc].walking)
451 		return;
452 
453 	if (is_in_cutscene())
454 		quit("!SkipUntilCharacterStops: cannot be used within a cutscene");
455 
456 	initialize_skippable_cutscene();
457 	_GP(play).fast_forward = 2;
458 	_GP(play).skip_until_char_stops = cc;
459 }
460 
EndSkippingUntilCharStops()461 void EndSkippingUntilCharStops() {
462 	// not currently skipping, so ignore
463 	if (_GP(play).skip_until_char_stops < 0)
464 		return;
465 
466 	stop_fast_forwarding();
467 	_GP(play).skip_until_char_stops = -1;
468 }
469 
StartCutscene(int skipwith)470 void StartCutscene(int skipwith) {
471 	static ScriptPosition last_cutscene_script_pos;
472 
473 	if (is_in_cutscene()) {
474 		quitprintf("!StartCutscene: already in a cutscene; previous started in \"%s\", line %d",
475 		           last_cutscene_script_pos.Section.GetCStr(), last_cutscene_script_pos.Line);
476 	}
477 
478 	if ((skipwith < 1) || (skipwith > 6))
479 		quit("!StartCutscene: invalid argument, must be 1 to 5.");
480 
481 	get_script_position(last_cutscene_script_pos);
482 
483 	// make sure they can't be skipping and cutsceneing at the same time
484 	EndSkippingUntilCharStops();
485 
486 	_GP(play).in_cutscene = skipwith;
487 	initialize_skippable_cutscene();
488 }
489 
SkipCutscene()490 void SkipCutscene() {
491 	if (is_in_cutscene())
492 		start_skipping_cutscene();
493 }
494 
EndCutscene()495 int EndCutscene() {
496 	if (!is_in_cutscene())
497 		quit("!EndCutscene: not in a cutscene");
498 
499 	int retval = _GP(play).fast_forward;
500 	_GP(play).in_cutscene = 0;
501 	// Stop it fast-forwarding
502 	stop_fast_forwarding();
503 
504 	// make sure that the screen redraws
505 	invalidate_screen();
506 
507 	// Return whether the player skipped it
508 	return retval;
509 }
510 
sc_inputbox(const char * msg,char * bufr)511 void sc_inputbox(const char *msg, char *bufr) {
512 	VALIDATE_STRING(bufr);
513 	setup_for_dialog();
514 	enterstringwindow(get_translation(msg), bufr);
515 	restore_after_dialog();
516 }
517 
518 // GetLocationType exported function - just call through
519 // to the main function with default 0
GetLocationType(int xxx,int yyy)520 int GetLocationType(int xxx, int yyy) {
521 	return __GetLocationType(xxx, yyy, 0);
522 }
523 
SaveCursorForLocationChange()524 void SaveCursorForLocationChange() {
525 	// update the current location name
526 	char tempo[100];
527 	GetLocationName(game_to_data_coord(_G(mousex)), game_to_data_coord(_G(mousey)), tempo);
528 
529 	if (_GP(play).get_loc_name_save_cursor != _GP(play).get_loc_name_last_time) {
530 		_GP(play).get_loc_name_save_cursor = _GP(play).get_loc_name_last_time;
531 		_GP(play).restore_cursor_mode_to = GetCursorMode();
532 		_GP(play).restore_cursor_image_to = GetMouseCursor();
533 		debug_script_log("Saving mouse: mode %d cursor %d", _GP(play).restore_cursor_mode_to, _GP(play).restore_cursor_image_to);
534 	}
535 }
536 
GetLocationName(int xxx,int yyy,char * tempo)537 void GetLocationName(int xxx, int yyy, char *tempo) {
538 	if (_G(displayed_room) < 0)
539 		quit("!GetLocationName: no room has been loaded");
540 
541 	VALIDATE_STRING(tempo);
542 
543 	tempo[0] = 0;
544 
545 	if (GetGUIAt(xxx, yyy) >= 0) {
546 		int mover = GetInvAt(xxx, yyy);
547 		if (mover > 0) {
548 			if (_GP(play).get_loc_name_last_time != 1000 + mover)
549 				GUI::MarkSpecialLabelsForUpdate(kLabelMacro_Overhotspot);
550 			_GP(play).get_loc_name_last_time = 1000 + mover;
551 			strcpy(tempo, get_translation(_GP(game).invinfo[mover].name));
552 		} else if ((_GP(play).get_loc_name_last_time > 1000) && (_GP(play).get_loc_name_last_time < 1000 + MAX_INV)) {
553 			// no longer selecting an item
554 			GUI::MarkSpecialLabelsForUpdate(kLabelMacro_Overhotspot);
555 			_GP(play).get_loc_name_last_time = -1;
556 		}
557 		return;
558 	}
559 
560 	int loctype = GetLocationType(xxx, yyy); // GetLocationType takes screen coords
561 	VpPoint vpt = _GP(play).ScreenToRoomDivDown(xxx, yyy);
562 	if (vpt.second < 0)
563 		return;
564 	xxx = vpt.first.X;
565 	yyy = vpt.first.Y;
566 	if ((xxx >= _GP(thisroom).Width) | (xxx < 0) | (yyy < 0) | (yyy >= _GP(thisroom).Height))
567 		return;
568 
569 	int onhs, aa;
570 	if (loctype == 0) {
571 		if (_GP(play).get_loc_name_last_time != 0) {
572 			_GP(play).get_loc_name_last_time = 0;
573 			GUI::MarkSpecialLabelsForUpdate(kLabelMacro_Overhotspot);
574 		}
575 		return;
576 	}
577 
578 	// on character
579 	if (loctype == LOCTYPE_CHAR) {
580 		onhs = _G(getloctype_index);
581 		strcpy(tempo, get_translation(_GP(game).chars[onhs].name));
582 		if (_GP(play).get_loc_name_last_time != 2000 + onhs)
583 			GUI::MarkSpecialLabelsForUpdate(kLabelMacro_Overhotspot);
584 		_GP(play).get_loc_name_last_time = 2000 + onhs;
585 		return;
586 	}
587 	// on object
588 	if (loctype == LOCTYPE_OBJ) {
589 		aa = _G(getloctype_index);
590 		strcpy(tempo, get_translation(_GP(thisroom).Objects[aa].Name.GetCStr()));
591 		// Compatibility: < 3.1.1 games returned space for nameless object
592 		// (presumably was a bug, but fixing it affected certain games behavior)
593 		if (_G(loaded_game_file_version) < kGameVersion_311 && tempo[0] == 0) {
594 			tempo[0] = ' ';
595 			tempo[1] = 0;
596 		}
597 		if (_GP(play).get_loc_name_last_time != 3000 + aa)
598 			GUI::MarkSpecialLabelsForUpdate(kLabelMacro_Overhotspot);
599 		_GP(play).get_loc_name_last_time = 3000 + aa;
600 		return;
601 	}
602 	onhs = _G(getloctype_index);
603 	if (onhs > 0) strcpy(tempo, get_translation(_GP(thisroom).Hotspots[onhs].Name.GetCStr()));
604 	if (_GP(play).get_loc_name_last_time != onhs)
605 		GUI::MarkSpecialLabelsForUpdate(kLabelMacro_Overhotspot);
606 	_GP(play).get_loc_name_last_time = onhs;
607 }
608 
IsKeyPressed(int keycode)609 int IsKeyPressed(int keycode) {
610 	auto status = ags_iskeydown(static_cast<eAGSKeyCode>(keycode));
611 	if (status < 0) {
612 		debug_script_log("IsKeyPressed: unsupported keycode %d", keycode);
613 		return 0;
614 	}
615 	return status;
616 }
617 
SaveScreenShot(const char * namm)618 int SaveScreenShot(const char *namm) {
619 	String fileName;
620 	String svg_dir = get_save_game_directory();
621 
622 	if (strchr(namm, '.') == nullptr)
623 		fileName = Path::MakePath(svg_dir, namm, "bmp");
624 	else
625 		fileName = Path::ConcatPaths(svg_dir, namm);
626 
627 	Bitmap *buffer = CopyScreenIntoBitmap(_GP(play).GetMainViewport().GetWidth(), _GP(play).GetMainViewport().GetHeight());
628 	if (!buffer->SaveToFile(fileName, _G(palette)) != 0) {
629 		delete buffer;
630 		return 0;
631 	}
632 	delete buffer;
633 	return 1;  // successful
634 }
635 
SetMultitasking(int mode)636 void SetMultitasking(int mode) {
637 	if ((mode < 0) | (mode > 1))
638 		quit("!SetMultitasking: invalid mode parameter");
639 
640 	if (_GP(usetup).override_multitasking >= 0) {
641 		mode = _GP(usetup).override_multitasking;
642 	}
643 
644 	// Don't allow background running if full screen
645 	if ((mode == 1) && (!_GP(scsystem).windowed))
646 		mode = 0;
647 
648 	// Install engine callbacks for switching in and out the window
649 	if (mode == 0) {
650 		sys_set_background_mode(false);
651 		sys_evt_set_focus_callbacks(display_switch_in_resume, display_switch_out_suspend);
652 	} else {
653 		sys_set_background_mode(true);
654 		sys_evt_set_focus_callbacks(display_switch_in, display_switch_out);
655 	}
656 }
657 
RoomProcessClick(int xx,int yy,int mood)658 void RoomProcessClick(int xx, int yy, int mood) {
659 	_G(getloctype_throughgui) = 1;
660 	int loctype = GetLocationType(xx, yy);
661 	VpPoint vpt = _GP(play).ScreenToRoomDivDown(xx, yy);
662 	if (vpt.second < 0)
663 		return;
664 	xx = vpt.first.X;
665 	yy = vpt.first.Y;
666 
667 	if ((mood == MODE_WALK) && (_GP(game).options[OPT_NOWALKMODE] == 0)) {
668 		int hsnum = get_hotspot_at(xx, yy);
669 		if (hsnum < 1);
670 		else if (_GP(thisroom).Hotspots[hsnum].WalkTo.X < 1);
671 		else if (_GP(play).auto_use_walkto_points == 0);
672 		else {
673 			xx = _GP(thisroom).Hotspots[hsnum].WalkTo.X;
674 			yy = _GP(thisroom).Hotspots[hsnum].WalkTo.Y;
675 			debug_script_log("Move to walk-to point hotspot %d", hsnum);
676 		}
677 		walk_character(_GP(game).playercharacter, xx, yy, 0, true);
678 		return;
679 	}
680 	_GP(play).usedmode = mood;
681 
682 	if (loctype == 0) {
683 		// click on nothing -> hotspot 0
684 		_G(getloctype_index) = 0;
685 		loctype = LOCTYPE_HOTSPOT;
686 	}
687 
688 	if (loctype == LOCTYPE_CHAR) {
689 		if (check_click_on_character(xx, yy, mood)) return;
690 	} else if (loctype == LOCTYPE_OBJ) {
691 		if (check_click_on_object(xx, yy, mood)) return;
692 	} else if (loctype == LOCTYPE_HOTSPOT)
693 		RunHotspotInteraction(_G(getloctype_index), mood);
694 }
695 
IsInteractionAvailable(int xx,int yy,int mood)696 int IsInteractionAvailable(int xx, int yy, int mood) {
697 	_G(getloctype_throughgui) = 1;
698 	int loctype = GetLocationType(xx, yy);
699 	VpPoint vpt = _GP(play).ScreenToRoomDivDown(xx, yy);
700 	if (vpt.second < 0)
701 		return 0;
702 	xx = vpt.first.X;
703 	yy = vpt.first.Y;
704 
705 	// You can always walk places
706 	if ((mood == MODE_WALK) && (_GP(game).options[OPT_NOWALKMODE] == 0))
707 		return 1;
708 
709 	_GP(play).check_interaction_only = 1;
710 
711 	if (loctype == 0) {
712 		// click on nothing -> hotspot 0
713 		_G(getloctype_index) = 0;
714 		loctype = LOCTYPE_HOTSPOT;
715 	}
716 
717 	if (loctype == LOCTYPE_CHAR) {
718 		check_click_on_character(xx, yy, mood);
719 	} else if (loctype == LOCTYPE_OBJ) {
720 		check_click_on_object(xx, yy, mood);
721 	} else if (loctype == LOCTYPE_HOTSPOT)
722 		RunHotspotInteraction(_G(getloctype_index), mood);
723 
724 	int ciwas = _GP(play).check_interaction_only;
725 	_GP(play).check_interaction_only = 0;
726 
727 	if (ciwas == 2)
728 		return 1;
729 
730 	return 0;
731 }
732 
GetMessageText(int msg,char * buffer)733 void GetMessageText(int msg, char *buffer) {
734 	VALIDATE_STRING(buffer);
735 	get_message_text(msg, buffer, 0);
736 }
737 
SetSpeechFont(int fontnum)738 void SetSpeechFont(int fontnum) {
739 	if ((fontnum < 0) || (fontnum >= _GP(game).numfonts))
740 		quit("!SetSpeechFont: invalid font number.");
741 	_GP(play).speech_font = fontnum;
742 }
743 
SetNormalFont(int fontnum)744 void SetNormalFont(int fontnum) {
745 	if ((fontnum < 0) || (fontnum >= _GP(game).numfonts))
746 		quit("!SetNormalFont: invalid font number.");
747 	_GP(play).normal_font = fontnum;
748 }
749 
_sc_AbortGame(const char * text)750 void _sc_AbortGame(const char *text) {
751 	char displbuf[STD_BUFFER_SIZE] = "!?";
752 	snprintf(&displbuf[2], STD_BUFFER_SIZE - 3, "%s", text);
753 	quit(displbuf);
754 }
755 
GetGraphicalVariable(const char * varName)756 int GetGraphicalVariable(const char *varName) {
757 	InteractionVariable *theVar = FindGraphicalVariable(varName);
758 	if (theVar == nullptr) {
759 		quitprintf("!GetGraphicalVariable: interaction variable '%s' not found", varName);
760 		return 0;
761 	}
762 	return theVar->Value;
763 }
764 
SetGraphicalVariable(const char * varName,int p_value)765 void SetGraphicalVariable(const char *varName, int p_value) {
766 	InteractionVariable *theVar = FindGraphicalVariable(varName);
767 	if (theVar == nullptr) {
768 		quitprintf("!SetGraphicalVariable: interaction variable '%s' not found", varName);
769 	} else
770 		theVar->Value = p_value;
771 }
772 
WaitImpl(int skip_type,int nloops)773 int WaitImpl(int skip_type, int nloops) {
774 	_GP(play).wait_counter = nloops;
775 	_GP(play).wait_skipped_by = SKIP_NONE;
776 	_GP(play).wait_skipped_by = SKIP_AUTOTIMER; // we set timer flag by default to simplify that case
777 	_GP(play).wait_skipped_by_data = 0;
778 	_GP(play).key_skip_wait = skip_type;
779 
780     GameLoopUntilValueIsZero(&_GP(play).wait_counter);
781 
782 	if (_GP(game).options[OPT_BASESCRIPTAPI] < kScriptAPI_v360) {
783 		// < 3.6.0 return 1 is skipped by user input, otherwise 0
784 		return (_GP(play).wait_skipped_by & (SKIP_KEYPRESS | SKIP_MOUSECLICK)) != 0 ? 1 : 0;
785 	}
786 	// >= 3.6.0 return positive keycode, negative mouse button code, or 0 as time-out
787 	return _GP(play).GetWaitSkipResult();
788 }
789 
scrWait(int nloops)790 void scrWait(int nloops) {
791 	WaitImpl(SKIP_AUTOTIMER, nloops);
792 }
793 
WaitKey(int nloops)794 int WaitKey(int nloops) {
795 	return WaitImpl(SKIP_KEYPRESS | SKIP_AUTOTIMER, nloops);
796 }
797 
WaitMouse(int nloops)798 int WaitMouse(int nloops) {
799 	return WaitImpl(SKIP_MOUSECLICK | SKIP_AUTOTIMER, nloops);
800 }
801 
WaitMouseKey(int nloops)802 int WaitMouseKey(int nloops) {
803 	return WaitImpl(SKIP_KEYPRESS | SKIP_MOUSECLICK | SKIP_AUTOTIMER, nloops);
804 }
805 
SkipWait()806 void SkipWait() {
807 	_GP(play).wait_counter = 0;
808 }
809 
810 } // namespace AGS3
811