diff --git a/CREDITS.md b/CREDITS.md index 2c22fe0a1d..2e9a355d94 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -401,6 +401,7 @@ This page lists all the individual contributions to the project by their author. - Fast access structure - Iron Curtain/Custom Tint Support for SHP Turreted Vehicles - Reactivate unused trigger events 2, 53, and 54 + - Map Action 511, 609, 610 - **NetsuNegi**: - Forbidding parallel AI queues by type - Jumpjet crash speed fix when crashing onto building diff --git a/docs/AI-Scripting-and-Mapping.md b/docs/AI-Scripting-and-Mapping.md index c1ac78ce80..f3418c4c05 100644 --- a/docs/AI-Scripting-and-Mapping.md +++ b/docs/AI-Scripting-and-Mapping.md @@ -652,6 +652,19 @@ ID=ActionCount,[Action1],510,0,0,[MCVRedeploy],0,0,0,A,[ActionX] ... ``` +### `511` Undeploy Building to Waypoint + +- Undeploy specific BuildingTypes into VehicleTypes and move them to a specific Waypoint. + - If `` is entered for the Building Type here, then undeploy all BuildingTypes. + +In `mycampaign.map`: +```ini +[Actions] +... +ID=ActionCount,[Action1],511,-10,[BuildingTypesID],[HouseIndex],0,0,0,[WaypointIndex],[ActionX] +... +``` + ### `606` Edit Hate-Value - Edit the hate-value that trigger houses to other houses. @@ -707,6 +720,40 @@ ID=ActionCount,[Action1],608,0,0,[HouseIndex],0,0,0,A,[ActionX] ... ``` +### `609` Set Radar Mode + +- Change the current radar mode of the trigger house. + +In `mycampaign.map`: +```ini +[Actions] +... +ID=ActionCount,[Action1],609,0,0,[RadarMode],0,0,0,A,[ActionX] +... +``` + +- The possible argument values are: + +| *Argument* | *Description* | +| :--------: | :-----------------------------------------------------------------------: | +| 0 | Normal mode, requires buildings that provide radar and sufficient power | +| 1 | Change to [FreeRadar](https://modenc.renegadeprojects.com/FreeRadar) mode | +| 2 | Force enable radar | +| 3 | Force disable radar | + +### `610` Set house's `TeamDelays` value + +- Set the `TeamDelays` value of the trigger's house. + - If this value is less than 0, then use the value of `[General] -> TeamDelays`. + +In `mycampaign.map`: +```ini +[Actions] +... +ID=ActionCount,[Action1],610,0,0,[Number],0,0,0,A,[ActionX] +... +``` + ### `800-802` Display Banner - Display a 'banner' at a fixed location that is relative to the screen. diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 5679abfa39..d2a7799e0f 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -185,17 +185,20 @@ HideShakeEffects=false ; boolean [ActionsRA2] 41=Play animation at a waypoint...,0,25,69,0,0,0,1,0,0,[LONG DESC].,0,1,41 125=Build at...,-10,47,0,65,0,0,1,0,0,[LONG DESC],0,1,125 - 500=Save game,-4,13,0,0,0,0,0,0,0,[LONG DESC],0,1,500,1 - 501=Edit variable,0,56,55,6,54,0,0,0,0,[LONG DESC],0,1,501,1 - 502=Generate random number,0,56,57,58,54,0,0,0,0,[LONG DESC],0,1,502,1 - 503=Print variable value,0,56,54,0,0,0,0,0,0,[LONG DESC],0,1,503,0 - 504=Binary operation,0,56,55,60,54,59,0,0,0,[LONG DESC],0,1,504,1 + 500=Save game (Phobos),-4,13,0,0,0,0,0,0,0,[LONG DESC],0,1,500,1 + 501=Edit variable (Phobos),0,56,55,6,54,0,0,0,0,[LONG DESC],0,1,501,1 + 502=Generate random number (Phobos),0,56,57,58,54,0,0,0,0,[LONG DESC],0,1,502,1 + 503=Print variable value (Phobos),0,56,54,0,0,0,0,0,0,[LONG DESC],0,1,503,0 + 504=Binary operation (Phobos),0,56,55,60,54,59,0,0,0,[LONG DESC],0,1,504,1 505=Fire Super Weapon at specified location (Phobos),0,0,20,2,21,22,0,0,0,Launch a Super Weapon from [SuperWeaponTypes] list at a specified location. House=-1 means random target that isn't neutral. House=-2 means the first neutral house. House=-3 means random human target. Coordinate X=-1 means random. Coordinate Y=-1 means random,0,1,505 506=Fire Super Weapon at specified waypoint (Phobos),0,0,20,2,30,0,0,0,0,Launch a Super Weapon from [SuperWeaponTypes] list at a specified waypoint. House=-1 means random target that isn't neutral. House=-2 means the first neutral house. House=-3 means random human target. Coordinate X=-1 means random. Coordinate Y=-1 means random,0,1,506 510=Toggle MCV Redeployablility (Phobos),0,0,15,0,0,0,0,0,0, Set MCVRedeploys to the given value,0,1,510 - 606=Edit hate-value (Phobos),0,2,55,6,0,0,0,0,0, Edit the hate-value that trigger houses to other houses. -1 works for all houses.,0,1,606 - 607=Clear hate-value (Phobos),0,2,0,0,0,0,0,0,0, Clear the hate-value that trigger houses to other houses. -1 works for all houses.,0,1,607 - 608=Set force enemy (Phobos),0,0,2,0,0,0,0,0,0, Force an enemy, it will not change with the change of hate-value. -1 will remove the forced enemy, -2 will never have any enemies.,0,1,608 + 511=Building Type undeploy at... (Phobos),-10,47,2,0,0,0,1,0,0,Recycle the building type into a vehicle and move it to the specified waypoint. If the type is ``, recycle all buildings.,0,1,511 + 606=Edit hate-value... (Phobos),0,2,55,6,0,0,0,0,0, Edit the hate-value that trigger houses to other houses. -1 works for all houses.,0,1,606 + 607=Clear hate-value... (Phobos),0,2,0,0,0,0,0,0,0, Clear the hate-value that trigger houses to other houses. -1 works for all houses.,0,1,607 + 608=Set force enemy... (Phobos),0,0,2,0,0,0,0,0,0, Force an enemy, it will not change with the change of hate-value. -1 will remove the forced enemy, -2 will never have any enemies.,0,1,608 + 609=Set radar mode... (Phobos),0,0,15,0,0,0,0,0,0, Trigger's house can modify the current radar mode. 0 for requires full-power and building, 1 for free radar, 2 for forced enable, 3 for forced disable.,0,1,609 + 610=Set team delay... (Phobos),0,0,6,0,0,0,0,0,0, Trigger's house can customize TeamDelay. When the value is less than 0 in `[General]>TeamDelays`.,0,1,610 800=Display banner and local variable (Phobos),-4,101,104,102,103,3,0,0,0,Draw banner on screen and replace banner with same ID,0,1,800 801=Display banner and global variable (Phobos),-4,101,104,102,103,35,0,0,0,Draw banner on screen and replace banner with same ID,0,1,801 802=Delete banner (Phobos),0,104,0,0,0,0,0,0,0,Delete banner with ID,0,1,802 @@ -475,6 +478,7 @@ New: - [Toggle per-target warhead effects apply timing](New-or-Enhanced-Logics.md#toggle-per-target-warhead-effects-apply-timing) (by TaranDahl) - [Extra range for chasing and pre-firing](New-or-Enhanced-Logics.md#extra-range) (by TaranDahl) - Allow techno type considered as other type when recruiting techno for teams (by NetsuNegi) +- Map Action [`511` Undeploy Building to Waypoint](AI-Scripting-and-Mapping.md#undeploy-building-to-waypoint), [`609` Set Radar Mode](AI-Scripting-and-Mapping.md#set-radar-mode), [`610` Set house's `TeamDelays` value](AI-Scripting-and-Mapping.md#set-house-s-teamdelays-value) (by FlyStar) Vanilla fixes: - Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya) diff --git a/src/Ext/House/Body.cpp b/src/Ext/House/Body.cpp index b1f8e95ca9..3adeb78310 100644 --- a/src/Ext/House/Body.cpp +++ b/src/Ext/House/Body.cpp @@ -685,6 +685,9 @@ void HouseExt::ExtData::Serialize(T& Stm) .Process(this->ForceEnemyIndex) .Process(this->ForceOnlyTargetHouseEnemy) .Process(this->ForceOnlyTargetHouseEnemyMode) + .Process(this->TeamDelay) + .Process(this->FreeRadar) + .Process(this->ForceRadar) ; } diff --git a/src/Ext/House/Body.h b/src/Ext/House/Body.h index 7042542922..8d1e60518c 100644 --- a/src/Ext/House/Body.h +++ b/src/Ext/House/Body.h @@ -68,6 +68,9 @@ class HouseExt std::vector SuperExts; int ForceEnemyIndex; + int TeamDelay; + bool FreeRadar; + bool ForceRadar; ExtData(HouseClass* OwnerObject) : Extension(OwnerObject) , PowerPlantEnhancers {} @@ -99,6 +102,9 @@ class HouseExt , ForceEnemyIndex(-1) , ForceOnlyTargetHouseEnemy { false } , ForceOnlyTargetHouseEnemyMode { -1 } + , TeamDelay(-1) + , FreeRadar(false) + , ForceRadar(false) { } bool OwnsLimboDeliveredBuilding(BuildingClass* pBuilding) const; diff --git a/src/Ext/House/Hooks.cpp b/src/Ext/House/Hooks.cpp index 2152239bd1..0489338586 100644 --- a/src/Ext/House/Hooks.cpp +++ b/src/Ext/House/Hooks.cpp @@ -483,3 +483,48 @@ DEFINE_HOOK(0x4FD8F7, HouseClass_UpdateAI_OnLastLegs, 0x10) return ret; } + +DEFINE_HOOK(0x4F8ACC, HouseClass_Update_ResetTeamDelay, 0x6) +{ + enum { ResetTeamDelay = 0x4F8AD5 }; + + GET(HouseClass*, pThis, ESI); + + const int teamDelay = HouseExt::ExtMap.Find(pThis)->TeamDelay; + + if (teamDelay >= 0) + { + R->ECX(teamDelay); + return ResetTeamDelay; + } + + return 0; +} + +DEFINE_HOOK(0x508E17, HouseClass_UpdateRadar_FreeRadar, 0x8) +{ + enum { ForceRadar = 0x508F2F, Continue = 0x508E4A }; + + GET(HouseClass*, pThis, ECX); + + auto const pExt = HouseExt::ExtMap.Find(pThis); + const bool freeRadar = pExt->FreeRadar; + + if (pExt->ForceRadar) + { + R->Stack(STACK_OFFSET(0x1C, -0xC), freeRadar); + return ForceRadar; + } + else if (pThis->PowerBlackoutTimer.InProgress()) + { + R->Stack(STACK_OFFSET(0x1C, -0xC), false); + return ForceRadar; + } + else if (freeRadar) + { + R->Stack(STACK_OFFSET(0x1C, -0xC), true); + return ForceRadar; + } + + return Continue; +} diff --git a/src/Ext/Scenario/Body.cpp b/src/Ext/Scenario/Body.cpp index a2135dfa35..3236b582d4 100644 --- a/src/Ext/Scenario/Body.cpp +++ b/src/Ext/Scenario/Body.cpp @@ -3,6 +3,8 @@ #include #include +#include + std::unique_ptr ScenarioExt::Data = nullptr; bool ScenarioExt::CellParsed = false; @@ -90,6 +92,11 @@ void ScenarioExt::Remove(ScenarioClass* pThis) void ScenarioExt::LoadFromINIFile(ScenarioClass* pThis, CCINIClass* pINI) { Data->LoadFromINI(pINI); + + for (auto const pHouse : HouseClass::Array) + { + HouseExt::ExtMap.Find(pHouse)->FreeRadar = ScenarioClass::Instance->FreeRadar; + } } void ScenarioExt::ExtData::UpdateAutoDeathObjectsInLimbo() diff --git a/src/Ext/TAction/Body.cpp b/src/Ext/TAction/Body.cpp index 9284bebb66..8a96324b4d 100644 --- a/src/Ext/TAction/Body.cpp +++ b/src/Ext/TAction/Body.cpp @@ -71,6 +71,8 @@ bool TActionExt::Execute(TActionClass* pThis, HouseClass* pHouse, ObjectClass* p case PhobosTriggerAction::ToggleMCVRedeploy: return TActionExt::ToggleMCVRedeploy(pThis, pHouse, pObject, pTrigger, location); + case PhobosTriggerAction::UndeployToWaypoint: + return TActionExt::UndeployToWaypoint(pThis, pHouse, pObject, pTrigger, location); case PhobosTriggerAction::EditAngerNode: return TActionExt::EditAngerNode(pThis, pHouse, pObject, pTrigger, location); @@ -78,6 +80,10 @@ bool TActionExt::Execute(TActionClass* pThis, HouseClass* pHouse, ObjectClass* p return TActionExt::ClearAngerNode(pThis, pHouse, pObject, pTrigger, location); case PhobosTriggerAction::SetForceEnemy: return TActionExt::SetForceEnemy(pThis, pHouse, pObject, pTrigger, location); + case PhobosTriggerAction::SetFreeRadar: + return TActionExt::SetFreeRadar(pThis, pHouse, pObject, pTrigger, location); + case PhobosTriggerAction::SetTeamDelay: + return TActionExt::SetTeamDelay(pThis, pHouse, pObject, pTrigger, location); case PhobosTriggerAction::CreateBannerLocal: return TActionExt::CreateBannerLocal(pThis, pHouse, pObject, pTrigger, location); @@ -384,6 +390,100 @@ bool TActionExt::ToggleMCVRedeploy(TActionClass* pThis, HouseClass* pHouse, Obje return true; } +bool TActionExt::UndeployToWaypoint(TActionClass* const pThis, HouseClass* const pHouse, ObjectClass* const pObject, TriggerClass* const pTrigger, const CellStruct& location) +{ + const auto& nCell = ScenarioExt::Global()->Waypoints[pThis->Waypoint]; + CellClass* const pCell = MapClass::Instance.TryGetCellAt(nCell); + + if (!pCell) + return true; + + HouseClass* vHouse = nullptr; + const int houseIndex = pThis->Param3; + + if (houseIndex >= 0) + { + vHouse = HouseClass::Index_IsMP(houseIndex) + ? HouseClass::FindByIndex(houseIndex) : HouseClass::FindByCountryIndex(houseIndex); + } + + if (!vHouse) + return true; + + const char* buildingName = pThis->TechnoID; + bool allBuilding = false; + BuildingTypeClass* pBuildingType = nullptr; + + if (!strcmp(buildingName, "")) + { + allBuilding = true; + } + else + { + pBuildingType = BuildingTypeClass::Find(buildingName); + } + + if (!allBuilding && !pBuildingType) + return true; + + const auto& limboDelivereds = HouseExt::ExtMap.Find(vHouse)->OwnedLimboDeliveredBuildings; + const bool existLimboBuilding = !limboDelivereds.empty(); + + // Thanks to chaserli for the relevant code! + // There should be a more perfect way to do this, but I don't know how. + auto canUndeploy = [&](BuildingClass* const pBuilding) + { + auto const pType = pBuilding->Type; + + if (!pType->UndeploysInto + || pBuilding->Owner != vHouse + || (!allBuilding && pType != pBuildingType) + || pBuilding->CurrentMission == Mission::Selling + || !pBuilding->IsAlive || pBuilding->Health <= 0 || pBuilding->InLimbo) + { + return false; + } + + // verify whether the building's source is LimboDelivery. + if (existLimboBuilding) + { + for (auto const pLimboBuilding : limboDelivereds) + { + if (pLimboBuilding == pBuilding) + return false; + } + } + + if (pType->ConstructionYard) + { + // Conyards can't undeploy if MCVRedeploy=no + if (!GameModeOptionsClass::Instance.MCVRedeploy) + return false; + // or MindControlledBy YURIX (why? for balance?) + if (!RulesExt::Global()->AllowDeployControlledMCV && pBuilding->MindControlledBy) + return false; + } + + return true; + }; + + for (const auto pBuilding : BuildingClass::Array) + { + if (!canUndeploy(pBuilding)) + continue; + + // Why does having this allow it to undeploy? + // Why don't vehicles move when waypoints are placed off the map? + + const bool old = std::exchange(VocClass::VoicesEnabled, false); + pBuilding->SetArchiveTarget(pCell); + pBuilding->Sell(true); + VocClass::VoicesEnabled = old; + } + + return true; +} + bool TActionExt::EditAngerNode(TActionClass* pThis, HouseClass* pHouse, ObjectClass* pObject, TriggerClass* pTrigger, CellStruct const& location) { if (pHouse->AngerNodes.Count <= 0) @@ -519,6 +619,59 @@ bool TActionExt::SetForceEnemy(TActionClass* pThis, HouseClass* pHouse, ObjectCl return true; } +bool TActionExt::SetFreeRadar(TActionClass* const pThis, HouseClass* const pHouse, ObjectClass* const pObject, TriggerClass* const pTrigger, const CellStruct& location) +{ + if (pHouse->IsControlledByHuman()) + { + auto const pHouseExt = HouseExt::ExtMap.Find(pHouse); + + switch (pThis->Param3) + { + case 1: + pHouseExt->FreeRadar = true; + pHouseExt->ForceRadar = false; + break; + case 2: + pHouseExt->FreeRadar = true; + pHouseExt->ForceRadar = true; + break; + case 3: + pHouseExt->FreeRadar = false; + pHouseExt->ForceRadar = true; + break; + default: + pHouseExt->FreeRadar = false; + pHouseExt->ForceRadar = false; + break; + } + + pHouse->RecheckRadar = true; + } + + return true; +} + +bool TActionExt::SetTeamDelay(TActionClass* const pThis, HouseClass* const pHouse, ObjectClass* const pObject, TriggerClass* const pTrigger, const CellStruct& location) +{ + const int value = pThis->Param3; + const int timer = value < 0 ? RulesClass::Instance->TeamDelays.Items[static_cast(pHouse->AIDifficulty)] : value; + HouseExt::ExtMap.Find(pHouse)->TeamDelay = value; + + auto& Timer = pHouse->TeamDelayTimer; + const int time = std::min(Timer.GetTimeLeft(), timer); + + if (Timer.StartTime == -1 && Timer.TimeLeft != 0 && time > 0) + { + Timer.TimeLeft = time; + } + else if (Timer.InProgress()) + { + Timer.Start(time); + } + + return true; +} + static void CreateOrReplaceBanner(TActionClass* pTAction, bool isGlobal) { const auto pBannerType = BannerTypeClass::Find(pTAction->Text); diff --git a/src/Ext/TAction/Body.h b/src/Ext/TAction/Body.h index 72bf78126f..e7ddc5ba58 100644 --- a/src/Ext/TAction/Body.h +++ b/src/Ext/TAction/Body.h @@ -19,10 +19,13 @@ enum class PhobosTriggerAction : unsigned int RunSuperWeaponAtLocation = 505, RunSuperWeaponAtWaypoint = 506, ToggleMCVRedeploy = 510, + UndeployToWaypoint = 511, EditAngerNode = 606, ClearAngerNode = 607, SetForceEnemy = 608, + SetFreeRadar = 609, + SetTeamDelay = 610, CreateBannerLocal = 800, // any banner w/ local variable CreateBannerGlobal = 801, // any banner w/ global variable @@ -71,10 +74,13 @@ class TActionExt ACTION_FUNC(RunSuperWeaponAtLocation); ACTION_FUNC(RunSuperWeaponAtWaypoint); ACTION_FUNC(ToggleMCVRedeploy); + ACTION_FUNC(UndeployToWaypoint); ACTION_FUNC(EditAngerNode); ACTION_FUNC(ClearAngerNode); ACTION_FUNC(SetForceEnemy); + ACTION_FUNC(SetFreeRadar); + ACTION_FUNC(SetTeamDelay); ACTION_FUNC(CreateBannerLocal); ACTION_FUNC(CreateBannerGlobal);