diff --git a/code/REDALERT/MapScript.cpp b/code/REDALERT/MapScript.cpp index 8c56eb5..da94f84 100644 --- a/code/REDALERT/MapScript.cpp +++ b/code/REDALERT/MapScript.cpp @@ -72,6 +72,59 @@ static int Script_NumBuildingTypeForPlayer(lua_State* L) { return 1; } +/*********************************************************************************************** + * Script_SetTriggerCallback - Adds a lua callback to an existing trigger * + * * + * SCRIPT INPUT: triggerName (string) - The ININame of the trigger via its class * + * callbackName (string) - The function name to be called when the actions * + * have been met * + * actionIndex (int) (optional) - 0 (default): always callback on any of the * + * given paths of the trigger * + * 1: only callback on first action * + * 2: only callback on second action * + * * + * SCRIPT OUTPUT: result (number) - The amount of matching buildings for that player * + * * + * INPUT: lua_State - The current Lua state * + * * + * OUTPUT: int; Did the function run successfully? Return 1 * + * * + * WARNINGS: ? * + * * + *=============================================================================================*/ +static int Script_SetTriggerCallback(lua_State* L) { + + const char* triggerName = lua_tostring(L, 1); + const char* callbackName = lua_tostring(L, 2); + char actionIndex = 0; + + // Optional actionIndex parameter + if (lua_gettop(L) == 3) { + actionIndex = lua_tointeger(L, 2); + } + + // Find the trigger and set up callback + for (int t_index = 0; t_index < Triggers.Count(); t_index++) { + TriggerClass* trigger = Triggers.Ptr(t_index); + + if (trigger != NULL) { + + if (strcmp(trigger->Name(), triggerName) == 0) { + + strncpy(trigger->MapScriptCallback,callbackName,sizeof(trigger->MapScriptCallback ) - 1); + trigger->MapScriptActionIndex = actionIndex; + + break; + } + + } + + } + + return 1; +} + + /*********************************************************************************************** * Script_Win - The specified player wins * @@ -779,11 +832,47 @@ static int Script_SetBriefingText(lua_State* L) { * * *=============================================================================================*/ void MapScript::CallFunction(const char* functionName) { + lua_getglobal(L, functionName); lua_pcall(L, 0, 0, 0); + } //BriefingText +/*********************************************************************************************** + * MapScript::setLuaPath - Sets Lua's path / extensions to search for scripts * + * * + * This helps Lua know where/how to look for files when using require(), otherwise we could * + * hard code path/extension as we did previously * + * * + * INPUT: pathvalue - See lua documentation RE: LUA_PATH to know how to deal with this * + * * + * OUTPUT: void * + * * + * WARNINGS: ? * + * * + *=============================================================================================*/ +void MapScript::SetLuaPath( const char* input_path) +{ + + // Needs space to keep the initial paths + char new_path[1024]; + + // Set new GLOBALS.package.path + lua_getglobal(L, "package"); + lua_getfield(L, -1, "path"); // get field "path" from table at top of stack (-1) + const char* cur_path = lua_tostring(L, -1); // grab path string from top of stack + + sprintf(new_path, "%s;%s", cur_path, input_path); + + lua_pop(L, 1); + lua_pushstring(L, new_path); + lua_setfield(L, -2, "path"); + lua_pop(L, 1); +} + + + /*********************************************************************************************** * MapScript::Init -- Loads and initializes script for a given map * * * @@ -798,14 +887,35 @@ void MapScript::CallFunction(const char* functionName) { *=============================================================================================*/ bool MapScript::Init(const char* mapName) { char scriptName[256]; + + + sprintf(scriptName, "scripts/%s.script", mapName); L = luaL_newstate(); - if (luaL_loadfile(L, scriptName) || lua_pcall(L, 0, 0, 0)) { + luaL_openlibs(L); + + if (luaL_loadfile(L, scriptName)){ L = NULL; return false; } + // TODO: I've tried several combinations of paths (including "scripts/?"), + // but I can only get "require 'basefilename'" to work within a + // script, not "require 'basefilename.script'". This may be completely + // normal within lua, but it seems odd. + + // This allows require() to work + SetLuaPath(";scripts/?.script;"); + + + if (lua_pcall(L, 0, 0, 0)) { + L = NULL; + return false; + } + + + /********************************************************************************************** * Red Alert Vanilla Actions * *=============================================================================================*/ @@ -883,6 +993,8 @@ bool MapScript::Init(const char* mapName) { lua_register(L, "GiveCreditsToPlayer", Script_GiveCreditsToPlayer); // Give [player] [credits] lua_register(L, "NumBuildingTypeForPlayer", Script_NumBuildingTypeForPlayer); // Number of buildings of [type] for given [player] + lua_register(L, "SetTriggerCallback", Script_SetTriggerCallback); // Initiates a given [callback] on an existing [trigger] + /********************************************************************************************** * Script Globals * diff --git a/code/REDALERT/MapScript.h b/code/REDALERT/MapScript.h index b12da67..b02c72d 100644 --- a/code/REDALERT/MapScript.h +++ b/code/REDALERT/MapScript.h @@ -1,6 +1,9 @@ // MapScript.h // +#ifndef MAPSCRIPT_H +#define MAPSCRIPT_H + // Lua is written in C, so compiler needs to know how to link its libraries extern "C" { # include "lua.h" @@ -15,6 +18,8 @@ class MapScript { public: bool Init(const char* mapName); void CallFunction(const char* functionName); + void SetLuaPath(const char* input_path); private: lua_State* L; }; +#endif \ No newline at end of file diff --git a/code/REDALERT/SCENARIO.CPP b/code/REDALERT/SCENARIO.CPP index c5d0878..8605672 100644 --- a/code/REDALERT/SCENARIO.CPP +++ b/code/REDALERT/SCENARIO.CPP @@ -60,7 +60,6 @@ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "function.h" -#include "MapScript.h" #ifdef WIN32 #include "tcpip.h" #include "ccdde.h" @@ -111,7 +110,6 @@ bool Is_Mission_Counterstrike (char *file_name); bool Is_Mission_Aftermath (char *file_name); #endif -MapScript* mapScript = nullptr; bool mapScriptPlayStart = false; void Cmd_Map(void); @@ -332,13 +330,13 @@ bool Start_Scenario(char * name, bool briefing) } // jmarshall: map scripts - if (mapScript) { - delete mapScript; + if (Scen.mapScript) { + delete Scen.mapScript; } - mapScript = new MapScript(); - if (!mapScript->Init(name)) { - delete mapScript; - mapScript = nullptr; + Scen.mapScript = new MapScript(); + if (!Scen.mapScript->Init(name)) { + delete Scen.mapScript; + Scen.mapScript = nullptr; } else { mapScriptPlayStart = true; @@ -379,11 +377,11 @@ bool Start_Scenario(char * name, bool briefing) // } Theme.Stop(); // jmarshall - map scripts - if (mapScript) { + if (Scen.mapScript) { Hide_Mouse(); VisiblePage.Clear(); Show_Mouse(); - mapScript->CallFunction("map_briefing"); + Scen.mapScript->CallFunction("map_briefing"); } else if (briefing) { Hide_Mouse(); @@ -430,16 +428,16 @@ bool Start_Scenario(char * name, bool briefing) } void Scenario_MapScriptFrame(void) { - if (!mapScript) + if (!Scen.mapScript) return; if (mapScriptPlayStart) { - mapScript->CallFunction("map_start"); + Scen.mapScript->CallFunction("map_start"); mapScriptPlayStart = false; return; } - mapScript->CallFunction("map_frame"); + Scen.mapScript->CallFunction("map_frame"); } diff --git a/code/REDALERT/SCENARIO.H b/code/REDALERT/SCENARIO.H index 10eb9fc..6012b64 100644 --- a/code/REDALERT/SCENARIO.H +++ b/code/REDALERT/SCENARIO.H @@ -36,6 +36,8 @@ #ifndef SCENARIO_H #define SCENARIO_H +#include "MapScript.h" + /* ** This class holds the information about the current game being played. This information is @@ -328,6 +330,12 @@ class ScenarioClass { #endif bool warpIntoMap; + + + /* + ** MapScript storage, to be accessed by triggers etc. + */ + MapScript* mapScript = nullptr; }; diff --git a/code/REDALERT/TRIGGER.CPP b/code/REDALERT/TRIGGER.CPP index 57dabfe..c7c9dcb 100644 --- a/code/REDALERT/TRIGGER.CPP +++ b/code/REDALERT/TRIGGER.CPP @@ -45,6 +45,7 @@ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "function.h" +#include "MapScript.h" #if defined(CHEAT_KEYS) || defined(SCENARIO_EDITOR) @@ -228,6 +229,7 @@ bool TriggerClass::Spring(TEventType event, ObjectClass * obj, CELL cell, bool f bool e1 = Class->Event1(Event1, event, Class->House, obj, forced); bool e2 = false; bool execute = false; + bool execute_mapScript = false; /* ** Forced triggers must presume that the cell parameter is invalid. It then @@ -295,6 +297,13 @@ bool TriggerClass::Spring(TEventType event, ObjectClass * obj, CELL cell, bool f } } + /* + ** We need to execute this trigger, so is there an attached MapScript callback? + */ + if (Scen.mapScript != NULL && strlen(MapScriptCallback) > 0) { + execute_mapScript = true; + } + /* ** For linked trigger events, perform the action associated with the matching ** trigger event. Otherwise perform the action for both events. @@ -302,19 +311,45 @@ bool TriggerClass::Spring(TEventType event, ObjectClass * obj, CELL cell, bool f bool ok = false; HousesType hh = Class->House; if (Class->EventControl == MULTI_LINKED) { - if (e1 || forced) ok |= Class->Action1(hh, obj, ID, cell); - if (e2 && !forced) ok |= Class->Action2(hh, obj, ID, cell); + if (e1 || forced) { + ok |= Class->Action1(hh, obj, ID, cell); + + // Execute MapScript Callback for action 1? + if (ok && execute_mapScript && MapScriptActionIndex <= 1) { + Scen.mapScript->CallFunction(MapScriptCallback); + } + } + if (e2 && !forced) { + ok |= Class->Action2(hh, obj, ID, cell); + + // Execute MapScript Callback for action 2? + if (ok && execute_mapScript && MapScriptActionIndex != 1) { + Scen.mapScript->CallFunction(MapScriptCallback); + } + } } else { switch (Class->ActionControl) { case MULTI_ONLY: ok |= Class->Action1(hh, obj, ID, cell); + + // Execute MapScript Callback for action 1? + if (ok && execute_mapScript && MapScriptActionIndex <= 1) { + Scen.mapScript->CallFunction(MapScriptCallback); + } + break; default: case MULTI_AND: ok |= Class->Action1(hh, obj, ID, cell); ok |= Class->Action2(hh, obj, ID, cell); + + // Execute MapScript Callback for action? + if (ok && execute_mapScript) { + Scen.mapScript->CallFunction(MapScriptCallback); + } + break; } } diff --git a/code/REDALERT/TRIGGER.H b/code/REDALERT/TRIGGER.H index cb0d772..7259f3d 100644 --- a/code/REDALERT/TRIGGER.H +++ b/code/REDALERT/TRIGGER.H @@ -115,6 +115,12 @@ class TriggerClass { ** Some additional padding in case we need to add data to the class and maintain backwards compatibility for save/load */ unsigned char SaveLoadPadding[32]; + + /* + ** Store MapScript callback function and optional action index (because there can be multiple outcomes) -- JJ 6/14/2020 + */ + char MapScriptCallback[32]; + char MapScriptActionIndex; };