From f1cb462b89c45b5fe87187cc67b583d1e3ff07f4 Mon Sep 17 00:00:00 2001 From: nev6502 Date: Sat, 20 Jun 2020 14:18:19 -0600 Subject: [PATCH] Heavy work done to scripting engine --- code/REDALERT/MapScript.cpp | 559 ++++++++++++++++++++++++++++++++++-- code/REDALERT/MapScript.h | 33 ++- 2 files changed, 569 insertions(+), 23 deletions(-) diff --git a/code/REDALERT/MapScript.cpp b/code/REDALERT/MapScript.cpp index 421f265..b923453 100644 --- a/code/REDALERT/MapScript.cpp +++ b/code/REDALERT/MapScript.cpp @@ -4,6 +4,8 @@ #include "FUNCTION.H" #include "MapScript.h" + + /*********************************************************************************************** * Script_GiveCredits - Gives or takes a given amount of credits to / from a player * * * @@ -96,11 +98,12 @@ static int Script_GetCredits(lua_State* L) { return 1; } + /*********************************************************************************************** - * Script_GetCellBuilding - Gets a building (if any) on a cell and returns the ID * + * Script_GetCellObject - Gets an object (if any) on a cell and returns the ID * * * - * SCRIPT INPUT: in_x (int) - The cell/X location of the cell to test * - * in_y (int) - The cell/Y location of the cell to test * + * SCRIPT INPUT: in_x (int) - The cell/X location of the cell to pick from * + * in_y (int) - The cell/Y location of the cell to pick from * * * * SCRIPT OUTPUT: result (number) - The ID of the building on the cell, or -1 if none * * * @@ -111,24 +114,211 @@ static int Script_GetCredits(lua_State* L) { * WARNINGS: ? * * * *=============================================================================================*/ -static int Script_GetCellBuilding(lua_State* L) { +static int Script_GetCellObject(lua_State* L) { int in_x = lua_tointeger(L, 1); int in_y = lua_tointeger(L, 2); long result = -1; + + ObjectClass* this_object = Map[XY_Cell(in_x, in_y)].Cell_Object(); - BuildingClass* this_building = Map[XY_Cell(in_x, in_y)].Cell_Building(); - - if (this_building != NULL) { - result = this_building->ID; + if (this_object == NULL) { + + lua_pushnumber(L, -1); + return 1; + } + + MapScriptObject* CacheObject = new MapScriptObject; + + CacheObject->ID = this_object->ID; + CacheObject->RTTI = this_object->RTTI; + CacheObject->classIndex = -1; + + switch (this_object->RTTI) { + case RTTI_BUILDING: { + CacheObject->classIndex = Script_BuildingIndexFromID(this_object->ID); + }break; + case RTTI_UNIT: { + CacheObject->classIndex = Script_UnitIndexFromID(this_object->ID); + }break; + case RTTI_AIRCRAFT: { + CacheObject->classIndex = Script_AircraftIndexFromID(this_object->ID); + }break; + case RTTI_INFANTRY: { + CacheObject->classIndex = Script_InfantryIndexFromID(this_object->ID); + }break; + case RTTI_VESSEL: { + CacheObject->classIndex = Script_VesselIndexFromID(this_object->ID); + }break; + } + + if (CacheObject->classIndex == -1 || CacheObject->ID < 0) { + delete CacheObject; + lua_pushnumber(L, -1); + }else { + Scen.mapScript->ObjectCache.push_back(CacheObject); + lua_pushnumber(L, CacheObject->ID); // Return index for ultrafast lookup } - - lua_pushnumber(L, result); return 1; } + +/*********************************************************************************************** + * MapScript::GetCacheObject -- Returns ObjectClass from cache with given ID * + * * + * INPUT: input_object_id (int) - Input object ID * + * * + * OUTPUT: this_object (ObjectClass*) - The object with the ID or NULL * + * * + * WARNINGS: none * + * * + *=============================================================================================*/ +ObjectClass* Script_GetCacheObject(int input_object_id) { + + if (input_object_id < 0 ) { + return false; + } + + // Look through cache for object with ID, and return it + for (std::vector::iterator it = Scen.mapScript->ObjectCache.begin(); it != Scen.mapScript->ObjectCache.end(); ++it) {//Error 2-4 + + MapScriptObject* thisCacheObject = (MapScriptObject*)(*it); + + if (thisCacheObject->ID == input_object_id) { + + // Determine type of object and return it from cache. If it doesn't exist any longer, remove it from cache + switch (thisCacheObject->RTTI) { + case RTTI_BUILDING: { + BuildingClass *this_building = Buildings.Ptr(thisCacheObject->classIndex); + if (this_building != NULL) { + return this_building; + } else { + it = Scen.mapScript->ObjectCache.erase(it); // Cache item exists but is invalidated; remove from cache + return NULL; + } + }break; + case RTTI_UNIT: { + UnitClass* this_unit = Units.Ptr(thisCacheObject->classIndex); + if (this_unit != NULL) { + return this_unit; + } else { + it = Scen.mapScript->ObjectCache.erase(it); // Cache item exists but is invalidated; remove from cache + return NULL; + } + }break; + case RTTI_AIRCRAFT: { + AircraftClass* this_aircraft = Aircraft.Ptr(thisCacheObject->classIndex); + if (this_aircraft != NULL) { + return this_aircraft; + } else { + it = Scen.mapScript->ObjectCache.erase(it); // Cache item exists but is invalidated; remove from cache + return NULL; + } + }break; + case RTTI_INFANTRY: { + InfantryClass* this_infantry = Infantry.Ptr(thisCacheObject->classIndex); + if (this_infantry != NULL) { + return this_infantry; + } else { + it = Scen.mapScript->ObjectCache.erase(it); // Cache item exists but is invalidated; remove from cache + return NULL; + } + }break; + case RTTI_VESSEL: { + VesselClass* this_vessel = Vessels.Ptr(thisCacheObject->classIndex); + if (this_vessel != NULL) { + return this_vessel; + } else { + it = Scen.mapScript->ObjectCache.erase(it); // Cache item exists but is invalidated; remove from cache + return NULL; + } + }break; + } + + } + + } + + // Not found in cache; let's find the object by ID the hard way... If scripting is done well, this doesn't get hit + int _objectIndex = -1; + MapScriptObject* CacheObject = new MapScriptObject; + CacheObject->ID = -1; + + _objectIndex = Script_BuildingIndexFromID(input_object_id); + BuildingClass* this_building = Buildings.Ptr(_objectIndex); + if (this_building != NULL) { + + CacheObject->ID = this_building->ID; + CacheObject->RTTI = this_building->RTTI; + CacheObject->classIndex = _objectIndex; + Scen.mapScript->ObjectCache.push_back(CacheObject); + + return this_building; + + }else { + _objectIndex = Script_UnitIndexFromID(input_object_id); + UnitClass* this_unit = Units.Ptr(_objectIndex); + if (this_unit != NULL) { + + CacheObject->ID = this_unit->ID; + CacheObject->RTTI = this_unit->RTTI; + CacheObject->classIndex = _objectIndex; + Scen.mapScript->ObjectCache.push_back(CacheObject); + + return this_unit; + + }else { + _objectIndex = Script_AircraftIndexFromID(input_object_id); + AircraftClass* this_aircraft = Aircraft.Ptr(_objectIndex); + if (this_aircraft != NULL) { + + CacheObject->ID = this_aircraft->ID; + CacheObject->RTTI = this_aircraft->RTTI; + CacheObject->classIndex = _objectIndex; + Scen.mapScript->ObjectCache.push_back(CacheObject); + + return this_aircraft; + + }else { + _objectIndex = Script_VesselIndexFromID(input_object_id); + VesselClass* this_vessel = Vessels.Ptr(_objectIndex); + if (this_vessel != NULL) { + + CacheObject->ID = this_vessel->ID; + CacheObject->RTTI = this_vessel->RTTI; + CacheObject->classIndex = _objectIndex; + Scen.mapScript->ObjectCache.push_back(CacheObject); + + return this_vessel; + + }else { + _objectIndex = Script_InfantryIndexFromID(input_object_id); + InfantryClass* this_infantry = Infantry.Ptr(_objectIndex); + if (this_infantry != NULL) { + + CacheObject->ID = this_infantry->ID; + CacheObject->RTTI = this_infantry->RTTI; + CacheObject->classIndex = _objectIndex; + Scen.mapScript->ObjectCache.push_back(CacheObject); + + return this_infantry; + + } + } + } + } + } + + // Looks like we didn't find anything. They probably shouldn't have gotten this far in the first place so it was worth a try. + delete CacheObject; + + return NULL; +} + + + /*********************************************************************************************** * Script_CountBuildings - Returns the amount of specific buildings for a player * * * @@ -1354,7 +1544,9 @@ static int Script_StopMissionTimer(lua_State* L) { * WARNINGS: ? * * * *=============================================================================================*/ -static int Script_CreateCellCallback(lua_State* L) { +static int Script_CellCallback(lua_State* L) { + + // TODO: option 6 arguments to allow x2,y2 (for a rect) int houseType = lua_tointeger(L, 1); int in_x = lua_tointeger(L, 2); @@ -1399,7 +1591,7 @@ static int Script_CreateCellCallback(lua_State* L) { /*********************************************************************************************** * Script_CreateSpiedByCallback - Creates a SPIED_BY trigger and attaches a callback * * * - * SCRIPT INPUT: buildingID (int) - The building ID being spied * + * SCRIPT INPUT: objectID (int) - The building ID being spied * * houseType (int) - The house (player) index doing the spying * * in_function (string) - The callback function to execute upon trigger * * * @@ -1412,13 +1604,13 @@ static int Script_CreateCellCallback(lua_State* L) { * WARNINGS: ? * * * *=============================================================================================*/ -static int Script_CreateSpiedByCallback(lua_State* L) { +static int Script_SpiedByCallback(lua_State* L) { - int buildingID = lua_tointeger(L, 1); - int houseType = lua_tointeger(L, 1); + int objectID = lua_tointeger(L, 1); + int houseType = lua_tointeger(L, 2); const char* in_function = lua_tostring(L, 3); - BuildingClass* this_building = Buildings.Ptr(buildingID); + BuildingClass* this_building = Buildings.Ptr(objectID); // A house must be specified if (houseType >= 0 && houseType < HouseTypes.Count() && this_building != NULL) { @@ -1455,6 +1647,120 @@ static int Script_CreateSpiedByCallback(lua_State* L) { } +/*********************************************************************************************** + * Script_CreateDiscoveryCallback - Creates a TEVENT_DISCOVERED trigger and attaches a callback* + * * + * SCRIPT INPUT: objectID (int) - The object's ID being discovered * + * in_function (string) - The callback function to execute upon trigger * + * * + * SCRIPT OUTPUT: triggerID - The ID of the trigger created for use with Trigger Functions * + * * + * INPUT: lua_State - The current Lua state * + * * + * OUTPUT: int; Did the function run successfully? Return 1 * + * * + * WARNINGS: ? * + * * + *=============================================================================================*/ +static int Script_DiscoveryCallback(lua_State* L) { + + int objectID = lua_tointeger(L, 1); + const char* in_function = lua_tostring(L, 2); + + // A house must be specified + if (objectID >= 0 ) { + + // Create trigger type + TriggerTypeClass* this_trigger_type = new TriggerTypeClass(); + this_trigger_type->House = HousesType::HOUSE_NONE; + this_trigger_type->IsActive = 1; + this_trigger_type->IsPersistant = TriggerTypeClass::PERSISTANT; + + this_trigger_type->Event1 = TEVENT_DISCOVERED; + this_trigger_type->EventControl = MULTI_ONLY; + + // Create instance of trigger type + TriggerClass* this_trigger = Find_Or_Make(this_trigger_type); + + // Copy the callback function into the trigger + strncpy(this_trigger->MapScriptCallback, in_function, sizeof(this_trigger->MapScriptCallback) - 1); + + Script_SetObjectTrigger(objectID, this_trigger); + + + + // Output the new trigger's ID + lua_pushnumber(L, this_trigger->ID); + + return 1; + + } + + lua_pushnumber(L, -1); + + return 1; +} + + +/*********************************************************************************************** + * Script_HouseDiscoveredCallback - Initiates when given house is discovered * + * * + * SCRIPT INPUT: houseType (int) - The house (player) index that has been discovered * + * in_function (string) - The callback function to execute upon trigger * + * * + * SCRIPT OUTPUT: triggerID - The ID of the trigger created for use with Trigger Functions * + * * + * INPUT: lua_State - The current Lua state * + * * + * OUTPUT: int; Did the function run successfully? Return 1 * + * * + * WARNINGS: ? * + * * + *=============================================================================================*/ +static int Script_HouseDiscoveredCallback(lua_State* L) { + + int houseType = lua_tointeger(L, 1); + const char* in_function = lua_tostring(L, 2); + + // A house must be specified + if (houseType >= 0 && houseType < HouseTypes.Count()) { + + // Create trigger type + TriggerTypeClass* this_trigger_type = new TriggerTypeClass(); + this_trigger_type->House = PlayerPtr->Class->House; + this_trigger_type->IsActive = 1; + this_trigger_type->IsPersistant = TriggerTypeClass::VOLATILE; + + this_trigger_type->Event1 = TEVENT_HOUSE_DISCOVERED; + this_trigger_type->EventControl = MULTI_ONLY; + + this_trigger_type->Event1.Data.House = (HousesType)houseType; + + // Create instance of trigger type + + TriggerClass* editor_trigger = Triggers.Ptr(0); + TriggerTypeClass* editor_trigger_type = editor_trigger->Class; + TriggerClass* this_trigger = Find_Or_Make(this_trigger_type); + + // Copy the callback function into the trigger + strncpy(this_trigger->MapScriptCallback, in_function, sizeof(this_trigger->MapScriptCallback) - 1); + + HouseTriggers[PlayerPtr->Class->House].Add(this_trigger); + + // Output the new trigger's ID + lua_pushnumber(L, this_trigger->ID); + + return 1; + + } + + lua_pushnumber(L, -1); + + return 1; +} + + + /*********************************************************************************************** * Script_Script_TriggerAddCell - Adds a cell to any cell based callback * * * @@ -1518,6 +1824,190 @@ static int Script_SetBriefingText(lua_State* L) { return 1; } +/*********************************************************************************************** + * Script_SetObjectTrigger -- Figures out what kind of object is given and sets up trigger * + * * + * INPUT: input_object (ObjectClass*) - Input object * + * input_trigger (TriggerClass*) - Input Trigger * + * * + * OUTPUT: bool; Was the object found? Does the trigger exist? * + * * + * WARNINGS: none * + * * + *=============================================================================================*/ +bool Script_SetObjectTrigger(int input_object_id, TriggerClass* input_trigger) { + + if (input_object_id < 0) { + return false; + } + + if (input_trigger == NULL) { + return false; + } + + ObjectClass* this_object = Script_GetCacheObject(input_object_id); + + if (this_object != NULL) { + + switch (this_object->RTTI) { + case RTTI_BUILDING:{ + input_trigger->Class->Event1.Data.Structure = dynamic_cast(this_object)->Class->Type; + dynamic_cast(this_object)->Trigger = input_trigger; + }break; + case RTTI_UNIT:{ + input_trigger->Class->Event1.Data.Unit = dynamic_cast(this_object)->Class->Type; + dynamic_cast(this_object)->Trigger = input_trigger; + }break; + case RTTI_AIRCRAFT:{ + input_trigger->Class->Event1.Data.Aircraft = dynamic_cast(this_object)->Class->Type; + dynamic_cast(this_object)->Trigger = input_trigger; + }break; + case RTTI_INFANTRY:{ + input_trigger->Class->Event1.Data.Infantry = dynamic_cast(this_object)->Class->Type; + dynamic_cast(this_object)->Trigger = input_trigger; + }break; + } + + } + + return false; +} + +/*********************************************************************************************** + * Script_BuildingIndexFromID -- Returns BuildingClass or NULL depending on the input * + * * + * INPUT: input_object_id (int) - Input object ID * + * * + * OUTPUT: bool; Was the object found? Does the trigger exist? * + * * + * WARNINGS: none * + * * + *=============================================================================================*/ +int Script_BuildingIndexFromID(int input_object_id) { + + if (input_object_id < 0) { + return false; + } + + for (int _index = 0; _index < Buildings.Count(); _index++) { + BuildingClass* _object = Buildings.Ptr(_index); + + if (_object->ID == input_object_id) { + return _index; + } + } + + return -1; +} + +/*********************************************************************************************** + * Script_UnitIndexFromID -- Returns UnitClass* or NULL depending on the input * + * * + * INPUT: input_unit_id (int) - Input Unit ID * + * * + * OUTPUT: bool; Was the object found? Does the trigger exist? * + * * + * WARNINGS: none * + * * + *=============================================================================================*/ +int Script_UnitIndexFromID(int input_object_id) { + + if (input_object_id < 0) { + return false; + } + + for (int _index = 0; _index < Units.Count(); _index++) { + UnitClass* _object = Units.Ptr(_index); + + if (_object->ID == input_object_id) { + return _index; + } + } + + return -1; +} + +/*********************************************************************************************** + * Script_AircraftIndexFromID -- Returns AircraftClass* or NULL depending on the input * + * * + * INPUT: input_object_id (int) - Input object ID * + * * + * OUTPUT: bool; Was the object found? Does the trigger exist? * + * * + * WARNINGS: none * + * * + *=============================================================================================*/ +int Script_AircraftIndexFromID(int input_object_id) { + + if (input_object_id < 0) { + return false; + } + + for (int _index = 0; _index < Aircraft.Count(); _index++) { + AircraftClass* _object = Aircraft.Ptr(_index); + + if (_object->ID == input_object_id) { + return _index; + } + } + + return -1; +} + +/*********************************************************************************************** + * Script_InfantryIndexFromID -- Returns InfantryClass* or NULL depending on the input * + * * + * INPUT: input_unit_id (int) - Input Infantry ID * + * * + * OUTPUT: bool; Was the object found? Does the trigger exist? * + * * + * WARNINGS: none * + * * + *=============================================================================================*/ +int Script_InfantryIndexFromID(int input_object_id) { + + if (input_object_id < 0) { + return false; + } + + for (int _index = 0; _index < Infantry.Count(); _index++) { + InfantryClass* _object = Infantry.Ptr(_index); + + if (_object->ID == input_object_id) { + return _index; + } + } + + return -1; +} + +/*********************************************************************************************** + * Script_VesselIndexFromID -- Returns VesselClass* or NULL depending on the input * + * * + * INPUT: input_unit_id (int) - Input vessel ID * + * * + * OUTPUT: bool; Was the object found? Does the trigger exist? * + * * + * WARNINGS: none * + * * + *=============================================================================================*/ +int Script_VesselIndexFromID(int input_object_id) { + + if (input_object_id < 0) { + return false; + } + + for (int _index = 0; _index < Vessels.Count(); _index++) { + VesselClass* _object = Vessels.Ptr(_index); + + if (_object->ID == input_object_id) { + return _index; + } + } + + return -1; +} + /*********************************************************************************************** * MapScript::CallFunction - Calls a given function within the current script * @@ -1611,6 +2101,9 @@ bool MapScript::Init(const char* mapName) { return false; } + // Reserve object cache memory + ObjectCache.reserve(2000); + /********************************************************************************************** * Red Alert Vanilla Actions * *=============================================================================================*/ @@ -1646,11 +2139,12 @@ bool MapScript::Init(const char* mapName) { * Red Alert Vanilla Events * *=============================================================================================*/ - lua_register(L, "CreateCellCallback", Script_CreateCellCallback); // player enters this square - lua_register(L, "CreateSpiedByCallback", Script_CreateSpiedByCallback); // Spied by. -// Thieved by (raided or stolen vehicle). -// player discovers this object -// House has been discovered. + lua_register(L, "CellCallback", Script_CellCallback); // player enters this square (or group of squares) + lua_register(L, "SpiedByCallback", Script_SpiedByCallback); // Spied by. + // Thieved by (raided or stolen vehicle). // This doesn't appear to be implemented in the engine (?) + lua_register(L, "ObjectDiscoveryCallback", Script_DiscoveryCallback); // player discovers this object + lua_register(L, "DiscoveryCallback", Script_DiscoveryCallback); // player discovers this object + lua_register(L, "HouseDiscoveredCallback", Script_HouseDiscoveredCallback); // House has been discovered. // player attacks this object // player destroys this object // Any object event will cause the trigger. @@ -1689,7 +2183,8 @@ bool MapScript::Init(const char* mapName) { lua_register(L, "SetBriefingText", Script_SetBriefingText); // Sets the [text] on the mission briefing screen lua_register(L, "GiveCredits", Script_GiveCredits); // Give [credits] to [player] lua_register(L, "GetCredits", Script_GetCredits); // Get [player]'s [credits] - lua_register(L, "GetCellBuilding", Script_GetCellBuilding); // Gets a building, if any, at [Cell/X],[Cell/Y] + + lua_register(L, "GetCellObject", Script_GetCellObject); // Gets an object (ID - building/vehicle/aircraft/infantry/vessel), if any, at [Cell/X],[Cell/Y] lua_register(L, "CountBuildings", Script_CountBuildings); // Number of buildings of [type] for given [player] lua_register(L, "CountAircraft", Script_CountAircraft); // Number of units of [type] for given [player] @@ -1715,3 +2210,23 @@ bool MapScript::Init(const char* mapName) { return true; } +/*********************************************************************************************** + * MapScript::Deinit - Deinitialize/free any data used by MapScript (used within destructor) * + * * + * SCRIPT INPUT: none * + * * + * SCRIPT OUTPUT: void * + * * + * WARNINGS: ? * + * * + *=============================================================================================*/ +void MapScript::Deinit() { + + for (int i = 0; i < ObjectCache.size(); ++i) + { + delete ObjectCache[i]; + } + + ObjectCache.clear(); + +} \ No newline at end of file diff --git a/code/REDALERT/MapScript.h b/code/REDALERT/MapScript.h index b02c72d..b3bf2d4 100644 --- a/code/REDALERT/MapScript.h +++ b/code/REDALERT/MapScript.h @@ -11,15 +11,46 @@ extern "C" { # include "lualib.h" } + +// Object Cache Item +// Stores an id lookup table +struct MapScriptObject { + int ID=-1; // The object's "Global ID" + int classIndex=-1; // The object's "Global ID" + RTTIType RTTI; // The type of object +}; + + // -// MapScript +// MapScript Class // class MapScript { public: + + + ~MapScript() { Deinit(); }; + + bool Init(const char* mapName); + void Deinit(); + void CallFunction(const char* functionName); void SetLuaPath(const char* input_path); + + std::vector ObjectCache; + private: lua_State* L; }; + +// Non-Class-Functions +bool Script_SetObjectTrigger(int input_object_id, TriggerClass* input_trigger); +ObjectClass* Script_GetCacheObject(int input_object_id); + +int Script_BuildingIndexFromID(int input_object_id); +int Script_UnitIndexFromID(int input_object_id); +int Script_AircraftIndexFromID(int input_object_id); +int Script_InfantryIndexFromID(int input_object_id); +int Script_VesselIndexFromID(int input_object_id); + #endif \ No newline at end of file