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