Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
5bc8b33
initial commit
FS-21 Nov 5, 2022
5915c35
Point to the desired YRpp
FS-21 Nov 5, 2022
287c07b
Improvements and additions
FS-21 Nov 8, 2022
6cdb227
Point to latest YRpp with the required change
FS-21 Nov 8, 2022
e016d41
Script action renaming and docs
FS-21 Nov 8, 2022
8b2da26
Merge remote-tracking branch 'origin/develop' into feature/script-act…
FS-21 Nov 8, 2022
f363188
Fix trailing whitespace
Belonit Nov 25, 2022
8f88565
YRpp
FS-21 May 28, 2023
68d181d
Code rewrite
FS-21 Sep 29, 2023
48f7006
Merge branch 'develop' into feature/script-action-repair-destroyed-br…
FS-21 Sep 29, 2023
6fb6873
Update docs
FS-21 Sep 29, 2023
463391c
fixes & tweaks
FS-21 Sep 29, 2023
9f80f0d
Merge remote-tracking branch 'origin/develop' into feature/script-act…
FS-21 Mar 10, 2024
3c50108
Merge remote-tracking branch 'origin/develop' into feature/script-act…
FS-21 Mar 10, 2024
ba9f42b
Merge branch 'develop' into feature/script-action-repair-destroyed-br…
FS-21 Mar 12, 2024
ec5daff
Merge branch 'develop' into feature/script-action-repair-destroyed-br…
FS-21 Apr 29, 2024
92338db
Script Action moved into its own file
FS-21 Apr 30, 2024
788ab04
fix mistakes
FS-21 May 1, 2024
fad5f95
Tweak
FS-21 May 3, 2024
79dbaad
Fix previous commit
FS-21 May 3, 2024
85ce43d
Fix crash
FS-21 May 10, 2024
8c18788
Merge branch 'develop' into feature/script-action-repair-destroyed-br…
FS-21 Jul 12, 2024
d7a655d
Merge branch 'develop' into feature/script-action-repair-destroyed-br…
FS-21 Jul 14, 2024
79ddf5d
Merge branch 'develop' into feature/script-action-repair-destroyed-br…
FS-21 Aug 29, 2024
2a3e968
Small tweaks and FA2 documentation
FS-21 Aug 29, 2024
44a2add
Merge branch 'develop' into feature/script-action-repair-destroyed-br…
FS-21 Aug 30, 2024
6febb9d
Merge branch 'develop' into feature/script-action-repair-destroyed-br…
FS-21 Oct 27, 2024
51d48c5
Merge branch 'develop' into feature/script-action-repair-destroyed-br…
FS-21 Oct 28, 2024
db25352
Merge branch 'develop' into feature/script-action-repair-destroyed-br…
FS-21 Feb 18, 2025
25fbc94
Merge branch 'develop' into feature/script-action-repair-destroyed-br…
FS-21 May 20, 2025
bd9d749
Merge branch 'develop' into feature/script-action-repair-destroyed-br…
FS-21 May 24, 2025
3805f78
Merge branch 'develop' into feature/script-action-repair-destroyed-br…
FS-21 May 29, 2025
abdc908
Merge branch 'develop' into feature/script-action-repair-destroyed-br…
FS-21 May 30, 2025
c7aaa6e
Merge branch 'develop' into feature/script-action-repair-destroyed-br…
FS-21 Jun 8, 2025
ee8bd15
Merge remote-tracking branch 'origin/develop' into feature/script-act…
FS-21 Jul 7, 2025
f305bbe
Merge branch 'develop' into feature/script-action-repair-destroyed-br…
FS-21 Aug 7, 2025
fc89c9c
Merge branch 'develop' into feature/script-action-repair-destroyed-br…
FS-21 Sep 2, 2025
e35e5f9
Merge remote-tracking branch 'Phobos-developers/develop' into feature…
DeathFishAtEase Dec 21, 2025
6ee3115
[doc] Move the Whats-New entries to the correct positions and link th…
DeathFishAtEase Dec 21, 2025
17799ca
Merge branch 'origin/develop' into feature/script-action-repair-destr…
FS-21 Aug 18, 2026
82ba506
Merge remote-tracking branch 'origin/develop' into feature/script-act…
FS-21 Aug 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ This page lists all the individual contributions to the project by their author.
- Event 606: AttachEffect is attaching to a Techno
- Linked superweapons
- Unit & infantry auto-conversion on ammo change
- Script action for repairing destroyed bridges
- Restore the ScriptType action#24 `Play speech` from Tiberian Sun
- Modify ammo on impact
- **Starkku**:
Expand Down
1 change: 1 addition & 0 deletions Phobos.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
<ClCompile Include="src\Ext\Scenario\Hooks.Waypoints.cpp" />
<!-- src\Ext\Script -->
<ClCompile Include="src\Ext\Script\Body.cpp" />
<ClCompile Include="src\Ext\Script\Body.BridgeRepair.cpp" />
<ClCompile Include="src\Ext\Script\Hooks.cpp" />
<ClCompile Include="src\Ext\Script\Mission.Attack.cpp" />
<ClCompile Include="src\Ext\Script\Mission.Move.cpp" />
Expand Down
18 changes: 18 additions & 0 deletions docs/AI-Scripting-and-Mapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,24 @@ In `aimd.ini`:
x=10104,n ; integer, additional distance in cells
```

##### `10105` Repair Destroyed Bridge

- Picks a Bridge Repair Hut from the map that is linked with a bridge with destroyed sections and is reachable by engineers and then send the Taskforce against it.
- Puts nonengineers into Area Guard mode when they arrive near the Bridge Repair Hut location.

In `aimd.ini`:
```ini
[SOMESCRIPTTYPE] ; ScriptType
x=10105,n ; integer, mode for selecting Bridge Repair Huts
```
- The possible argument values are:

| *Argument* | *Target priority* |
| :--------: | :----------------: |
| 0 | Pick the closest |
| 1 | Pick the Farthest |
| -1 | Pick Random |

### `12000-12999` Suplementary/Setup Pre-actions

#### `12000` Wait if No Target Found
Expand Down
3 changes: 2 additions & 1 deletion docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ HideShakeEffects=false ; boolean
10102=Regroup Temporarily Around the Team Leader,20,0,1,[LONG DESC]
10103=Load Onto Transports,0,0,1,[LONG DESC]
10104=Chronoshift to Enemy Base,20,0,1,[LONG DESC]
10105=Repair Destroyed Bridge,20,0,1,[LONG DESC]
14004=Force Global OnlyTargetHouseEnemy value in Teams,20,0,1,[LONG DESC]
18000=Local variable set,22,0,1,[LONG DESC]
18001=Local variable add,22,0,1,[LONG DESC]
Expand Down Expand Up @@ -577,7 +578,7 @@ HideShakeEffects=false ; boolean
- OpenTopped range bonus and damage multiplier customization for passengers (by Ollerus)
- AutoDeath upon ownership change (by Ollerus)
- [Script Action 14004 for forcing all new actions to target only the main owner's enemy](AI-Scripting-and-Mapping.md#force-global-onlytargethouseenemy-value-in-teams-for-new-attack-move-actions-introduced-by-phobos) (by FS-21)
- [Allow merging AOE damage to buildings into one](New-or-Enhanced-Logics.md#allow-merging-aoe-damage-to-buildings-into-one) (by CrimRecya)
- [Script action for repairing destroyed bridges](AI-Scripting-and-Mapping.md#repair-destroyed-bridge) (by FS-21)
- [Allow customizing whether to synchronously change the owner of the RadioLink-linked units when the owner of a building changes](Fixed-or-Improved-Logics.md#custom-whether-to-synchronously-change-the-owner-of-the-radiolink-linked-units-when-the-owner-of-a-building-changes) (by TaranDahl)
- [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)
Expand Down
260 changes: 260 additions & 0 deletions src/Ext/Script/Body.BridgeRepair.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
#include "Body.h"
#include <Ext/Techno/Body.h>

void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1)
{
auto pTeamData = TeamExt::Fetch(pTeam);
if (!pTeamData)
return;

auto pScript = pTeam->CurrentScript;
int currentMission = pScript->CurrentMission;

// The first time this team runs this kind of script the repair huts list will updated. The only reason of why it isn't stored in ScenarioClass is because always exists the possibility of a modder to make destroyable Repair Huts
if (pTeamData->BridgeRepairHuts.size() == 0)
{
for (auto pTechno : TechnoClass::Array)
{
if (pTechno->WhatAmI() != AbstractType::Building)
continue;

const auto pBuilding = abstract_cast<BuildingClass*>(pTechno);
if (!pBuilding)
continue;

if (pBuilding->Type->BridgeRepairHut)
pTeamData->BridgeRepairHuts.push_back(pTechno);
}

if (pTeamData->BridgeRepairHuts.size() == 0)
{
pTeam->StepCompleted = true;
ScriptExt::Log("AI Scripts - RepairDestroyedBridge: [%s][%s] (line: %d = %d,%d) Jump to next line: %d = %d,%d -> (Reason: No repair huts found).\n",
pTeam->Type->ID,
pScript->Type->ID,
currentMission,
pScript->Type->ScriptActions[currentMission].Action,
pScript->Type->ScriptActions[currentMission].Argument,
currentMission + 1,
pScript->Type->ScriptActions[currentMission + 1].Action,
pScript->Type->ScriptActions[currentMission + 1].Argument);

return;
}
}

// Reset Team's target if the current target isn't a repair hut
if (pTeam->Focus)
{
if (pTeam->Focus->WhatAmI() != AbstractType::Building)
{
pTeam->Focus = nullptr;
}
else
{
const auto pBuilding = static_cast<BuildingClass*>(pTeam->Focus);

if (!pBuilding->Type->BridgeRepairHut)
{
pTeam->Focus = nullptr;
}
else
{
CellStruct cell = pBuilding->GetCell()->MapCoords;

// If the Bridge was repaired then the repair hut isn't valid anymore
if (!MapClass::Instance.IsLinkedBridgeDestroyed(cell))
pTeam->Focus = nullptr;
}
}
}

TechnoClass* selectedTarget = pTeam->Focus ? static_cast<TechnoClass*>(pTeam->Focus) : nullptr;
bool isEngineerAmphibious = false;
std::vector<FootClass*> engineers;
std::vector<FootClass*> otherTeamMembers;

// Check if there are no engineers
for (auto pUnit = pTeam->FirstUnit; pUnit; pUnit = pUnit->NextTeamMember)
{
if (!ScriptExt::IsUnitAvailable(pUnit, true))
continue;

if (!pTeam->Focus)
{
pUnit->SetTarget(nullptr);
pUnit->SetDestination(nullptr, false);
pUnit->ForceMission(Mission::Guard);
}

if (pUnit->WhatAmI() == AbstractType::Infantry)
{
const auto pInf = static_cast<InfantryClass*>(pUnit);

if (pInf->IsEngineer())
{
if (pUnit->GetTechnoType()->MovementZone == MovementZone::Amphibious
|| pUnit->GetTechnoType()->MovementZone == MovementZone::AmphibiousCrusher
|| pUnit->GetTechnoType()->MovementZone == MovementZone::AmphibiousDestroyer)
{
isEngineerAmphibious = true;
}

engineers.push_back(pUnit);
continue;
}
}

// Non-engineers will receive a different command
otherTeamMembers.push_back(pUnit);
}

if (engineers.size() == 0)
{
pTeam->StepCompleted = true;
ScriptExt::Log("AI Scripts - RepairDestroyedBridge: [%s][%s] (line: %d = %d,%d) Jump to next line: %d = %d,%d -> (Reason: Team has no engineers).\n",
pTeam->Type->ID,
pScript->Type->ID,
currentMission,
pScript->Type->ScriptActions[currentMission].Action,
pScript->Type->ScriptActions[currentMission].Argument,
currentMission + 1,
pScript->Type->ScriptActions[currentMission + 1].Action,
pScript->Type->ScriptActions[currentMission + 1].Argument);

return;
}

std::vector<TechnoClass*> validHuts;

if (!selectedTarget)
{
for (const auto pTechno : pTeamData->BridgeRepairHuts)
{
CellStruct cell = pTechno->GetCell()->MapCoords;

// Skip all huts linked to non-destroyed bridges
if (!MapClass::Instance.IsLinkedBridgeDestroyed(cell))
continue;

if (isEngineerAmphibious)
{
validHuts.push_back(pTechno);
}
else
{
CoordStruct coords = pTechno->GetCenterCoords();

// Only huts reachable by the (first) engineer are valid
if (engineers.at(0)->IsInSameZoneAsCoords(pTechno->GetCenterCoords()))
validHuts.push_back(pTechno);
}
}

if (validHuts.size() == 0)
{
ScriptExt::Log("AI Scripts - RepairDestroyedBridge: [%s][%s] (line: %d = %d,%d) Jump to next line: %d = %d,%d (Reason: Can not select a bridge repair hut).\n",
pTeam->Type->ID,
pScript->Type->ID,
currentMission,
pScript->Type->ScriptActions[currentMission].Action,
pScript->Type->ScriptActions[currentMission].Argument,
currentMission + 1,
pScript->Type->ScriptActions[currentMission + 1].Action,
pScript->Type->ScriptActions[currentMission + 1].Argument);

pTeam->StepCompleted = true;
return;
}

// Find the best repair hut
int bestVal = -1;

if (mode < 0)
mode = pTeam->CurrentScript->Type->ScriptActions[pTeam->CurrentScript->CurrentMission].Argument;

if (mode < 0) // Pick a random bridge
{
selectedTarget = validHuts.at(ScenarioClass::Instance->Random.RandomRanged(0, validHuts.size() - 1));
}
else
{
for (const auto pHut : validHuts)
{
int value = engineers.at(0)->DistanceFrom(pHut); // Note: distance is in leptons (*256)
bool isValidCandidate = false;

if (mode == 0)
isValidCandidate = value < bestVal; // Pick the closest target
else
isValidCandidate = value >= bestVal; // Pick the farthest target

if (isValidCandidate || bestVal < 0)
{
bestVal = value;
selectedTarget = pHut;
}
}
}
}

validHuts.clear();

if (!selectedTarget)
{
ScriptExt::Log("AI Scripts - RepairDestroyedBridge: [%s][%s] (line: %d = %d,%d) Jump to next line: %d = %d,%d (Reason: Can not select a bridge repair hut).\n",
pTeam->Type->ID,
pScript->Type->ID,
currentMission,
pScript->Type->ScriptActions[currentMission].Action,
pScript->Type->ScriptActions[currentMission].Argument,
currentMission + 1,
pScript->Type->ScriptActions[currentMission + 1].Action,
pScript->Type->ScriptActions[currentMission + 1].Argument);

pTeam->StepCompleted = true;
return;
}

// Setting the team's target & mission
pTeam->Focus = selectedTarget;

for (auto engineer : engineers)
{
if (engineer->Destination != selectedTarget)
{
engineer->SetTarget(selectedTarget);
engineer->QueueMission(Mission::Capture, true);
}
}

if (otherTeamMembers.size() > 0)
{
double closeEnough = RulesClass::Instance->CloseEnough; // Note: this value is in leptons (*256)

for (auto pFoot : otherTeamMembers)
{
if (pTeamData && pTeamData->CloseEnough > 0)
closeEnough = pTeamData->CloseEnough * 256.0;

if (!pFoot->Destination
|| (selectedTarget->DistanceFrom(pFoot->Destination) > closeEnough))
{
// Reset previous command
pFoot->SetTarget(nullptr);
pFoot->SetDestination(nullptr, false);
pFoot->ForceMission(Mission::Guard);

// Get a cell near the target
pFoot->QueueMission(Mission::Move, false);
CoordStruct coord = TechnoExt::PassengerKickOutLocation(selectedTarget, pFoot);
CellClass* pCellDestination = MapClass::Instance.TryGetCellAt(coord);
pFoot->SetDestination(pCellDestination, true);
}

// Reached destination, stay in guard until next action
if (pFoot->DistanceFrom(pFoot->Destination) < closeEnough)
pFoot->QueueMission(Mission::Area_Guard, false);
}
}
}
4 changes: 4 additions & 0 deletions src/Ext/Script/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,10 @@ void ScriptExt::ProcessAction(TeamClass* pTeam)
// Chronoshift to enemy base, argument is additional distance modifier
ScriptExt::ChronoshiftToEnemyBase(pTeam, argument);
break;
case PhobosScripts::RepairDestroyedBridge:
// Start Timed Jump that jumps to the same line when the countdown finish (in frames)
ScriptExt::RepairDestroyedBridge(pTeam, -1);
break;
case PhobosScripts::ForceGlobalOnlyTargetHouseEnemy:
ScriptExt::ForceGlobalOnlyTargetHouseEnemy(pTeam, -1);
break;
Expand Down
2 changes: 2 additions & 0 deletions src/Ext/Script/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ enum class PhobosScripts : unsigned int
GatherAroundLeader = 10102,
LoadIntoTransports = 10103,
ChronoshiftToEnemyBase = 10104,
RepairDestroyedBridge = 10105,

// Range 12000-12999 are suplementary/setup pre-actions
WaitIfNoTarget = 12000,
Expand Down Expand Up @@ -230,6 +231,7 @@ class ScriptExt final : public AbstractExt
static void VariableBinaryOperationHandler(TeamClass* pTeam, int nVariable, int nVarToOperate);
static bool IsUnitAvailable(TechnoClass* pTechno, bool checkIfInTransportOrAbsorbed);
static void Log(const char* pFormat, ...);
static void RepairDestroyedBridge(TeamClass* pTeam, int mode);
static void PlaySpeech(TeamClass* pTeam);

// Mission.Attack.cpp
Expand Down
1 change: 1 addition & 0 deletions src/Ext/Team/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ void TeamExt::Serialize(T& Stm)
.Process(this->ForceJump_RepeatMode)
.Process(this->TeamLeader)
.Process(this->PreviousScriptList)
.Process(this->BridgeRepairHuts)
;
}

Expand Down
2 changes: 2 additions & 0 deletions src/Ext/Team/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class TeamExt final : public AbstractExt, public Detach::Listener<FootClass>
bool ForceJump_RepeatMode;
FootClass* TeamLeader;
std::vector<ScriptClass*> PreviousScriptList;
std::vector<TechnoClass*> BridgeRepairHuts;

TeamExt(TeamClass* OwnerObject) : AbstractExt(OwnerObject)
, WaitNoTargetAttempts { 0 }
Expand All @@ -52,6 +53,7 @@ class TeamExt final : public AbstractExt, public Detach::Listener<FootClass>
, ForceJump_RepeatMode { false }
, TeamLeader { nullptr }
, PreviousScriptList { }
, BridgeRepairHuts { }
{ }

virtual ~TeamExt() = default;
Expand Down
2 changes: 1 addition & 1 deletion src/Ext/Techno/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ double TechnoExt::GetCurrentArmorMultiplier(TechnoClass* pThis, TechnoTypeClass*
(pThis->HasAbility(Ability::Stronger) ? RulesClass::Instance->VeteranArmor : 1.0);
}

CoordStruct TechnoExt::PassengerKickOutLocation(TechnoClass* pThis, FootClass* pPassenger, int maxAttempts = 1)
CoordStruct TechnoExt::PassengerKickOutLocation(TechnoClass* pThis, FootClass* pPassenger, int maxAttempts)
{
if (!pThis || !pPassenger)
return CoordStruct::Empty;
Expand Down
2 changes: 1 addition & 1 deletion src/Ext/Techno/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ class TechnoExt : public RadioExt, public Detach::Listener<AirstrikeClass>
static void DrawInsignia(TechnoClass* pThis, Point2D* pLocation, RectangleStruct* pBounds);
static void ApplyGainedSelfHeal(TechnoClass* pThis);
static void SyncInvulnerability(TechnoClass* pFrom, TechnoClass* pTo);
static CoordStruct PassengerKickOutLocation(TechnoClass* pThis, FootClass* pPassenger, int maxAttempts);
static CoordStruct PassengerKickOutLocation(TechnoClass* pThis, FootClass* pPassenger, int maxAttempts = 1);
static bool AllowedTargetByZone(TechnoClass* pThis, TechnoClass* pTarget, TargetZoneScanType zoneScanType, WeaponTypeClass* pWeapon = nullptr, bool useZone = false, int zone = -1);
static void UpdateAttachedAnimLayers(TechnoClass* pThis);
static bool ConvertToType(FootClass* pThis, TechnoTypeClass* toType);
Expand Down
Loading