1 //=============================================================================
2 //
3 // Adventure Game Studio (AGS)
4 //
5 // Copyright (C) 1999-2011 Chris Jones and 2011-20xx others
6 // The full list of copyright holders can be found in the Copyright.txt
7 // file, which is part of this source code distribution.
8 //
9 // The AGS source code is provided under the Artistic License 2.0.
10 // A copy of this license can be found in the file License.txt and at
11 // http://www.opensource.org/licenses/artistic-license-2.0.php
12 //
13 //=============================================================================
14
15 //
16 // Game loop
17 //
18
19 #include "ac/common.h"
20 #include "ac/characterextras.h"
21 #include "ac/characterinfo.h"
22 #include "ac/draw.h"
23 #include "ac/event.h"
24 #include "ac/game.h"
25 #include "ac/gamesetup.h"
26 #include "ac/gamesetupstruct.h"
27 #include "ac/global_debug.h"
28 #include "ac/global_display.h"
29 #include "ac/global_game.h"
30 #include "ac/global_gui.h"
31 #include "ac/global_region.h"
32 #include "ac/gui.h"
33 #include "ac/hotspot.h"
34 #include "ac/keycode.h"
35 #include "ac/mouse.h"
36 #include "ac/overlay.h"
37 #include "ac/record.h"
38 #include "ac/room.h"
39 #include "ac/roomobject.h"
40 #include "ac/roomstatus.h"
41 #include "ac/roomstruct.h"
42 #include "debug/debugger.h"
43 #include "debug/debug_log.h"
44 #include "gui/guiinv.h"
45 #include "gui/guimain.h"
46 #include "gui/guitextbox.h"
47 #include "main/mainheader.h"
48 #include "main/engine.h"
49 #include "main/game_run.h"
50 #include "main/update.h"
51 #include "media/audio/soundclip.h"
52 #include "plugin/agsplugin.h"
53 #include "plugin/plugin_engine.h"
54 #include "script/script.h"
55 #include "ac/spritecache.h"
56
57 using namespace AGS::Common;
58
59 extern AnimatingGUIButton animbuts[MAX_ANIMATING_BUTTONS];
60 extern int numAnimButs;
61 extern int mouse_on_iface; // mouse cursor is over this interface
62 extern int ifacepopped;
63 extern int is_text_overlay;
64 extern volatile char want_exit, abort_engine;
65 extern int proper_exit,our_eip;
66 extern int displayed_room, starting_room, in_new_room, new_room_was;
67 extern GameSetupStruct game;
68 extern roomstruct thisroom;
69 extern int game_paused;
70 extern int getloctype_index;
71 extern int in_enters_screen,done_es_error;
72 extern int in_leaves_screen;
73 extern int inside_script,in_graph_script;
74 extern int no_blocking_functions;
75 extern CharacterInfo*playerchar;
76 extern GameState play;
77 extern int mouse_ifacebut_xoffs,mouse_ifacebut_yoffs;
78 extern int cur_mode;
79 extern RoomObject*objs;
80 extern int replay_start_this_time;
81 extern char noWalkBehindsAtAll;
82 extern RoomStatus*croom;
83 extern CharacterExtras *charextra;
84 extern int spritewidth[MAX_SPRITES],spriteheight[MAX_SPRITES];
85 extern SpriteCache spriteset;
86 extern int offsetx, offsety;
87 extern unsigned int loopcounter,lastcounter;
88 extern volatile int timerloop;
89 extern int cur_mode,cur_cursor;
90
91 // Checks if user interface should remain disabled for now
92 int ShouldStayInWaitMode();
93
94 int numEventsAtStartOfFunction;
95 long t1; // timer for FPS // ... 't1'... how very appropriate.. :)
96
97 long user_disabled_for=0,user_disabled_data=0,user_disabled_data2=0;
98 int user_disabled_data3=0;
99
100 int restrict_until=0;
101
ProperExit()102 void ProperExit()
103 {
104 want_exit = 0;
105 proper_exit = 1;
106 quit("||exit!");
107 }
108
game_loop_check_problems_at_start()109 void game_loop_check_problems_at_start()
110 {
111 if ((in_enters_screen != 0) & (displayed_room == starting_room))
112 quit("!A text script run in the Player Enters Screen event caused the\n"
113 "screen to be updated. If you need to use Wait(), do so in After Fadein");
114 if ((in_enters_screen != 0) && (done_es_error == 0)) {
115 debug_script_warn("Wait() was used in Player Enters Screen - use Enters Screen After Fadein instead");
116 done_es_error = 1;
117 }
118 if (no_blocking_functions)
119 quit("!A blocking function was called from within a non-blocking event such as " REP_EXEC_ALWAYS_NAME);
120 }
121
game_loop_check_new_room()122 void game_loop_check_new_room()
123 {
124 if (in_new_room == 0) {
125 // Run the room and game script repeatedly_execute
126 run_function_on_non_blocking_thread(&repExecAlways);
127 setevent(EV_TEXTSCRIPT,TS_REPEAT);
128 setevent(EV_RUNEVBLOCK,EVB_ROOM,0,6);
129 }
130 // run this immediately to make sure it gets done before fade-in
131 // (player enters screen)
132 check_new_room ();
133 }
134
game_loop_do_late_update()135 void game_loop_do_late_update()
136 {
137 if (in_new_room == 0)
138 {
139 // Run the room and game script late_repeatedly_execute
140 run_function_on_non_blocking_thread(&lateRepExecAlways);
141 }
142 }
143
game_loop_check_ground_level_interactions()144 int game_loop_check_ground_level_interactions()
145 {
146 if ((play.ground_level_areas_disabled & GLED_INTERACTION) == 0) {
147 // check if he's standing on a hotspot
148 int hotspotThere = get_hotspot_at(playerchar->x, playerchar->y);
149 // run Stands on Hotspot event
150 setevent(EV_RUNEVBLOCK, EVB_HOTSPOT, hotspotThere, 0);
151
152 // check current region
153 int onRegion = GetRegionAt (playerchar->x, playerchar->y);
154 int inRoom = displayed_room;
155
156 if (onRegion != play.player_on_region) {
157 // we need to save this and set play.player_on_region
158 // now, so it's correct going into RunRegionInteraction
159 int oldRegion = play.player_on_region;
160
161 play.player_on_region = onRegion;
162 // Walks Off last region
163 if (oldRegion > 0)
164 RunRegionInteraction (oldRegion, 2);
165 // Walks Onto new region
166 if (onRegion > 0)
167 RunRegionInteraction (onRegion, 1);
168 }
169 if (play.player_on_region > 0) // player stands on region
170 RunRegionInteraction (play.player_on_region, 0);
171
172 // one of the region interactions sent us to another room
173 if (inRoom != displayed_room) {
174 check_new_room();
175 }
176
177 // if in a Wait loop which is no longer valid (probably
178 // because the Region interaction did a NewRoom), abort
179 // the rest of the loop
180 if ((restrict_until) && (!ShouldStayInWaitMode())) {
181 // cancel the Rep Exec and Stands on Hotspot events that
182 // we just added -- otherwise the event queue gets huge
183 numevents = numEventsAtStartOfFunction;
184 return 0;
185 }
186 } // end if checking ground level interactions
187
188 return RETURN_CONTINUE;
189 }
190
lock_mouse_on_click()191 void lock_mouse_on_click()
192 {
193 if (usetup.mouse_auto_lock && scsystem.windowed)
194 Mouse::TryLockToWindow();
195 }
196
toggle_mouse_lock()197 void toggle_mouse_lock()
198 {
199 if (scsystem.windowed)
200 {
201 if (Mouse::IsLockedToWindow())
202 Mouse::UnlockFromWindow();
203 else
204 Mouse::TryLockToWindow();
205 }
206 }
207
208 // Runs default mouse button handling
check_mouse_controls()209 void check_mouse_controls()
210 {
211 int mongu=-1;
212
213 mongu = gui_on_mouse_move();
214
215 mouse_on_iface=mongu;
216 if ((ifacepopped>=0) && (mousey>=guis[ifacepopped].Y+guis[ifacepopped].Height))
217 remove_popup_interface(ifacepopped);
218
219 // check mouse clicks on GUIs
220 static int wasbutdown=0,wasongui=0;
221
222 if ((wasbutdown>0) && (misbuttondown(wasbutdown-1))) {
223 gui_on_mouse_hold(wasongui, wasbutdown);
224 }
225 else if ((wasbutdown>0) && (!misbuttondown(wasbutdown-1))) {
226 gui_on_mouse_up(wasongui, wasbutdown);
227 wasbutdown=0;
228 }
229
230 int mbut =mgetbutton();
231 if (mbut>NONE) {
232 lock_mouse_on_click();
233
234 if ((play.in_cutscene == 3) || (play.in_cutscene == 4))
235 start_skipping_cutscene();
236 if ((play.in_cutscene == 5) && (mbut == RIGHT))
237 start_skipping_cutscene();
238
239 if (play.fast_forward) { }
240 else if ((play.wait_counter > 0) && (play.key_skip_wait > 1))
241 play.wait_counter = -1;
242 else if (is_text_overlay > 0) {
243 if (play.cant_skip_speech & SKIP_MOUSECLICK)
244 remove_screen_overlay(OVER_TEXTMSG);
245 }
246 else if (!IsInterfaceEnabled()) ; // blocking cutscene, ignore mouse
247 else if (pl_run_plugin_hooks(AGSE_MOUSECLICK, mbut+1)) {
248 // plugin took the click
249 debug_script_log("Plugin handled mouse button %d", mbut+1);
250 }
251 else if (mongu>=0) {
252 if (wasbutdown==0) {
253 gui_on_mouse_down(mongu, mbut+1);
254 }
255 wasongui=mongu;
256 wasbutdown=mbut+1;
257 }
258 else setevent(EV_TEXTSCRIPT,TS_MCLICK,mbut+1);
259 // else RunTextScriptIParam(gameinst,"on_mouse_click",aa+1);
260 }
261 mbut = check_mouse_wheel();
262 if (mbut !=0)
263 lock_mouse_on_click();
264 if (mbut < 0)
265 setevent (EV_TEXTSCRIPT, TS_MCLICK, 9);
266 else if (mbut > 0)
267 setevent (EV_TEXTSCRIPT, TS_MCLICK, 8);
268 }
269
270 // Returns current key modifiers;
271 // NOTE: annoyingly enough, on Windows (not sure about other platforms)
272 // Allegro API's 'key_shifts' variable seem to be always one step behind real
273 // situation: if first modifier gets pressed, 'key_shifts' will be zero,
274 // when second modifier gets pressed it will only contain first one, and so on.
get_active_shifts()275 int get_active_shifts()
276 {
277 int shifts = 0;
278 if (key[KEY_LSHIFT] || key[KEY_RSHIFT])
279 shifts |= KB_SHIFT_FLAG;
280 if (key[KEY_LCONTROL] || key[KEY_RCONTROL])
281 shifts |= KB_CTRL_FLAG;
282 if (key[KEY_ALT] || key[KEY_ALTGR])
283 shifts |= KB_ALT_FLAG;
284 return shifts;
285 }
286
287 // Special flags to OR saved shift flags with:
288 // Shifts key combination already fired (wait until full shifts release)
289 #define KEY_SHIFTS_FIRED 0x80000000
290
291 // Runs service key controls, returns false if service key combinations were handled
292 // and no more processing required, otherwise returns true and provides current keycode and key shifts.
run_service_key_controls(int & kgn)293 bool run_service_key_controls(int &kgn)
294 {
295 // check keypresses
296 static int old_key_shifts = 0; // for saving shift modes
297
298 bool handled = false;
299 int kbhit_res = kbhit();
300 // First, check shifts
301 const int act_shifts = get_active_shifts();
302 // If shifts combination have already triggered an action, then do nothing
303 // until new shifts are empty, in which case reset saved shifts
304 if (old_key_shifts & KEY_SHIFTS_FIRED)
305 {
306 if (act_shifts == 0)
307 old_key_shifts = 0;
308 }
309 else
310 {
311 // If any non-shift key is pressed, add fired flag to indicate that
312 // this is no longer a pure shifts key combination
313 if (kbhit_res)
314 {
315 old_key_shifts = act_shifts | KEY_SHIFTS_FIRED;
316 }
317 // If all the previously registered shifts are still pressed,
318 // then simply resave new shift state.
319 else if ((old_key_shifts & act_shifts) == old_key_shifts)
320 {
321 old_key_shifts = act_shifts;
322 }
323 // Otherwise some of the shifts were released, then run key combo action
324 // and set KEY_COMBO_FIRED flag to prevent multiple execution
325 else if (old_key_shifts)
326 {
327 // Toggle mouse lock on Ctrl + Alt
328 if (old_key_shifts == (KB_ALT_FLAG | KB_CTRL_FLAG))
329 {
330 toggle_mouse_lock();
331 handled = true;
332 }
333 old_key_shifts |= KEY_SHIFTS_FIRED;
334 }
335 }
336
337 if (!kbhit_res || handled)
338 return false;
339
340 int keycode = getch();
341 if (keycode == 0)
342 keycode = getch() + AGS_EXT_KEY_SHIFT;
343
344 // LAlt or RAlt + Enter
345 // NOTE: for some reason LAlt + Enter produces same code as F9
346 if (act_shifts == KB_ALT_FLAG && ((keycode == 367 && !key[KEY_F9]) || keycode == 13))
347 {
348 engine_try_switch_windowed_gfxmode();
349 return false;
350 }
351
352 // No service operation triggered? return active keypress and shifts to caller
353 kgn = keycode;
354 return true;
355 }
356
357 // Runs default keyboard handling
check_keyboard_controls()358 void check_keyboard_controls()
359 {
360 int kgn;
361 // First check for service engine's combinations (mouse lock, display mode switch, and so forth)
362 if (!run_service_key_controls(kgn))
363 return;
364 // Now check for in-game controls
365 {
366 // in case they press the finish-recording button, make sure we know
367 int was_playing = play.playback;
368
369 // if (kgn==367) restart_game();
370 // if (kgn==2) Display("numover: %d character movesped: %d, animspd: %d",numscreenover,playerchar->walkspeed,playerchar->animspeed);
371 // if (kgn==2) CreateTextOverlay(50,60,170,FONT_SPEECH,14,"This is a test screen overlay which shouldn't disappear");
372 // if (kgn==2) { Display("Crashing"); strcpy(NULL, NULL); }
373 // if (kgn == 2) FaceLocation (game.playercharacter, playerchar->x + 1, playerchar->y);
374 //if (kgn == 2) SetCharacterIdle (game.playercharacter, 5, 0);
375 //if (kgn == 2) Display("Some for?ign text");
376 //if (kgn == 2) do_conversation(5);
377
378 if (kgn == play.replay_hotkey) {
379 // start/stop recording
380 if (play.recording)
381 stop_recording();
382 else if ((play.playback) || (was_playing))
383 ; // do nothing (we got the replay of the stop key)
384 else
385 replay_start_this_time = 1;
386 }
387
388 check_skip_cutscene_keypress (kgn);
389
390 if (play.fast_forward) { }
391 else if (pl_run_plugin_hooks(AGSE_KEYPRESS, kgn)) {
392 // plugin took the keypress
393 debug_script_log("Keypress code %d taken by plugin", kgn);
394 }
395 else if ((kgn == '`') && (play.debug_mode > 0)) {
396 // debug console
397 display_console = !display_console;
398 }
399 else if ((is_text_overlay > 0) &&
400 (play.cant_skip_speech & SKIP_KEYPRESS) &&
401 (kgn != 434)) {
402 // 434 = F12, allow through for screenshot of text
403 // (though atm with one script at a time that won't work)
404 // only allow a key to remove the overlay if the icon bar isn't up
405 if (IsGamePaused() == 0) {
406 // check if it requires a specific keypress
407 if ((play.skip_speech_specific_key > 0) &&
408 (kgn != play.skip_speech_specific_key)) { }
409 else
410 remove_screen_overlay(OVER_TEXTMSG);
411 }
412 }
413 else if ((play.wait_counter > 0) && (play.key_skip_wait > 0)) {
414 play.wait_counter = -1;
415 debug_script_log("Keypress code %d ignored - in Wait", kgn);
416 }
417 else if ((kgn == 5) && (display_fps == 2)) {
418 // if --fps paramter is used, Ctrl+E will max out frame rate
419 SetGameSpeed(1000);
420 display_fps = 2;
421 }
422 else if ((kgn == 4) && (play.debug_mode > 0)) {
423 // ctrl+D - show info
424 char infobuf[900];
425 int ff;
426 // MACPORT FIX 9/6/5: added last %s
427 sprintf(infobuf,"In room %d %s[Player at %d, %d (view %d, loop %d, frame %d)%s%s%s",
428 displayed_room, (noWalkBehindsAtAll ? "(has no walk-behinds)" : ""), playerchar->x,playerchar->y,
429 playerchar->view + 1, playerchar->loop,playerchar->frame,
430 (IsGamePaused() == 0) ? "" : "[Game paused.",
431 (play.ground_level_areas_disabled == 0) ? "" : "[Ground areas disabled.",
432 (IsInterfaceEnabled() == 0) ? "[Game in Wait state" : "");
433 for (ff=0;ff<croom->numobj;ff++) {
434 if (ff >= 8) break; // buffer not big enough for more than 7
435 sprintf(&infobuf[strlen(infobuf)],
436 "[Object %d: (%d,%d) size (%d x %d) on:%d moving:%s animating:%d slot:%d trnsp:%d clkble:%d",
437 ff, objs[ff].x, objs[ff].y,
438 (spriteset[objs[ff].num] != NULL) ? spritewidth[objs[ff].num] : 0,
439 (spriteset[objs[ff].num] != NULL) ? spriteheight[objs[ff].num] : 0,
440 objs[ff].on,
441 (objs[ff].moving > 0) ? "yes" : "no", objs[ff].cycling,
442 objs[ff].num, objs[ff].transparent,
443 ((objs[ff].flags & OBJF_NOINTERACT) != 0) ? 0 : 1 );
444 }
445 Display(infobuf);
446 int chd = game.playercharacter;
447 char bigbuffer[STD_BUFFER_SIZE] = "CHARACTERS IN THIS ROOM:[";
448 for (ff = 0; ff < game.numcharacters; ff++) {
449 if (game.chars[ff].room != displayed_room) continue;
450 if (strlen(bigbuffer) > 430) {
451 strcat(bigbuffer, "and more...");
452 Display(bigbuffer);
453 strcpy(bigbuffer, "CHARACTERS IN THIS ROOM (cont'd):[");
454 }
455 chd = ff;
456 sprintf(&bigbuffer[strlen(bigbuffer)],
457 "%s (view/loop/frm:%d,%d,%d x/y/z:%d,%d,%d idleview:%d,time:%d,left:%d walk:%d anim:%d follow:%d flags:%X wait:%d zoom:%d)[",
458 game.chars[chd].scrname, game.chars[chd].view+1, game.chars[chd].loop, game.chars[chd].frame,
459 game.chars[chd].x, game.chars[chd].y, game.chars[chd].z,
460 game.chars[chd].idleview, game.chars[chd].idletime, game.chars[chd].idleleft,
461 game.chars[chd].walking, game.chars[chd].animating, game.chars[chd].following,
462 game.chars[chd].flags, game.chars[chd].wait, charextra[chd].zoom);
463 }
464 Display(bigbuffer);
465
466 }
467 /* else if (kgn == 21) {
468 play.debug_mode++;
469 script_debug(5,0);
470 play.debug_mode--;
471 }*/
472 else if ((kgn == 22 + AGS_EXT_KEY_SHIFT && (key[KEY_LCONTROL] || key[KEY_RCONTROL]) ) &&
473 (play.wait_counter < 1) && (is_text_overlay == 0) && (restrict_until == 0))
474 {
475 // make sure we can't interrupt a Wait()
476 // and desync the music to cutscene
477 play.debug_mode++;
478 script_debug (1,0);
479 play.debug_mode--;
480 }
481 else if (inside_script) {
482 // Don't queue up another keypress if it can't be run instantly
483 debug_script_log("Keypress %d ignored (game blocked)", kgn);
484 }
485 else {
486 int keywasprocessed = 0;
487 // determine if a GUI Text Box should steal the click
488 // it should do if a displayable character (32-255) is
489 // pressed, but exclude control characters (<32) and
490 // extended keys (eg. up/down arrow; 256+)
491 if ( ((kgn >= 32) && (kgn != '[') && (kgn < 256)) || (kgn == 13) || (kgn == 8) ) {
492 int uu,ww;
493 for (uu=0;uu<game.numgui;uu++) {
494 if (!guis[uu].IsVisible()) continue;
495 for (ww=0;ww<guis[uu].ControlCount;ww++) {
496 // not a text box, ignore it
497 if ((guis[uu].CtrlRefs[ww] >> 16)!=kGUITextBox)
498 continue;
499 GUITextBox*guitex=(GUITextBox*)guis[uu].Controls[ww];
500 // if the text box is disabled, it cannot except keypresses
501 if ((guitex->IsDisabled()) || (!guitex->IsVisible()))
502 continue;
503 guitex->KeyPress(kgn);
504 if (guitex->activated) {
505 guitex->activated = 0;
506 setevent(EV_IFACECLICK, uu, ww, 1);
507 }
508 keywasprocessed = 1;
509 }
510 }
511 }
512 if (!keywasprocessed) {
513 kgn = GetKeyForKeyPressCb(kgn);
514 debug_script_log("Running on_key_press keycode %d", kgn);
515 setevent(EV_TEXTSCRIPT,TS_KEYPRESS,kgn);
516 }
517 }
518 // RunTextScriptIParam(gameinst,"on_key_press",kgn);
519 }
520 }
521
522 // check_controls: checks mouse & keyboard interface
check_controls()523 void check_controls() {
524 our_eip = 1007;
525 NEXT_ITERATION();
526
527 check_mouse_controls();
528 check_keyboard_controls();
529 }
530
check_room_edges(int numevents_was)531 void check_room_edges(int numevents_was)
532 {
533 if ((IsInterfaceEnabled()) && (IsGamePaused() == 0) &&
534 (in_new_room == 0) && (new_room_was == 0)) {
535 // Only allow walking off edges if not in wait mode, and
536 // if not in Player Enters Screen (allow walking in from off-screen)
537 int edgesActivated[4] = {0, 0, 0, 0};
538 // Only do it if nothing else has happened (eg. mouseclick)
539 if ((numevents == numevents_was) &&
540 ((play.ground_level_areas_disabled & GLED_INTERACTION) == 0)) {
541
542 if (playerchar->x <= thisroom.left)
543 edgesActivated[0] = 1;
544 else if (playerchar->x >= thisroom.right)
545 edgesActivated[1] = 1;
546 if (playerchar->y >= thisroom.bottom)
547 edgesActivated[2] = 1;
548 else if (playerchar->y <= thisroom.top)
549 edgesActivated[3] = 1;
550
551 if ((play.entered_edge >= 0) && (play.entered_edge <= 3)) {
552 // once the player is no longer outside the edge, forget the stored edge
553 if (edgesActivated[play.entered_edge] == 0)
554 play.entered_edge = -10;
555 // if we are walking in from off-screen, don't activate edges
556 else
557 edgesActivated[play.entered_edge] = 0;
558 }
559
560 for (int ii = 0; ii < 4; ii++) {
561 if (edgesActivated[ii])
562 setevent(EV_RUNEVBLOCK, EVB_ROOM, 0, ii);
563 }
564 }
565 }
566 our_eip = 1008;
567
568 }
569
game_loop_check_controls(bool checkControls)570 void game_loop_check_controls(bool checkControls)
571 {
572 // don't let the player do anything before the screen fades in
573 if ((in_new_room == 0) && (checkControls)) {
574 int inRoom = displayed_room;
575 int numevents_was = numevents;
576 check_controls();
577 check_room_edges(numevents_was);
578 // If an inventory interaction changed the room
579 if (inRoom != displayed_room)
580 check_new_room();
581 }
582 }
583
game_loop_do_update()584 void game_loop_do_update()
585 {
586 if (debug_flags & DBG_NOUPDATE) ;
587 else if (game_paused==0) update_stuff();
588 }
589
game_loop_update_animated_buttons()590 void game_loop_update_animated_buttons()
591 {
592 // update animating GUI buttons
593 // this bit isn't in update_stuff because it always needs to
594 // happen, even when the game is paused
595 for (int aa = 0; aa < numAnimButs; aa++) {
596 if (UpdateAnimatingButton(aa)) {
597 StopButtonAnimation(aa);
598 aa--;
599 }
600 }
601 }
602
game_loop_do_render_and_check_mouse(IDriverDependantBitmap * extraBitmap,int extraX,int extraY)603 void game_loop_do_render_and_check_mouse(IDriverDependantBitmap *extraBitmap, int extraX, int extraY)
604 // [IKM] ...and some coffee, please :)
605 {
606 if (!play.fast_forward) {
607 int mwasatx=mousex,mwasaty=mousey;
608
609 // Only do this if we are not skipping a cutscene
610 render_graphics(extraBitmap, extraX, extraY);
611
612 // Check Mouse Moves Over Hotspot event
613 static int offsetxWas = -100, offsetyWas = -100;
614
615 if (((mwasatx!=mousex) || (mwasaty!=mousey) ||
616 (offsetxWas != offsetx) || (offsetyWas != offsety)) &&
617 (displayed_room >= 0))
618 {
619 // mouse moves over hotspot
620 if (__GetLocationType(divide_down_coordinate(mousex), divide_down_coordinate(mousey), 1) == LOCTYPE_HOTSPOT) {
621 int onhs = getloctype_index;
622
623 setevent(EV_RUNEVBLOCK,EVB_HOTSPOT,onhs,6);
624 }
625 }
626
627 offsetxWas = offsetx;
628 offsetyWas = offsety;
629
630 #ifdef MAC_VERSION
631 // take a breather after the heavy work
632 // cuts down on CPU usage and reduces the fan noise
633 rest(2);
634 #endif
635 }
636 }
637
game_loop_update_events()638 void game_loop_update_events()
639 {
640 new_room_was = in_new_room;
641 if (in_new_room>0)
642 setevent(EV_FADEIN,0,0,0);
643 in_new_room=0;
644 update_events();
645 if ((new_room_was > 0) && (in_new_room == 0)) {
646 // if in a new room, and the room wasn't just changed again in update_events,
647 // then queue the Enters Screen scripts
648 // run these next time round, when it's faded in
649 if (new_room_was==2) // first time enters screen
650 setevent(EV_RUNEVBLOCK,EVB_ROOM,0,4);
651 if (new_room_was!=3) // enters screen after fadein
652 setevent(EV_RUNEVBLOCK,EVB_ROOM,0,7);
653 }
654 }
655
game_loop_update_background_animation()656 void game_loop_update_background_animation()
657 {
658 if (play.bg_anim_delay > 0) play.bg_anim_delay--;
659 else if (play.bg_frame_locked) ;
660 else {
661 play.bg_anim_delay = play.anim_background_speed;
662 play.bg_frame++;
663 if (play.bg_frame >= thisroom.num_bscenes)
664 play.bg_frame=0;
665 if (thisroom.num_bscenes >= 2) {
666 // get the new frame's palette
667 on_background_frame_change();
668 }
669 }
670 }
671
game_loop_update_loop_counter()672 void game_loop_update_loop_counter()
673 {
674 loopcounter++;
675
676 if (play.wait_counter > 0) play.wait_counter--;
677 if (play.shakesc_length > 0) play.shakesc_length--;
678
679 if (loopcounter % 5 == 0)
680 {
681 update_ambient_sound_vol();
682 update_directional_sound_vol();
683 }
684 }
685
game_loop_check_replay_record()686 void game_loop_check_replay_record()
687 {
688 if (replay_start_this_time) {
689 replay_start_this_time = 0;
690 start_replay_record();
691 }
692 }
693
game_loop_update_fps()694 void game_loop_update_fps()
695 {
696 if (time(NULL) != t1) {
697 t1 = time(NULL);
698 fps = loopcounter - lastcounter;
699 lastcounter = loopcounter;
700 }
701 }
702
WaitForNextFrame()703 void WaitForNextFrame()
704 {
705 while (timerloop == 0) platform->YieldCPU();
706 }
707
UpdateGameOnce(bool checkControls,IDriverDependantBitmap * extraBitmap,int extraX,int extraY)708 void UpdateGameOnce(bool checkControls, IDriverDependantBitmap *extraBitmap, int extraX, int extraY) {
709
710 int res;
711
712 update_mp3();
713
714 numEventsAtStartOfFunction = numevents;
715
716 if (want_exit) {
717 ProperExit();
718 }
719
720 ccNotifyScriptStillAlive ();
721 our_eip=1;
722 timerloop=0;
723
724 game_loop_check_problems_at_start();
725
726 // if we're not fading in, don't count the fadeouts
727 if ((play.no_hicolor_fadein) && (game.options[OPT_FADETYPE] == FADE_NORMAL))
728 play.screen_is_faded_out = 0;
729
730 our_eip = 1014;
731
732 update_gui_disabled_status();
733
734 our_eip = 1004;
735
736 game_loop_check_new_room();
737
738 our_eip = 1005;
739
740 res = game_loop_check_ground_level_interactions();
741 if (res != RETURN_CONTINUE) {
742 return;
743 }
744
745 mouse_on_iface=-1;
746
747 check_debug_keys();
748
749 game_loop_check_controls(checkControls);
750
751 our_eip=2;
752
753 game_loop_do_update();
754
755 game_loop_update_animated_buttons();
756
757 game_loop_do_late_update();
758
759 update_polled_audio_and_crossfade();
760
761 game_loop_do_render_and_check_mouse(extraBitmap, extraX, extraY);
762
763 our_eip=6;
764
765 game_loop_update_events();
766
767 our_eip=7;
768
769 // if (mgetbutton()>NONE) break;
770 update_polled_stuff_if_runtime();
771
772 game_loop_update_background_animation();
773
774 game_loop_update_loop_counter();
775
776 game_loop_check_replay_record();
777
778 // Immediately start the next frame if we are skipping a cutscene
779 if (play.fast_forward)
780 return;
781
782 our_eip=72;
783
784 game_loop_update_fps();
785
786 update_polled_stuff_if_runtime();
787 WaitForNextFrame();
788 }
789
UpdateMouseOverLocation()790 void UpdateMouseOverLocation()
791 {
792 // Call GetLocationName - it will internally force a GUI refresh
793 // if the result it returns has changed from last time
794 char tempo[STD_BUFFER_SIZE];
795 GetLocationName(divide_down_coordinate(mousex), divide_down_coordinate(mousey), tempo);
796
797 if ((play.get_loc_name_save_cursor >= 0) &&
798 (play.get_loc_name_save_cursor != play.get_loc_name_last_time) &&
799 (mouse_on_iface < 0) && (ifacepopped < 0)) {
800 // we have saved the cursor, but the mouse location has changed
801 // and it's time to restore it
802 play.get_loc_name_save_cursor = -1;
803 set_cursor_mode(play.restore_cursor_mode_to);
804
805 if (cur_mode == play.restore_cursor_mode_to)
806 {
807 // make sure it changed -- the new mode might have been disabled
808 // in which case don't change the image
809 set_mouse_cursor(play.restore_cursor_image_to);
810 }
811 debug_script_log("Restore mouse to mode %d cursor %d", play.restore_cursor_mode_to, play.restore_cursor_image_to);
812 }
813 }
814
815 // Checks if user interface should remain disabled for now
ShouldStayInWaitMode()816 int ShouldStayInWaitMode() {
817 if (restrict_until == 0)
818 quit("end_wait_loop called but game not in loop_until state");
819 int retval = restrict_until;
820
821 if (restrict_until==UNTIL_MOVEEND) {
822 short*wkptr=(short*)user_disabled_data;
823 if (wkptr[0]<1) retval=0;
824 }
825 else if (restrict_until==UNTIL_CHARIS0) {
826 char*chptr=(char*)user_disabled_data;
827 if (chptr[0]==0) retval=0;
828 }
829 else if (restrict_until==UNTIL_NEGATIVE) {
830 short*wkptr=(short*)user_disabled_data;
831 if (wkptr[0]<0) retval=0;
832 }
833 else if (restrict_until==UNTIL_INTISNEG) {
834 int*wkptr=(int*)user_disabled_data;
835 if (wkptr[0]<0) retval=0;
836 }
837 else if (restrict_until==UNTIL_NOOVERLAY) {
838 if (is_text_overlay < 1) retval=0;
839 }
840 else if (restrict_until==UNTIL_INTIS0) {
841 int*wkptr=(int*)user_disabled_data;
842 if (wkptr[0]<0) retval=0;
843 }
844 else if (restrict_until==UNTIL_SHORTIS0) {
845 short*wkptr=(short*)user_disabled_data;
846 if (wkptr[0]==0) retval=0;
847 }
848 else quit("loop_until: unknown until event");
849
850 return retval;
851 }
852
UpdateWaitMode()853 int UpdateWaitMode()
854 {
855 if (restrict_until==0) ;
856 else {
857 restrict_until = ShouldStayInWaitMode();
858 our_eip = 77;
859
860 if (restrict_until==0) {
861 set_default_cursor();
862 guis_need_update = 1;
863 play.disabled_user_interface--;
864 /* if (user_disabled_for==FOR_ANIMATION)
865 run_animation((FullAnimation*)user_disabled_data2,user_disabled_data3);
866 else*/ if (user_disabled_for==FOR_EXITLOOP) {
867 user_disabled_for=0; return -1; }
868 else if (user_disabled_for==FOR_SCRIPT) {
869 quit("err: for_script obsolete (v2.1 and earlier only)");
870 }
871 else
872 quit("Unknown user_disabled_for in end restrict_until");
873
874 user_disabled_for=0;
875 }
876 }
877
878 return RETURN_CONTINUE;
879 }
880
881 // Run single game iteration; calls UpdateGameOnce() internally
GameTick()882 int GameTick()
883 {
884 if (displayed_room < 0)
885 quit("!A blocking function was called before the first room has been loaded");
886
887 UpdateGameOnce(true);
888 UpdateMouseOverLocation();
889
890 our_eip=76;
891
892 int res = UpdateWaitMode();
893 if (res != RETURN_CONTINUE) {
894 return res;
895 }
896
897 our_eip = 78;
898 return 0;
899 }
900
SetupLoopParameters(int untilwhat,long udata,int mousestuff)901 void SetupLoopParameters(int untilwhat,long udata,int mousestuff) {
902 play.disabled_user_interface++;
903 guis_need_update = 1;
904 // Only change the mouse cursor if it hasn't been specifically changed first
905 // (or if it's speech, always change it)
906 if (((cur_cursor == cur_mode) || (untilwhat == UNTIL_NOOVERLAY)) &&
907 (cur_mode != CURS_WAIT))
908 set_mouse_cursor(CURS_WAIT);
909
910 restrict_until=untilwhat;
911 user_disabled_data=udata;
912 user_disabled_for=FOR_EXITLOOP;
913 return;
914 }
915
916 // This function is called from lot of various functions
917 // in the game core, character, room object etc
GameLoopUntilEvent(int untilwhat,long daaa)918 void GameLoopUntilEvent(int untilwhat,long daaa) {
919 // blocking cutscene - end skipping
920 EndSkippingUntilCharStops();
921
922 // this function can get called in a nested context, so
923 // remember the state of these vars in case a higher level
924 // call needs them
925 int cached_restrict_until = restrict_until;
926 int cached_user_disabled_data = user_disabled_data;
927 int cached_user_disabled_for = user_disabled_for;
928
929 SetupLoopParameters(untilwhat,daaa,0);
930 while (GameTick()==0) ;
931
932 restrict_until = cached_restrict_until;
933 user_disabled_data = cached_user_disabled_data;
934 user_disabled_for = cached_user_disabled_for;
935 }
936
937 // for external modules to call
NextIteration()938 void NextIteration() {
939 NEXT_ITERATION();
940 }
941
942 extern unsigned int load_new_game;
RunGameUntilAborted()943 void RunGameUntilAborted()
944 {
945 while (!abort_engine) {
946 GameTick();
947
948 if (load_new_game) {
949 RunAGSGame (NULL, load_new_game, 0);
950 load_new_game = 0;
951 }
952 }
953 }
954
update_polled_stuff_if_runtime()955 void update_polled_stuff_if_runtime()
956 {
957 if (want_exit) {
958 want_exit = 0;
959 quit("||exit!");
960 }
961
962 if (!psp_audio_multithreaded)
963 update_polled_mp3();
964
965 if (editor_debugging_initialized)
966 check_for_messages_from_editor();
967 }
968