/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "ags/engine/script/script.h" #include "ags/shared/ac/common.h" #include "ags/engine/ac/character.h" #include "ags/engine/ac/dialog.h" #include "ags/engine/ac/event.h" #include "ags/engine/ac/game.h" #include "ags/shared/ac/gamesetupstruct.h" #include "ags/engine/ac/gamestate.h" #include "ags/engine/ac/global_audio.h" #include "ags/engine/ac/global_character.h" #include "ags/engine/ac/global_dialog.h" #include "ags/engine/ac/global_display.h" #include "ags/engine/ac/global_game.h" #include "ags/engine/ac/global_gui.h" #include "ags/engine/ac/global_hotspot.h" #include "ags/engine/ac/global_object.h" #include "ags/engine/ac/global_room.h" #include "ags/engine/ac/invwindow.h" #include "ags/engine/ac/mouse.h" #include "ags/engine/ac/room.h" #include "ags/engine/ac/roomobject.h" #include "ags/shared/script/cc_error.h" #include "ags/shared/script/cc_options.h" #include "ags/engine/debugging/debugger.h" #include "ags/engine/debugging/debug_log.h" #include "ags/engine/main/game_run.h" #include "ags/engine/media/video/video.h" #include "ags/engine/script/script_runtime.h" #include "ags/shared/util/string_compat.h" #include "ags/engine/media/audio/audio_system.h" #include "ags/globals.h" namespace AGS3 { int run_dialog_request(int parmtr) { _GP(play).stop_dialog_at_end = DIALOG_RUNNING; RunTextScriptIParam(_G(gameinst), "dialog_request", RuntimeScriptValue().SetInt32(parmtr)); if (_GP(play).stop_dialog_at_end == DIALOG_STOP) { _GP(play).stop_dialog_at_end = DIALOG_NONE; return -2; } if (_GP(play).stop_dialog_at_end >= DIALOG_NEWTOPIC) { int tval = _GP(play).stop_dialog_at_end - DIALOG_NEWTOPIC; _GP(play).stop_dialog_at_end = DIALOG_NONE; return tval; } if (_GP(play).stop_dialog_at_end >= DIALOG_NEWROOM) { int roomnum = _GP(play).stop_dialog_at_end - DIALOG_NEWROOM; _GP(play).stop_dialog_at_end = DIALOG_NONE; NewRoom(roomnum); return -2; } _GP(play).stop_dialog_at_end = DIALOG_NONE; return -1; } void run_function_on_non_blocking_thread(NonBlockingScriptFunction *funcToRun) { update_script_mouse_coords(); int room_changes_was = _GP(play).room_changes; funcToRun->atLeastOneImplementationExists = false; // run modules // modules need a forkedinst for this to work for (int kk = 0; kk < _G(numScriptModules); kk++) { funcToRun->moduleHasFunction[kk] = DoRunScriptFuncCantBlock(_GP(moduleInstFork)[kk], funcToRun, funcToRun->moduleHasFunction[kk]); if (room_changes_was != _GP(play).room_changes) return; } funcToRun->globalScriptHasFunction = DoRunScriptFuncCantBlock(_G(gameinstFork), funcToRun, funcToRun->globalScriptHasFunction); if (room_changes_was != _GP(play).room_changes) return; funcToRun->roomHasFunction = DoRunScriptFuncCantBlock(_G(roominstFork), funcToRun, funcToRun->roomHasFunction); } //----------------------------------------------------------- // [IKM] 2012-06-22 // // run_interaction_event() and run_interaction_script() // are *almost* identical, except for the first parameter // type. // May these types be made children of the same base? //----------------------------------------------------------- // Returns 0 normally, or -1 to indicate that the NewInteraction has // become invalid and don't run another interaction on it // (eg. a room change occured) int run_interaction_event(Interaction *nint, int evnt, int chkAny, int isInv) { if (evnt < 0 || (size_t)evnt >= nint->Events.size() || (nint->Events[evnt].Response.get() == nullptr) || (nint->Events[evnt].Response->Cmds.size() == 0)) { // no response defined for this event // If there is a response for "Any Click", then abort now so as to // run that instead if (chkAny < 0); else if ((size_t)chkAny < nint->Events.size() && (nint->Events[chkAny].Response.get() != nullptr) && (nint->Events[chkAny].Response->Cmds.size() > 0)) return 0; // Otherwise, run unhandled_event run_unhandled_event(evnt); return 0; } if (_GP(play).check_interaction_only) { _GP(play).check_interaction_only = 2; return -1; } int cmdsrun = 0, retval = 0; // Right, so there were some commands defined in response to the event. retval = run_interaction_commandlist(nint->Events[evnt].Response.get(), &nint->Events[evnt].TimesRun, &cmdsrun); if (_G(abort_engine)) return -1; // An inventory interaction, but the wrong item was used if ((isInv) && (cmdsrun == 0)) run_unhandled_event(evnt); return retval; } // Returns 0 normally, or -1 to indicate that the NewInteraction has // become invalid and don't run another interaction on it // (eg. a room change occured) int run_interaction_script(InteractionScripts *nint, int evnt, int chkAny, int isInv) { if ((nint->ScriptFuncNames[evnt] == nullptr) || (nint->ScriptFuncNames[evnt][0u] == 0)) { // no response defined for this event // If there is a response for "Any Click", then abort now so as to // run that instead if (chkAny < 0); else if ((nint->ScriptFuncNames[chkAny] != nullptr) && (nint->ScriptFuncNames[chkAny][0u] != 0)) return 0; // Otherwise, run unhandled_event run_unhandled_event(evnt); return 0; } if (_GP(play).check_interaction_only) { _GP(play).check_interaction_only = 2; return -1; } int room_was = _GP(play).room_changes; RuntimeScriptValue rval_null; if ((strstr(_G(evblockbasename), "character") != nullptr) || (strstr(_G(evblockbasename), "inventory") != nullptr)) { // Character or Inventory (global script) QueueScriptFunction(kScInstGame, nint->ScriptFuncNames[evnt]); } else { // Other (room script) QueueScriptFunction(kScInstRoom, nint->ScriptFuncNames[evnt]); } int retval = 0; // if the room changed within the action if (room_was != _GP(play).room_changes) retval = -1; return retval; } int create_global_script() { ccSetOption(SCOPT_AUTOIMPORT, 1); for (int kk = 0; kk < _G(numScriptModules); kk++) { _GP(moduleInst)[kk] = ccInstance::CreateFromScript(_GP(scriptModules)[kk]); if (_GP(moduleInst)[kk] == nullptr) return -3; // create a forked instance for rep_exec_always _GP(moduleInstFork)[kk] = _GP(moduleInst)[kk]->Fork(); if (_GP(moduleInstFork)[kk] == nullptr) return -3; _GP(moduleRepExecAddr)[kk] = _GP(moduleInst)[kk]->GetSymbolAddress(REP_EXEC_NAME); } _G(gameinst) = ccInstance::CreateFromScript(_GP(gamescript)); if (_G(gameinst) == nullptr) return -3; // create a forked instance for rep_exec_always _G(gameinstFork) = _G(gameinst)->Fork(); if (_G(gameinstFork) == nullptr) return -3; if (_GP(dialogScriptsScript) != nullptr) { _G(dialogScriptsInst) = ccInstance::CreateFromScript(_GP(dialogScriptsScript)); if (_G(dialogScriptsInst) == nullptr) return -3; } ccSetOption(SCOPT_AUTOIMPORT, 0); return 0; } void cancel_all_scripts() { int aa; for (aa = 0; aa < _G(num_scripts); aa++) { if (_G(scripts)[aa].forked) _G(scripts)[aa].inst->AbortAndDestroy(); else _G(scripts)[aa].inst->Abort(); _G(scripts)[aa].numanother = 0; } _G(num_scripts) = 0; /* if (_G(gameinst)!=NULL) ->Abort(_G(gameinst)); if (_G(roominst)!=NULL) ->Abort(_G(roominst));*/ } ccInstance *GetScriptInstanceByType(ScriptInstType sc_inst) { if (sc_inst == kScInstGame) return _G(gameinst); else if (sc_inst == kScInstRoom) return _G(roominst); return nullptr; } void QueueScriptFunction(ScriptInstType sc_inst, const char *fn_name, size_t param_count, const RuntimeScriptValue &p1, const RuntimeScriptValue &p2) { if (_G(inside_script)) // queue the script for the run after current script is finished _G(curscript)->run_another(fn_name, sc_inst, param_count, p1, p2); else // if no script is currently running, run the requested script right away RunScriptFunction(sc_inst, fn_name, param_count, p1, p2); } void RunScriptFunction(ScriptInstType sc_inst, const char *fn_name, size_t param_count, const RuntimeScriptValue &p1, const RuntimeScriptValue &p2) { ccInstance *sci = GetScriptInstanceByType(sc_inst); if (sci) { if (param_count == 2) RunTextScript2IParam(sci, fn_name, p1, p2); else if (param_count == 1) RunTextScriptIParam(sci, fn_name, p1); else if (param_count == 0) RunTextScript(sci, fn_name); } } bool DoRunScriptFuncCantBlock(ccInstance *sci, NonBlockingScriptFunction *funcToRun, bool hasTheFunc) { if (!hasTheFunc) return (false); _G(no_blocking_functions)++; int result = 0; if (funcToRun->numParameters < 3) { result = sci->CallScriptFunction((const char *)funcToRun->functionName, funcToRun->numParameters, funcToRun->params); } else quit("DoRunScriptFuncCantBlock called with too many parameters"); if (result == -2) { // the function doens't exist, so don't try and run it again hasTheFunc = false; } else if ((result != 0) && (result != 100)) { quit_with_script_error(funcToRun->functionName); } else { funcToRun->atLeastOneImplementationExists = true; } // this might be nested, so don't disrupt blocked scripts _G(ccErrorString) = ""; _G(ccError) = 0; _G(no_blocking_functions)--; return (hasTheFunc); } char scfunctionname[MAX_FUNCTION_NAME_LEN + 1]; int PrepareTextScript(ccInstance *sci, const char **tsname) { _G(ccError) = 0; // FIXME: try to make it so this function is not called with NULL sci if (sci == nullptr) return -1; if (sci->GetSymbolAddress(tsname[0]).IsNull()) { _G(ccErrorString) = "no such function in script"; return -2; } if (sci->IsBeingRun()) { _G(ccErrorString) = "script is already in execution"; return -3; } assert(_G(num_scripts) < MAX_SCRIPT_AT_ONCE); _G(scripts)[_G(num_scripts)].init(); _G(scripts)[_G(num_scripts)].inst = sci; // CHECKME: this conditional block will never run, because // function would have quit earlier (deprecated functionality?) if (sci->IsBeingRun()) { _G(scripts)[_G(num_scripts)].inst = sci->Fork(); if (_G(scripts)[_G(num_scripts)].inst == nullptr) quit("unable to fork instance for secondary script"); _G(scripts)[_G(num_scripts)].forked = 1; } _G(curscript) = &_G(scripts)[_G(num_scripts)]; _G(num_scripts)++; if (_G(num_scripts) >= MAX_SCRIPT_AT_ONCE) quit("too many nested text script instances created"); // in case script_run_another is the function name, take a backup strncpy(scfunctionname, tsname[0], MAX_FUNCTION_NAME_LEN); tsname[0] = &scfunctionname[0]; update_script_mouse_coords(); _G(inside_script)++; // aborted_ip=0; // abort_executor=0; return 0; } int RunScriptFunctionIfExists(ccInstance *sci, const char *tsname, int numParam, const RuntimeScriptValue *params) { int oldRestoreCount = _G(gameHasBeenRestored); // First, save the current _G(ccError) state // This is necessary because we might be attempting // to run Script B, while Script A is still running in the // background. // If CallInstance here has an error, it would otherwise // also abort Script A because _G(ccError) is a global variable. int cachedCcError = _G(ccError); _G(ccError) = 0; int toret = PrepareTextScript(sci, &tsname); if (toret) { _G(ccError) = cachedCcError; return -18; } // Clear the error message _G(ccErrorString) = ""; if (numParam < 3) { toret = _G(curscript)->inst->CallScriptFunction(tsname, numParam, params); } else quit("Too many parameters to RunScriptFunctionIfExists"); if (_G(abort_engine)) return -1; // 100 is if Aborted (eg. because we are LoadAGSGame'ing) if ((toret != 0) && (toret != -2) && (toret != 100)) { quit_with_script_error(tsname); } _G(post_script_cleanup_stack)++; if (_G(post_script_cleanup_stack) > 50) quitprintf("!post_script_cleanup call stack exceeded: possible recursive function call? running %s", tsname); post_script_cleanup(); _G(post_script_cleanup_stack)--; // restore cached error state _G(ccError) = cachedCcError; // if the game has been restored, ensure that any further scripts are not run if ((oldRestoreCount != _G(gameHasBeenRestored)) && (_G(eventClaimed) == EVENT_INPROGRESS)) _G(eventClaimed) = EVENT_CLAIMED; return toret; } int RunTextScript(ccInstance *sci, const char *tsname) { if (strcmp(tsname, REP_EXEC_NAME) == 0) { // run module rep_execs // FIXME: in theory the function may be already called for _GP(moduleInst)[i], // in which case this should not be executed; need to rearrange the code somehow int room_changes_was = _GP(play).room_changes; int restore_game_count_was = _G(gameHasBeenRestored); for (int kk = 0; kk < _G(numScriptModules); kk++) { if (!_GP(moduleRepExecAddr)[kk].IsNull()) RunScriptFunctionIfExists(_GP(moduleInst)[kk], tsname, 0, nullptr); if ((room_changes_was != _GP(play).room_changes) || (restore_game_count_was != _G(gameHasBeenRestored))) return 0; } } int toret = RunScriptFunctionIfExists(sci, tsname, 0, nullptr); if ((toret == -18) && (sci == _G(roominst))) { // functions in room script must exist quitprintf("prepare_script: error %d (%s) trying to run '%s' (Room %d)", toret, _G(ccErrorString).GetCStr(), tsname, _G(displayed_room)); } return toret; } int RunTextScriptIParam(ccInstance *sci, const char *tsname, const RuntimeScriptValue &iparam) { if ((strcmp(tsname, "on_key_press") == 0) || (strcmp(tsname, "on_mouse_click") == 0)) { bool eventWasClaimed; int toret = run_claimable_event(tsname, true, 1, &iparam, &eventWasClaimed); if (eventWasClaimed) return toret; } return RunScriptFunctionIfExists(sci, tsname, 1, &iparam); } int RunTextScript2IParam(ccInstance *sci, const char *tsname, const RuntimeScriptValue &iparam, const RuntimeScriptValue ¶m2) { RuntimeScriptValue params[2]; params[0] = iparam; params[1] = param2; if (strcmp(tsname, "on_event") == 0) { bool eventWasClaimed; int toret = run_claimable_event(tsname, true, 2, params, &eventWasClaimed); if (eventWasClaimed || _G(abort_engine)) return toret; } // response to a button click, better update guis if (ags_strnicmp(tsname, "interface_click", 15) == 0) _G(guis_need_update) = 1; return RunScriptFunctionIfExists(sci, tsname, 2, params); } String GetScriptName(ccInstance *sci) { // TODO: have script name a ccScript's member? // TODO: check script modules too? if (!sci) return "Not in a script"; else if (sci->instanceof == _GP(gamescript)) return "Global script"; else if (sci->instanceof == _GP(thisroom).CompiledScript) return String::FromFormat("Room %d script", _G(displayed_room)); return "Unknown script"; } //============================================================================= char bname[MAX_FUNCTION_NAME_LEN + 1], bne[MAX_FUNCTION_NAME_LEN + 1]; char *make_ts_func_name(const char *base, int iii, int subd) { int err = snprintf(bname, MAX_FUNCTION_NAME_LEN, base, iii); if (err >= (int)sizeof(bname)) debug_script_warn("Function name length limit exceeded: %s (%d)", base, iii); err = snprintf(bne, MAX_FUNCTION_NAME_LEN, "%s_%c", bname, subd + 'a'); if (err >= (int)sizeof(bne)) debug_script_warn("Function name length limit exceeded: %s", bname); return &bne[0]; } void post_script_cleanup() { // should do any post-script stuff here, like go to new room if (_G(ccError)) quit(_G(ccErrorString)); ExecutingScript copyof = _G(scripts)[_G(num_scripts) - 1]; if (_G(scripts)[_G(num_scripts) - 1].forked) delete _G(scripts)[_G(num_scripts) - 1].inst; _G(num_scripts)--; _G(inside_script)--; if (_G(num_scripts) > 0) _G(curscript) = &_G(scripts)[_G(num_scripts) - 1]; else { _G(curscript) = nullptr; } // if (abort_executor) user_disabled_data2=aborted_ip; int old_room_number = _G(displayed_room); // run the queued post-script actions for (int ii = 0; ii < copyof.numPostScriptActions; ii++) { int thisData = copyof.postScriptActionData[ii]; switch (copyof.postScriptActions[ii]) { case ePSANewRoom: // only change rooms when all scripts are done if (_G(num_scripts) == 0) { new_room(thisData, _G(playerchar)); // don't allow any pending room scripts from the old room // in run_another to be executed return; } else _G(curscript)->queue_action(ePSANewRoom, thisData, "NewRoom"); break; case ePSAInvScreen: invscreen(); break; case ePSARestoreGame: cancel_all_scripts(); try_restore_save(thisData); return; case ePSARestoreGameDialog: restore_game_dialog(); return; case ePSARunAGSGame: cancel_all_scripts(); _G(load_new_game) = thisData; return; case ePSARunDialog: do_conversation(thisData); break; case ePSARestartGame: cancel_all_scripts(); restart_game(); return; case ePSASaveGame: save_game(thisData, copyof.postScriptSaveSlotDescription[ii]); break; case ePSASaveGameDialog: save_game_dialog(); break; default: quitprintf("undefined post script action found: %d", copyof.postScriptActions[ii]); } // if the room changed in a conversation, for example, abort if (old_room_number != _G(displayed_room)) { return; } } int jj; for (jj = 0; jj < copyof.numanother; jj++) { old_room_number = _G(displayed_room); QueuedScript &script = copyof.ScFnQueue[jj]; RunScriptFunction(script.Instance, script.FnName, script.ParamCount, script.Param1, script.Param2); if (script.Instance == kScInstRoom && script.ParamCount == 1) { // some bogus hack for "on_call" event handler _GP(play).roomscript_finished = 1; } // if they've changed rooms, cancel any further pending scripts if ((_G(displayed_room) != old_room_number) || (_G(load_new_game))) break; } copyof.numanother = 0; } void quit_with_script_error(const char *functionName) { // TODO: clean up the error reporting logic. Now engine will append call // stack info in quit_check_for_error_state() but only in case of explicit // script error ("!" type), and not in other case. if (_G(ccErrorIsUserError)) quitprintf("!Error running function '%s':\n%s", functionName, _G(ccErrorString).GetCStr()); else quitprintf("Error running function '%s':\n%s\n\n%s", functionName, _G(ccErrorString).GetCStr(), get_cur_script(5).GetCStr()); } int get_nivalue(InteractionCommandList *nic, int idx, int parm) { if (nic->Cmds[idx].Data[parm].Type == AGS::Shared::kInterValVariable) { // return the value of the variable return get_interaction_variable(nic->Cmds[idx].Data[parm].Value)->Value; } return nic->Cmds[idx].Data[parm].Value; } InteractionVariable *get_interaction_variable(int varindx) { if ((varindx >= LOCAL_VARIABLE_OFFSET) && ((size_t)varindx < LOCAL_VARIABLE_OFFSET + _GP(thisroom).LocalVariables.size())) return &_GP(thisroom).LocalVariables[varindx - LOCAL_VARIABLE_OFFSET]; if ((varindx < 0) || (varindx >= _G(numGlobalVars))) quit("!invalid interaction variable specified"); return &_G(globalvars)[varindx]; } InteractionVariable *FindGraphicalVariable(const char *varName) { int ii; for (ii = 0; ii < _G(numGlobalVars); ii++) { if (ags_stricmp(_G(globalvars)[ii].Name, varName) == 0) return &_G(globalvars)[ii]; } for (size_t i = 0; i < _GP(thisroom).LocalVariables.size(); ++i) { if (ags_stricmp(_GP(thisroom).LocalVariables[i].Name, varName) == 0) return &_GP(thisroom).LocalVariables[i]; } return nullptr; } #define IPARAM1 get_nivalue(nicl, i, 0) #define IPARAM2 get_nivalue(nicl, i, 1) #define IPARAM3 get_nivalue(nicl, i, 2) #define IPARAM4 get_nivalue(nicl, i, 3) #define IPARAM5 get_nivalue(nicl, i, 4) struct TempEip { int oldval; TempEip(int newval) { oldval = _G(our_eip); _G(our_eip) = newval; } ~TempEip() { _G(our_eip) = oldval; } }; // the 'cmdsrun' parameter counts how many commands are run. // if a 'Inv Item Was Used' check does not pass, it doesn't count // so cmdsrun remains 0 if no inventory items matched int run_interaction_commandlist(InteractionCommandList *nicl, int *timesrun, int *cmdsrun) { size_t i; if (nicl == nullptr) return -1; for (i = 0; i < nicl->Cmds.size(); i++) { cmdsrun[0] ++; int room_was = _GP(play).room_changes; switch (nicl->Cmds[i].Type) { case 0: // Do nothing break; case 1: { // Run script TempEip tempip(4001); RuntimeScriptValue rval_null; if ((strstr(_G(evblockbasename), "character") != nullptr) || (strstr(_G(evblockbasename), "inventory") != nullptr)) { // Character or Inventory (global script) const char *torun = make_ts_func_name(_G(evblockbasename), _G(evblocknum), nicl->Cmds[i].Data[0].Value); // we are already inside the mouseclick event of the script, can't nest calls QueueScriptFunction(kScInstGame, torun); } else { // Other (room script) const char *torun = make_ts_func_name(_G(evblockbasename), _G(evblocknum), nicl->Cmds[i].Data[0].Value); QueueScriptFunction(kScInstRoom, torun); } break; } case 2: // Add score (first time) if (timesrun[0] > 0) break; timesrun[0]++; // fall through case 3: // Add score GiveScore(IPARAM1); break; case 4: // Display Message /* if (comprdata<0) _G(display_message_aschar)=evb->data[ss];*/ DisplayMessage(IPARAM1); break; case 5: // Play Music PlayMusicResetQueue(IPARAM1); break; case 6: // Stop Music stopmusic(); break; case 7: // Play Sound play_sound(IPARAM1); break; case 8: // Play Flic play_flc_file(IPARAM1, IPARAM2); break; case 9: { // Run Dialog int roomWas = _GP(play).room_changes; RunDialog(IPARAM1); // if they changed room within the dialog script, // the interaction command list is no longer valid if (roomWas != _GP(play).room_changes) return -1; } break; case 10: // Enable Dialog Option SetDialogOption(IPARAM1, IPARAM2, 1); break; case 11: // Disable Dialog Option SetDialogOption(IPARAM1, IPARAM2, 0); break; case 12: // Go To Screen Character_ChangeRoomAutoPosition(_G(playerchar), IPARAM1, IPARAM2); return -1; case 13: // Add Inventory add_inventory(IPARAM1); break; case 14: // Move Object MoveObject(IPARAM1, IPARAM2, IPARAM3, IPARAM4); // if they want to wait until finished, do so if (IPARAM5) GameLoopUntilNotMoving(&_G(objs)[IPARAM1].moving); break; case 15: // Object Off ObjectOff(IPARAM1); break; case 16: // Object On ObjectOn(IPARAM1); break; case 17: // Set Object View SetObjectView(IPARAM1, IPARAM2); break; case 18: // Animate Object AnimateObject(IPARAM1, IPARAM2, IPARAM3, IPARAM4); break; case 19: // Move Character if (IPARAM4) MoveCharacterBlocking(IPARAM1, IPARAM2, IPARAM3, 0); else MoveCharacter(IPARAM1, IPARAM2, IPARAM3); break; case 20: // If Inventory Item was used if (_GP(play).usedinv == IPARAM1) { if (_GP(game).options[OPT_NOLOSEINV] == 0) lose_inventory(_GP(play).usedinv); if (run_interaction_commandlist(nicl->Cmds[i].Children.get(), timesrun, cmdsrun)) return -1; } else cmdsrun[0] --; break; case 21: // if player has inventory item if (_G(playerchar)->inv[IPARAM1] > 0) if (run_interaction_commandlist(nicl->Cmds[i].Children.get(), timesrun, cmdsrun)) return -1; break; case 22: // if a character is moving if (_GP(game).chars[IPARAM1].walking) if (run_interaction_commandlist(nicl->Cmds[i].Children.get(), timesrun, cmdsrun)) return -1; break; case 23: // if two variables are equal if (IPARAM1 == IPARAM2) if (run_interaction_commandlist(nicl->Cmds[i].Children.get(), timesrun, cmdsrun)) return -1; break; case 24: // Stop character walking StopMoving(IPARAM1); break; case 25: // Go to screen at specific co-ordinates NewRoomEx(IPARAM1, IPARAM2, IPARAM3); return -1; case 26: // Move NPC to different room if (!is_valid_character(IPARAM1)) quit("!Move NPC to different room: invalid character specified"); _GP(game).chars[IPARAM1].room = IPARAM2; break; case 27: // Set character view SetCharacterView(IPARAM1, IPARAM2); break; case 28: // Release character view ReleaseCharacterView(IPARAM1); break; case 29: // Follow character FollowCharacter(IPARAM1, IPARAM2); break; case 30: // Stop following FollowCharacter(IPARAM1, -1); break; case 31: // Disable hotspot DisableHotspot(IPARAM1); break; case 32: // Enable hotspot EnableHotspot(IPARAM1); break; case 33: // Set variable value get_interaction_variable(nicl->Cmds[i].Data[0].Value)->Value = IPARAM2; break; case 34: // Run animation scAnimateCharacter(IPARAM1, IPARAM2, IPARAM3, 0); GameLoopUntilValueIsZero(&_GP(game).chars[IPARAM1].animating); break; case 35: // Quick animation SetCharacterView(IPARAM1, IPARAM2); scAnimateCharacter(IPARAM1, IPARAM3, IPARAM4, 0); GameLoopUntilValueIsZero(&_GP(game).chars[IPARAM1].animating); ReleaseCharacterView(IPARAM1); break; case 36: // Set idle animation SetCharacterIdle(IPARAM1, IPARAM2, IPARAM3); break; case 37: // Disable idle animation SetCharacterIdle(IPARAM1, -1, -1); break; case 38: // Lose inventory item lose_inventory(IPARAM1); break; case 39: // Show GUI InterfaceOn(IPARAM1); break; case 40: // Hide GUI InterfaceOff(IPARAM1); break; case 41: // Stop running more commands return -1; case 42: // Face location FaceLocation(IPARAM1, IPARAM2, IPARAM3); break; case 43: // Pause command processor scrWait(IPARAM1); break; case 44: // Change character view ChangeCharacterView(IPARAM1, IPARAM2); break; case 45: // If player character is if (GetPlayerCharacter() == IPARAM1) if (run_interaction_commandlist(nicl->Cmds[i].Children.get(), timesrun, cmdsrun)) return -1; break; case 46: // if cursor mode is if (GetCursorMode() == IPARAM1) if (run_interaction_commandlist(nicl->Cmds[i].Children.get(), timesrun, cmdsrun)) return -1; break; case 47: // if player has been to room if (HasBeenToRoom(IPARAM1)) if (run_interaction_commandlist(nicl->Cmds[i].Children.get(), timesrun, cmdsrun)) return -1; break; default: quit("unknown new interaction command"); break; } if (_G(abort_engine)) return -1; // if the room changed within the action, nicl is no longer valid if (room_was != _GP(play).room_changes) return -1; } return 0; } // check and abort game if the script is currently // inside the rep_exec_always function void can_run_delayed_command() { if (_G(no_blocking_functions)) quit("!This command cannot be used within non-blocking events such as " REP_EXEC_ALWAYS_NAME); } void run_unhandled_event(int evnt) { if (_GP(play).check_interaction_only) return; int evtype = 0; if (ags_strnicmp(_G(evblockbasename), "hotspot", 7) == 0) evtype = 1; else if (ags_strnicmp(_G(evblockbasename), "object", 6) == 0) evtype = 2; else if (ags_strnicmp(_G(evblockbasename), "character", 9) == 0) evtype = 3; else if (ags_strnicmp(_G(evblockbasename), "inventory", 9) == 0) evtype = 5; else if (ags_strnicmp(_G(evblockbasename), "region", 6) == 0) return; // no unhandled_events for regions // clicked Hotspot 0, so change the type code if ((evtype == 1) & (_G(evblocknum) == 0) & (evnt != 0) & (evnt != 5) & (evnt != 6)) evtype = 4; if ((evtype == 1) & ((evnt == 0) | (evnt == 5) | (evnt == 6))) ; // character stands on hotspot, mouse moves over hotspot, any click else if ((evtype == 2) & (evnt == 4)); // any click on object else if ((evtype == 3) & (evnt == 4)); // any click on character else if (evtype > 0) { can_run_delayed_command(); QueueScriptFunction(kScInstGame, "unhandled_event", 2, RuntimeScriptValue().SetInt32(evtype), RuntimeScriptValue().SetInt32(evnt)); } } } // namespace AGS3