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 #include <string.h>
16 #include "script/script.h"
17 #include "ac/common.h"
18 #include "ac/roomstruct.h"
19 #include "ac/character.h"
20 #include "ac/dialog.h"
21 #include "ac/event.h"
22 #include "ac/game.h"
23 #include "ac/gamesetupstruct.h"
24 #include "ac/gamestate.h"
25 #include "ac/global_audio.h"
26 #include "ac/global_character.h"
27 #include "ac/global_dialog.h"
28 #include "ac/global_display.h"
29 #include "ac/global_game.h"
30 #include "ac/global_gui.h"
31 #include "ac/global_hotspot.h"
32 #include "ac/global_object.h"
33 #include "ac/global_room.h"
34 #include "ac/invwindow.h"
35 #include "ac/mouse.h"
36 #include "ac/room.h"
37 #include "ac/roomobject.h"
38 #include "script/cc_error.h"
39 #include "script/cc_options.h"
40 #include "debug/debug_log.h"
41 #include "main/game_run.h"
42 #include "media/audio/audio.h"
43 #include "media/audio/soundclip.h"
44 #include "media/video/video.h"
45 #include "script/script_runtime.h"
46 #include "util/string_utils.h"
47 
48 extern GameSetupStruct game;
49 extern GameState play;
50 extern roomstruct thisroom;
51 extern int gameHasBeenRestored, displayed_room;
52 extern unsigned int load_new_game;
53 extern RoomObject*objs;
54 extern int our_eip;
55 extern CharacterInfo*playerchar;
56 
57 ExecutingScript scripts[MAX_SCRIPT_AT_ONCE];
58 ExecutingScript*curscript = NULL;
59 
60 PScript gamescript;
61 PScript dialogScriptsScript;
62 ccInstance *gameinst = NULL, *roominst = NULL;
63 ccInstance *dialogScriptsInst = NULL;
64 ccInstance *gameinstFork = NULL, *roominstFork = NULL;
65 
66 int num_scripts=0;
67 int post_script_cleanup_stack = 0;
68 
69 int inside_script=0,in_graph_script=0;
70 int no_blocking_functions = 0; // set to 1 while in rep_Exec_always
71 
72 NonBlockingScriptFunction repExecAlways(REP_EXEC_ALWAYS_NAME, 0);
73 NonBlockingScriptFunction lateRepExecAlways(LATE_REP_EXEC_ALWAYS_NAME, 0);
74 NonBlockingScriptFunction getDialogOptionsDimensionsFunc("dialog_options_get_dimensions", 1);
75 NonBlockingScriptFunction renderDialogOptionsFunc("dialog_options_render", 1);
76 NonBlockingScriptFunction getDialogOptionUnderCursorFunc("dialog_options_get_active", 1);
77 NonBlockingScriptFunction runDialogOptionMouseClickHandlerFunc("dialog_options_mouse_click", 2);
78 NonBlockingScriptFunction runDialogOptionKeyPressHandlerFunc("dialog_options_key_press", 2);
79 NonBlockingScriptFunction runDialogOptionRepExecFunc("dialog_options_repexec", 1);
80 
81 ScriptSystem scsystem;
82 
83 std::vector<PScript> scriptModules;
84 std::vector<ccInstance *> moduleInst;
85 std::vector<ccInstance *> moduleInstFork;
86 std::vector<RuntimeScriptValue> moduleRepExecAddr;
87 int numScriptModules = 0;
88 
89 std::vector<String> characterScriptObjNames;
90 String              objectScriptObjNames[MAX_INIT_SPR];
91 std::vector<String> guiScriptObjNames;
92 
93 
run_dialog_request(int parmtr)94 int run_dialog_request (int parmtr) {
95     play.stop_dialog_at_end = DIALOG_RUNNING;
96     gameinst->RunTextScriptIParam("dialog_request", RuntimeScriptValue().SetInt32(parmtr));
97 
98     if (play.stop_dialog_at_end == DIALOG_STOP) {
99         play.stop_dialog_at_end = DIALOG_NONE;
100         return -2;
101     }
102     if (play.stop_dialog_at_end >= DIALOG_NEWTOPIC) {
103         int tval = play.stop_dialog_at_end - DIALOG_NEWTOPIC;
104         play.stop_dialog_at_end = DIALOG_NONE;
105         return tval;
106     }
107     if (play.stop_dialog_at_end >= DIALOG_NEWROOM) {
108         int roomnum = play.stop_dialog_at_end - DIALOG_NEWROOM;
109         play.stop_dialog_at_end = DIALOG_NONE;
110         NewRoom(roomnum);
111         return -2;
112     }
113     play.stop_dialog_at_end = DIALOG_NONE;
114     return -1;
115 }
116 
run_function_on_non_blocking_thread(NonBlockingScriptFunction * funcToRun)117 void run_function_on_non_blocking_thread(NonBlockingScriptFunction* funcToRun) {
118 
119     update_script_mouse_coords();
120 
121     int room_changes_was = play.room_changes;
122     funcToRun->atLeastOneImplementationExists = false;
123 
124     // run modules
125     // modules need a forkedinst for this to work
126     for (int kk = 0; kk < numScriptModules; kk++) {
127         funcToRun->moduleHasFunction[kk] = moduleInstFork[kk]->DoRunScriptFuncCantBlock(funcToRun, funcToRun->moduleHasFunction[kk]);
128 
129         if (room_changes_was != play.room_changes)
130             return;
131     }
132 
133     funcToRun->globalScriptHasFunction = gameinstFork->DoRunScriptFuncCantBlock(funcToRun, funcToRun->globalScriptHasFunction);
134 
135     if (room_changes_was != play.room_changes)
136         return;
137 
138     funcToRun->roomHasFunction = roominstFork->DoRunScriptFuncCantBlock(funcToRun, funcToRun->roomHasFunction);
139 }
140 
141 //-----------------------------------------------------------
142 // [IKM] 2012-06-22
143 //
144 // run_interaction_event() and run_interaction_script()
145 // are *almost* identical, except for the first parameter
146 // type.
147 // May these types be made children of the same base?
148 //-----------------------------------------------------------
149 
150 
151 // Returns 0 normally, or -1 to indicate that the NewInteraction has
152 // become invalid and don't run another interaction on it
153 // (eg. a room change occured)
run_interaction_event(Interaction * nint,int evnt,int chkAny,int isInv)154 int run_interaction_event (Interaction *nint, int evnt, int chkAny, int isInv) {
155 
156     if (evnt < 0 || (size_t)evnt >= nint->Events.size() ||
157         (nint->Events[evnt].Response.get() == NULL) || (nint->Events[evnt].Response->Cmds.size() == 0)) {
158         // no response defined for this event
159         // If there is a response for "Any Click", then abort now so as to
160         // run that instead
161         if (chkAny < 0) ;
162         else if ((size_t)chkAny < nint->Events.size() &&
163                 (nint->Events[chkAny].Response.get() != NULL) && (nint->Events[chkAny].Response->Cmds.size() > 0))
164             return 0;
165 
166         // Otherwise, run unhandled_event
167         run_unhandled_event(evnt);
168 
169         return 0;
170     }
171 
172     if (play.check_interaction_only) {
173         play.check_interaction_only = 2;
174         return -1;
175     }
176 
177     int cmdsrun = 0, retval = 0;
178     // Right, so there were some commands defined in response to the event.
179     retval = run_interaction_commandlist (nint->Events[evnt].Response.get(), &nint->Events[evnt].TimesRun, &cmdsrun);
180 
181     // An inventory interaction, but the wrong item was used
182     if ((isInv) && (cmdsrun == 0))
183         run_unhandled_event (evnt);
184 
185     return retval;
186 }
187 
188 // Returns 0 normally, or -1 to indicate that the NewInteraction has
189 // become invalid and don't run another interaction on it
190 // (eg. a room change occured)
run_interaction_script(InteractionScripts * nint,int evnt,int chkAny,int isInv)191 int run_interaction_script(InteractionScripts *nint, int evnt, int chkAny, int isInv) {
192 
193     if ((nint->ScriptFuncNames[evnt] == NULL) || (nint->ScriptFuncNames[evnt][0u] == 0)) {
194         // no response defined for this event
195         // If there is a response for "Any Click", then abort now so as to
196         // run that instead
197         if (chkAny < 0) ;
198         else if ((nint->ScriptFuncNames[chkAny] != NULL) && (nint->ScriptFuncNames[chkAny][0u] != 0))
199             return 0;
200 
201         // Otherwise, run unhandled_event
202         run_unhandled_event(evnt);
203 
204         return 0;
205     }
206 
207     if (play.check_interaction_only) {
208         play.check_interaction_only = 2;
209         return -1;
210     }
211 
212     int room_was = play.room_changes;
213 
214     RuntimeScriptValue rval_null;
215 
216     update_mp3();
217         if ((strstr(evblockbasename,"character")!=0) || (strstr(evblockbasename,"inventory")!=0)) {
218             // Character or Inventory (global script)
219             QueueScriptFunction(kScInstGame, nint->ScriptFuncNames[evnt]);
220         }
221         else {
222             // Other (room script)
223             QueueScriptFunction(kScInstRoom, nint->ScriptFuncNames[evnt]);
224         }
225         update_mp3();
226 
227             int retval = 0;
228         // if the room changed within the action
229         if (room_was != play.room_changes)
230             retval = -1;
231 
232         return retval;
233 }
234 
create_global_script()235 int create_global_script() {
236     ccSetOption(SCOPT_AUTOIMPORT, 1);
237     for (int kk = 0; kk < numScriptModules; kk++) {
238         moduleInst[kk] = ccInstance::CreateFromScript(scriptModules[kk]);
239         if (moduleInst[kk] == NULL)
240             return -3;
241         // create a forked instance for rep_exec_always
242         moduleInstFork[kk] = moduleInst[kk]->Fork();
243         if (moduleInstFork[kk] == NULL)
244             return -3;
245 
246         moduleRepExecAddr[kk] = moduleInst[kk]->GetSymbolAddress(REP_EXEC_NAME);
247     }
248     gameinst = ccInstance::CreateFromScript(gamescript);
249     if (gameinst == NULL)
250         return -3;
251     // create a forked instance for rep_exec_always
252     gameinstFork = gameinst->Fork();
253     if (gameinstFork == NULL)
254         return -3;
255 
256     if (dialogScriptsScript != NULL)
257     {
258         dialogScriptsInst = ccInstance::CreateFromScript(dialogScriptsScript);
259         if (dialogScriptsInst == NULL)
260             return -3;
261     }
262 
263     ccSetOption(SCOPT_AUTOIMPORT, 0);
264     return 0;
265 }
266 
cancel_all_scripts()267 void cancel_all_scripts() {
268     int aa;
269 
270     for (aa = 0; aa < num_scripts; aa++) {
271         if (scripts[aa].forked)
272             scripts[aa].inst->AbortAndDestroy();
273         else
274             scripts[aa].inst->Abort();
275         scripts[aa].numanother = 0;
276     }
277     num_scripts = 0;
278     /*  if (gameinst!=NULL) ->Abort(gameinst);
279     if (roominst!=NULL) ->Abort(roominst);*/
280 }
281 
GetScriptInstanceByType(ScriptInstType sc_inst)282 ccInstance *GetScriptInstanceByType(ScriptInstType sc_inst)
283 {
284     if (sc_inst == kScInstGame)
285         return gameinst;
286     else if (sc_inst == kScInstRoom)
287         return roominst;
288     return NULL;
289 }
290 
QueueScriptFunction(ScriptInstType sc_inst,const char * fn_name,size_t param_count,const RuntimeScriptValue & p1,const RuntimeScriptValue & p2)291 void QueueScriptFunction(ScriptInstType sc_inst, const char *fn_name, size_t param_count, const RuntimeScriptValue &p1, const RuntimeScriptValue &p2)
292 {
293     if (inside_script)
294         // queue the script for the run after current script is finished
295         curscript->run_another (fn_name, sc_inst, param_count, p1, p2);
296     else
297         // if no script is currently running, run the requested script right away
298         RunScriptFunction(sc_inst, fn_name, param_count, p1, p2);
299 }
300 
RunScriptFunction(ScriptInstType sc_inst,const char * fn_name,size_t param_count,const RuntimeScriptValue & p1,const RuntimeScriptValue & p2)301 void RunScriptFunction(ScriptInstType sc_inst, const char *fn_name, size_t param_count, const RuntimeScriptValue &p1, const RuntimeScriptValue &p2)
302 {
303     ccInstance *inst = GetScriptInstanceByType(sc_inst);
304     if (inst)
305     {
306         if (param_count == 2)
307             inst->RunTextScript2IParam(fn_name, p1, p2);
308         else if (param_count == 1)
309             inst->RunTextScriptIParam(fn_name, p1);
310         else if (param_count == 0)
311             inst->RunTextScript(fn_name);
312     }
313 }
314 
315 //=============================================================================
316 
317 
318 char bname[MAX_FUNCTION_NAME_LEN+1],bne[MAX_FUNCTION_NAME_LEN+1];
make_ts_func_name(char * base,int iii,int subd)319 char* make_ts_func_name(char*base,int iii,int subd) {
320     snprintf(bname,MAX_FUNCTION_NAME_LEN,base,iii);
321     snprintf(bne,MAX_FUNCTION_NAME_LEN,"%s_%c",bname,subd+'a');
322     return &bne[0];
323 }
324 
post_script_cleanup()325 void post_script_cleanup() {
326     // should do any post-script stuff here, like go to new room
327     if (ccError) quit(ccErrorString);
328     ExecutingScript copyof = scripts[num_scripts-1];
329     if (scripts[num_scripts-1].forked)
330         delete scripts[num_scripts-1].inst;
331     num_scripts--;
332     inside_script--;
333 
334     if (num_scripts > 0)
335         curscript = &scripts[num_scripts-1];
336     else {
337         curscript = NULL;
338     }
339     //  if (abort_executor) user_disabled_data2=aborted_ip;
340 
341     int old_room_number = displayed_room;
342 
343     // run the queued post-script actions
344     for (int ii = 0; ii < copyof.numPostScriptActions; ii++) {
345         int thisData = copyof.postScriptActionData[ii];
346 
347         switch (copyof.postScriptActions[ii]) {
348     case ePSANewRoom:
349         // only change rooms when all scripts are done
350         if (num_scripts == 0) {
351             new_room(thisData, playerchar);
352             // don't allow any pending room scripts from the old room
353             // in run_another to be executed
354             return;
355         }
356         else
357             curscript->queue_action(ePSANewRoom, thisData, "NewRoom");
358         break;
359     case ePSAInvScreen:
360         invscreen();
361         break;
362     case ePSARestoreGame:
363         cancel_all_scripts();
364         try_restore_save(thisData);
365         return;
366     case ePSARestoreGameDialog:
367         restore_game_dialog();
368         return;
369     case ePSARunAGSGame:
370         cancel_all_scripts();
371         load_new_game = thisData;
372         return;
373     case ePSARunDialog:
374         do_conversation(thisData);
375         break;
376     case ePSARestartGame:
377         cancel_all_scripts();
378         restart_game();
379         return;
380     case ePSASaveGame:
381         save_game(thisData, copyof.postScriptSaveSlotDescription[ii]);
382         break;
383     case ePSASaveGameDialog:
384         save_game_dialog();
385         break;
386     default:
387         quitprintf("undefined post script action found: %d", copyof.postScriptActions[ii]);
388         }
389         // if the room changed in a conversation, for example, abort
390         if (old_room_number != displayed_room) {
391             return;
392         }
393     }
394 
395 
396     int jj;
397     for (jj = 0; jj < copyof.numanother; jj++) {
398         old_room_number = displayed_room;
399         QueuedScript &script = copyof.ScFnQueue[jj];
400         RunScriptFunction(script.Instance, script.FnName, script.ParamCount, script.Param1, script.Param2);
401         if (script.Instance == kScInstRoom && script.ParamCount == 1)
402         {
403             // some bogus hack for "on_call" event handler
404             play.roomscript_finished = 1;
405         }
406 
407         // if they've changed rooms, cancel any further pending scripts
408         if ((displayed_room != old_room_number) || (load_new_game))
409             break;
410     }
411     copyof.numanother = 0;
412 
413 }
414 
quit_with_script_error(const char * functionName)415 void quit_with_script_error(const char *functionName)
416 {
417     quitprintf("%sError running function '%s':\n%s", (ccErrorIsUserError ? "!" : ""), functionName, ccErrorString);
418 }
419 
get_nivalue(InteractionCommandList * nic,int idx,int parm)420 int get_nivalue (InteractionCommandList *nic, int idx, int parm) {
421     if (nic->Cmds[idx].Data[parm].Type == AGS::Common::kInterValVariable) {
422         // return the value of the variable
423         return get_interaction_variable(nic->Cmds[idx].Data[parm].Value)->Value;
424     }
425     return nic->Cmds[idx].Data[parm].Value;
426 }
427 
get_interaction_variable(int varindx)428 InteractionVariable *get_interaction_variable (int varindx) {
429 
430     if ((varindx >= LOCAL_VARIABLE_OFFSET) && (varindx < LOCAL_VARIABLE_OFFSET + thisroom.numLocalVars))
431         return &thisroom.localvars[varindx - LOCAL_VARIABLE_OFFSET];
432 
433     if ((varindx < 0) || (varindx >= numGlobalVars))
434         quit("!invalid interaction variable specified");
435 
436     return &globalvars[varindx];
437 }
438 
FindGraphicalVariable(const char * varName)439 InteractionVariable *FindGraphicalVariable(const char *varName) {
440     int ii;
441     for (ii = 0; ii < numGlobalVars; ii++) {
442         if (stricmp (globalvars[ii].Name, varName) == 0)
443             return &globalvars[ii];
444     }
445     for (ii = 0; ii < thisroom.numLocalVars; ii++) {
446         if (stricmp (thisroom.localvars[ii].Name, varName) == 0)
447             return &thisroom.localvars[ii];
448     }
449     return NULL;
450 }
451 
452 #define IPARAM1 get_nivalue(nicl, i, 0)
453 #define IPARAM2 get_nivalue(nicl, i, 1)
454 #define IPARAM3 get_nivalue(nicl, i, 2)
455 #define IPARAM4 get_nivalue(nicl, i, 3)
456 #define IPARAM5 get_nivalue(nicl, i, 4)
457 
458 struct TempEip {
459     int oldval;
TempEipTempEip460     TempEip (int newval) {
461         oldval = our_eip;
462         our_eip = newval;
463     }
~TempEipTempEip464     ~TempEip () { our_eip = oldval; }
465 };
466 
467 // the 'cmdsrun' parameter counts how many commands are run.
468 // if a 'Inv Item Was Used' check does not pass, it doesn't count
469 // so cmdsrun remains 0 if no inventory items matched
run_interaction_commandlist(InteractionCommandList * nicl,int * timesrun,int * cmdsrun)470 int run_interaction_commandlist (InteractionCommandList *nicl, int *timesrun, int*cmdsrun) {
471     size_t i;
472 
473     if (nicl == NULL)
474         return -1;
475 
476     for (i = 0; i < nicl->Cmds.size(); i++) {
477         cmdsrun[0] ++;
478         int room_was = play.room_changes;
479 
480         switch (nicl->Cmds[i].Type) {
481       case 0:  // Do nothing
482           break;
483       case 1:  // Run script
484           {
485               TempEip tempip(4001);
486               RuntimeScriptValue rval_null;
487               update_mp3();
488                   if ((strstr(evblockbasename,"character")!=0) || (strstr(evblockbasename,"inventory")!=0)) {
489                       // Character or Inventory (global script)
490                       const char *torun = make_ts_func_name(evblockbasename,evblocknum,nicl->Cmds[i].Data[0].Value);
491                       // we are already inside the mouseclick event of the script, can't nest calls
492                       QueueScriptFunction(kScInstGame, torun);
493                   }
494                   else {
495                       // Other (room script)
496                       const char *torun = make_ts_func_name(evblockbasename,evblocknum,nicl->Cmds[i].Data[0].Value);
497                       QueueScriptFunction(kScInstRoom, torun);
498                   }
499                   update_mp3();
500                       break;
501           }
502       case 2:  // Add score (first time)
503           if (timesrun[0] > 0)
504               break;
505           timesrun[0] ++;
506       case 3:  // Add score
507           GiveScore (IPARAM1);
508           break;
509       case 4:  // Display Message
510           /*        if (comprdata<0)
511           display_message_aschar=evb->data[ss];*/
512           DisplayMessage(IPARAM1);
513           break;
514       case 5:  // Play Music
515           PlayMusicResetQueue(IPARAM1);
516           break;
517       case 6:  // Stop Music
518           stopmusic ();
519           break;
520       case 7:  // Play Sound
521           play_sound (IPARAM1);
522           break;
523       case 8:  // Play Flic
524           play_flc_file(IPARAM1, IPARAM2);
525           break;
526       case 9:  // Run Dialog
527           { int room_was = play.room_changes;
528           RunDialog(IPARAM1);
529           // if they changed room within the dialog script,
530           // the interaction command list is no longer valid
531           if (room_was != play.room_changes)
532               return -1;
533           }
534           break;
535       case 10: // Enable Dialog Option
536           SetDialogOption (IPARAM1, IPARAM2, 1);
537           break;
538       case 11: // Disable Dialog Option
539           SetDialogOption (IPARAM1, IPARAM2, 0);
540           break;
541       case 12: // Go To Screen
542           Character_ChangeRoomAutoPosition(playerchar, IPARAM1, IPARAM2);
543           return -1;
544       case 13: // Add Inventory
545           add_inventory (IPARAM1);
546           break;
547       case 14: // Move Object
548           MoveObject (IPARAM1, IPARAM2, IPARAM3, IPARAM4);
549           // if they want to wait until finished, do so
550           if (IPARAM5)
551               GameLoopUntilEvent(UNTIL_MOVEEND,(long)&objs[IPARAM1].moving);
552           break;
553       case 15: // Object Off
554           ObjectOff (IPARAM1);
555           break;
556       case 16: // Object On
557           ObjectOn (IPARAM1);
558           break;
559       case 17: // Set Object View
560           SetObjectView (IPARAM1, IPARAM2);
561           break;
562       case 18: // Animate Object
563           AnimateObject (IPARAM1, IPARAM2, IPARAM3, IPARAM4);
564           break;
565       case 19: // Move Character
566           if (IPARAM4)
567               MoveCharacterBlocking (IPARAM1, IPARAM2, IPARAM3, 0);
568           else
569               MoveCharacter (IPARAM1, IPARAM2, IPARAM3);
570           break;
571       case 20: // If Inventory Item was used
572           if (play.usedinv == IPARAM1) {
573               if (game.options[OPT_NOLOSEINV] == 0)
574                   lose_inventory (play.usedinv);
575               if (run_interaction_commandlist (nicl->Cmds[i].Children.get(), timesrun, cmdsrun))
576                   return -1;
577           }
578           else
579               cmdsrun[0] --;
580           break;
581       case 21: // if player has inventory item
582           if (playerchar->inv[IPARAM1] > 0)
583               if (run_interaction_commandlist (nicl->Cmds[i].Children.get(), timesrun, cmdsrun))
584                   return -1;
585           break;
586       case 22: // if a character is moving
587           if (game.chars[IPARAM1].walking)
588               if (run_interaction_commandlist (nicl->Cmds[i].Children.get(), timesrun, cmdsrun))
589                   return -1;
590           break;
591       case 23: // if two variables are equal
592           if (IPARAM1 == IPARAM2)
593               if (run_interaction_commandlist (nicl->Cmds[i].Children.get(), timesrun, cmdsrun))
594                   return -1;
595           break;
596       case 24: // Stop character walking
597           StopMoving (IPARAM1);
598           break;
599       case 25: // Go to screen at specific co-ordinates
600           NewRoomEx (IPARAM1, IPARAM2, IPARAM3);
601           return -1;
602       case 26: // Move NPC to different room
603           if (!is_valid_character(IPARAM1))
604               quit("!Move NPC to different room: invalid character specified");
605           game.chars[IPARAM1].room = IPARAM2;
606           break;
607       case 27: // Set character view
608           SetCharacterView (IPARAM1, IPARAM2);
609           break;
610       case 28: // Release character view
611           ReleaseCharacterView (IPARAM1);
612           break;
613       case 29: // Follow character
614           FollowCharacter (IPARAM1, IPARAM2);
615           break;
616       case 30: // Stop following
617           FollowCharacter (IPARAM1, -1);
618           break;
619       case 31: // Disable hotspot
620           DisableHotspot (IPARAM1);
621           break;
622       case 32: // Enable hotspot
623           EnableHotspot (IPARAM1);
624           break;
625       case 33: // Set variable value
626           get_interaction_variable(nicl->Cmds[i].Data[0].Value)->Value = IPARAM2;
627           break;
628       case 34: // Run animation
629           scAnimateCharacter(IPARAM1, IPARAM2, IPARAM3, 0);
630           GameLoopUntilEvent(UNTIL_SHORTIS0,(long)&game.chars[IPARAM1].animating);
631           break;
632       case 35: // Quick animation
633           SetCharacterView (IPARAM1, IPARAM2);
634           scAnimateCharacter(IPARAM1, IPARAM3, IPARAM4, 0);
635           GameLoopUntilEvent(UNTIL_SHORTIS0,(long)&game.chars[IPARAM1].animating);
636           ReleaseCharacterView (IPARAM1);
637           break;
638       case 36: // Set idle animation
639           SetCharacterIdle (IPARAM1, IPARAM2, IPARAM3);
640           break;
641       case 37: // Disable idle animation
642           SetCharacterIdle (IPARAM1, -1, -1);
643           break;
644       case 38: // Lose inventory item
645           lose_inventory (IPARAM1);
646           break;
647       case 39: // Show GUI
648           InterfaceOn (IPARAM1);
649           break;
650       case 40: // Hide GUI
651           InterfaceOff (IPARAM1);
652           break;
653       case 41: // Stop running more commands
654           return -1;
655       case 42: // Face location
656           FaceLocation (IPARAM1, IPARAM2, IPARAM3);
657           break;
658       case 43: // Pause command processor
659           scrWait (IPARAM1);
660           break;
661       case 44: // Change character view
662           ChangeCharacterView (IPARAM1, IPARAM2);
663           break;
664       case 45: // If player character is
665           if (GetPlayerCharacter() == IPARAM1)
666               if (run_interaction_commandlist (nicl->Cmds[i].Children.get(), timesrun, cmdsrun))
667                   return -1;
668           break;
669       case 46: // if cursor mode is
670           if (GetCursorMode() == IPARAM1)
671               if (run_interaction_commandlist (nicl->Cmds[i].Children.get(), timesrun, cmdsrun))
672                   return -1;
673           break;
674       case 47: // if player has been to room
675           if (HasBeenToRoom(IPARAM1))
676               if (run_interaction_commandlist (nicl->Cmds[i].Children.get(), timesrun, cmdsrun))
677                   return -1;
678           break;
679       default:
680           quit("unknown new interaction command");
681           break;
682         }
683 
684         // if the room changed within the action, nicl is no longer valid
685         if (room_was != play.room_changes)
686             return -1;
687     }
688     return 0;
689 
690 }
691 
692 // check and abort game if the script is currently
693 // inside the rep_exec_always function
can_run_delayed_command()694 void can_run_delayed_command() {
695   if (no_blocking_functions)
696     quit("!This command cannot be used within non-blocking events such as " REP_EXEC_ALWAYS_NAME);
697 }
698 
run_unhandled_event(int evnt)699 void run_unhandled_event (int evnt) {
700 
701     if (play.check_interaction_only)
702         return;
703 
704     int evtype=0;
705     if (strnicmp(evblockbasename,"hotspot",7)==0) evtype=1;
706     else if (strnicmp(evblockbasename,"object",6)==0) evtype=2;
707     else if (strnicmp(evblockbasename,"character",9)==0) evtype=3;
708     else if (strnicmp(evblockbasename,"inventory",9)==0) evtype=5;
709     else if (strnicmp(evblockbasename,"region",6)==0)
710         return;  // no unhandled_events for regions
711 
712     // clicked Hotspot 0, so change the type code
713     if ((evtype == 1) & (evblocknum == 0) & (evnt != 0) & (evnt != 5) & (evnt != 6))
714         evtype = 4;
715     if ((evtype==1) & ((evnt==0) | (evnt==5) | (evnt==6)))
716         ;  // character stands on hotspot, mouse moves over hotspot, any click
717     else if ((evtype==2) & (evnt==4)) ;  // any click on object
718     else if ((evtype==3) & (evnt==4)) ;  // any click on character
719     else if (evtype > 0) {
720         can_run_delayed_command();
721 
722         QueueScriptFunction(kScInstGame, "unhandled_event", 2, RuntimeScriptValue().SetInt32(evtype), RuntimeScriptValue().SetInt32(evnt));
723     }
724 }
725