2021-04-30 20:35:09 -07:00

884 lines
28 KiB
C++

/* 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 &param2) {
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