From 5bc8b33c01d9434453d73be07ff1e310e2ac7cea Mon Sep 17 00:00:00 2001 From: FS-21 Date: Sat, 5 Nov 2022 08:42:53 +0100 Subject: [PATCH 01/17] initial commit --- YRpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/YRpp b/YRpp index a41eb1a449..e8794ade27 160000 --- a/YRpp +++ b/YRpp @@ -1 +1 @@ -Subproject commit a41eb1a4491f8256459a1c3e4a25af5ebded45db +Subproject commit e8794ade27cbf6122a508f0c186f0c9a4262f7a9 From 5915c356e8a49edc5414411ad80380012f27489e Mon Sep 17 00:00:00 2001 From: FS-21 Date: Sat, 5 Nov 2022 08:44:18 +0100 Subject: [PATCH 02/17] Point to the desired YRpp With the Bridge validation check --- YRpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/YRpp b/YRpp index e8794ade27..792aa8b549 160000 --- a/YRpp +++ b/YRpp @@ -1 +1 @@ -Subproject commit e8794ade27cbf6122a508f0c186f0c9a4262f7a9 +Subproject commit 792aa8b5494acfc3eec86931fd39f0aa8d85de43 From 287c07b724f10972e1ea92f441ef3c6357dc06e9 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Tue, 8 Nov 2022 02:38:29 +0100 Subject: [PATCH 03/17] Improvements and additions Now the script action argument can be used to select a mode: 0: Closest Repair Hut with destroyed bridge. 1: Farthest Repair Hut with destroyed bridge. -1: Random Repair Hut with destroyed bridge. Important: now Engineers will pick only Repair Huts that can be reached. --- YRpp | 2 +- src/Ext/Script/Body.cpp | 466 ++++++++++++++++++++++++++++++++++++++++ src/Ext/Script/Body.h | 3 + src/Ext/Team/Body.cpp | 8 + src/Ext/Team/Body.h | 17 ++ src/Ext/Techno/Body.cpp | 30 +++ src/Ext/Techno/Body.h | 38 ++++ 7 files changed, 563 insertions(+), 1 deletion(-) diff --git a/YRpp b/YRpp index 792aa8b549..e8794ade27 160000 --- a/YRpp +++ b/YRpp @@ -1 +1 @@ -Subproject commit 792aa8b5494acfc3eec86931fd39f0aa8d85de43 +Subproject commit e8794ade27cbf6122a508f0c186f0c9a4262f7a9 diff --git a/src/Ext/Script/Body.cpp b/src/Ext/Script/Body.cpp index 0e34e346b7..b9518b0f1a 100644 --- a/src/Ext/Script/Body.cpp +++ b/src/Ext/Script/Body.cpp @@ -210,6 +210,10 @@ void ScriptExt::ProcessAction(TeamClass* pTeam) // Start Timed Jump that jumps to the same line when the countdown finish (in frames) ScriptExt::Set_ForceJump_Countdown(pTeam, true, -1); break; + case PhobosScripts::CaptureDestroyedBridge: + // Start Timed Jump that jumps to the same line when the countdown finish (in frames) + ScriptExt::CaptureDestroyedBridge(pTeam, -1); + break; default: // Do nothing because or it is a wrong Action number or it is an Ares/YR action... if (action > 70 && !IsExtVariableAction(action)) @@ -3088,3 +3092,465 @@ void ScriptExt::Stop_ForceJump_Countdown(TeamClass *pTeam) pTeam->StepCompleted = true; Debug::Log("DEBUG: [%s] [%s](line: %d = %d,%d): Stopped Timed Jump\n", pTeam->Type->ID, pScript->Type->ID, pScript->CurrentMission, pScript->Type->ScriptActions[pScript->CurrentMission].Action, pScript->Type->ScriptActions[pScript->CurrentMission].Argument); } + +void ScriptExt::CaptureDestroyedBridge(TeamClass* pTeam, int mode = -1) +{ + if (!pTeam) + return; + + auto pScript = pTeam->CurrentScript; + + auto pTeamData = TeamExt::ExtMap.Find(pTeam); + if (!pTeamData) + { + pTeam->StepCompleted = true; + Debug::Log("DEBUG: [%s] [%s] (line: %d = %d,%d) Jump to next line: %d = %d,%d -> (Reason: ExtData found)\n", + pTeam->Type->ID, + pScript->Type->ID, + pScript->CurrentMission, + pScript->Type->ScriptActions[pScript->CurrentMission].Action, + pScript->Type->ScriptActions[pScript->CurrentMission].Argument, + pScript->CurrentMission + 1, + pScript->Type->ScriptActions[pScript->CurrentMission + 1].Action, + pScript->Type->ScriptActions[pScript->CurrentMission + 1].Argument); + + return; + } + + // Save all Bridge Repair Hut Structures for saving time in the team actions. The worst case-scenario is if the map doesn't have bridges + if (pTeamData->MapPath_BridgeRepairHuts.Count == 0) + { + for (auto pTechno : *TechnoClass::Array) + { + if (pTechno->WhatAmI() == AbstractType::Building) + { + auto pBuilding = static_cast(pTechno); + if (!pBuilding) + continue; + + if (pBuilding->Type->BridgeRepairHut) + pTeamData->MapPath_BridgeRepairHuts.AddItem(pTechno); + } + } + } + + //DynamicVectorClass bridgeRepairHuts; + bool isReachable = false; + + if (pTeamData->MapPath_InProgress + && pTeamData->MapPath_StartTechno + && pTeamData->MapPath_EndTechno) + { + // Continue the search process + isReachable = ScriptExt::FindLinkedPath(pTeam, pTeamData->MapPath_StartTechno, pTeamData->MapPath_EndTechno); + + // The operation didn't end, it will continue in the next game frame + if (pTeamData->MapPath_InProgress) + return; + + if (isReachable) + { + CellStruct cell = pTeamData->MapPath_EndTechno->GetCell()->MapCoords; + bool isLinkedBridgeDestroyed = MapClass::Instance->IsLinkedBridgeDestroyed(cell); + + if (isLinkedBridgeDestroyed) + pTeamData->MapPath_ValidBridgeRepairHuts.AddItem(pTeamData->MapPath_EndTechno); + } + + if (pTeamData->MapPath_CheckedBridgeRepairHuts.FindItemIndex(pTeamData->MapPath_EndTechno) < 0) + pTeamData->MapPath_CheckedBridgeRepairHuts.AddItem(pTeamData->MapPath_EndTechno); + } + else + { + // Or is the first run or someone in the operation died so it should start again + pTeamData->MapPath_InProgress = false; + pTeamData->MapPath_StartTechno = nullptr; + pTeamData->MapPath_EndTechno = nullptr; + pTeamData->MapPath_ValidBridgeRepairHuts.Clear(); + pTeamData->MapPath_CheckedBridgeRepairHuts.Clear(); + } + + if (pTeam->Focus) + { + if (pTeam->Focus->WhatAmI() != AbstractType::Building) + { + pTeam->Focus = nullptr; + } + else + { + auto pBuilding = static_cast(pTeam->Focus); + + if (!pBuilding->Type->BridgeRepairHut) + { + pTeam->Focus = nullptr; + } + else + { + CellStruct cell = pBuilding->GetCell()->MapCoords; + + // If the Bridge was repaired then isn't valid anymore + if (!MapClass::Instance->IsLinkedBridgeDestroyed(cell)) + pTeam->Focus = nullptr; + } + } + } + + TechnoClass* selectedTarget = pTeam->Focus ? static_cast(pTeam->Focus) : nullptr; + DynamicVectorClass engineers; + DynamicVectorClass otherTeamMembers; + + // If there are no engineers end this script action + for (auto pUnit = pTeam->FirstUnit; pUnit; pUnit = pUnit->NextTeamMember) + { + if (pUnit + && pUnit->IsAlive + && !pUnit->InLimbo + && !pUnit->Transporter + && !pUnit->TemporalTargetingMe + && !pUnit->BeingWarpedOut) + { + if (pUnit->WhatAmI() == AbstractType::Infantry) + { + auto pInf = static_cast(pUnit); + + if (pInf->IsEngineer()) + { + engineers.AddItem(pInf); + continue; + } + } + + // These units will receive a different command + otherTeamMembers.AddItem(pUnit); + } + } + + if (engineers.Count == 0) + { + pTeam->StepCompleted = true; + Debug::Log("DEBUG: [%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, + pScript->CurrentMission, + pScript->Type->ScriptActions[pScript->CurrentMission].Action, + pScript->Type->ScriptActions[pScript->CurrentMission].Argument, + pScript->CurrentMission + 1, + pScript->Type->ScriptActions[pScript->CurrentMission + 1].Action, + pScript->Type->ScriptActions[pScript->CurrentMission + 1].Argument); + pTeamData->MapPath_ValidBridgeRepairHuts.Clear(); + pTeamData->MapPath_CheckedBridgeRepairHuts.Clear(); + + return; + } + + if (!selectedTarget) + { + // Looking for BridgeRepairHut=yes structures + //auto anyEngineer = engineers.GetItem(0); + for (auto pTechno : pTeamData->MapPath_BridgeRepairHuts) + { + // If it was previously inserted we will ignore it + if (pTeamData->MapPath_ValidBridgeRepairHuts.FindItemIndex(pTechno) >= 0) + continue; + + // If it was previously checked we will ignore it + if (pTeamData->MapPath_CheckedBridgeRepairHuts.FindItemIndex(pTechno) >= 0) + continue; + + auto engineer = static_cast(engineers.GetItem(0)); + isReachable = ScriptExt::FindLinkedPath(pTeam, engineer, pTechno); + + // This process didn't end. It will continue in the next game frame + if (pTeamData->MapPath_InProgress) + return; + + if (isReachable) + { + CellStruct cell = pTechno->GetCell()->MapCoords; + bool isLinkedBridgeDestroyed = MapClass::Instance->IsLinkedBridgeDestroyed(cell); + + if (isLinkedBridgeDestroyed) + pTeamData->MapPath_ValidBridgeRepairHuts.AddItem(pTechno); + } + + pTeamData->MapPath_CheckedBridgeRepairHuts.AddItem(pTechno); + } + + if (pTeamData->MapPath_ValidBridgeRepairHuts.Count == 0) + { + pTeam->StepCompleted = true; + Debug::Log("DEBUG: [%s] [%s] (line: %d = %d,%d) Jump to next line: %d = %d,%d -> (Reason: This map has no Bridge Repair Huts)\n", + pTeam->Type->ID, + pScript->Type->ID, + pScript->CurrentMission, + pScript->Type->ScriptActions[pScript->CurrentMission].Action, + pScript->Type->ScriptActions[pScript->CurrentMission].Argument, + pScript->CurrentMission + 1, + pScript->Type->ScriptActions[pScript->CurrentMission + 1].Action, + pScript->Type->ScriptActions[pScript->CurrentMission + 1].Argument); + pTeamData->MapPath_ValidBridgeRepairHuts.Clear(); + pTeamData->MapPath_CheckedBridgeRepairHuts.Clear(); + + return; + } + + if (mode < 0) + mode = pTeam->CurrentScript->Type->ScriptActions[pTeam->CurrentScript->CurrentMission].Argument; + + // Pick the nearest destroyed bridge + int bestVal = -1; + + if (mode < 0) + { + // Pick a random bridge + selectedTarget = pTeamData->MapPath_ValidBridgeRepairHuts.GetItem(ScenarioClass::Instance->Random.RandomRanged(0, pTeamData->MapPath_ValidBridgeRepairHuts.Count - 1)); + } + else + { + for (auto pHut : pTeamData->MapPath_ValidBridgeRepairHuts) + { + if (mode > 0) + { + // Pick the farthest target + int value = engineers.GetItem(0)->DistanceFrom(pHut); // Note: distance is in leptons (*256) + + if (value >= bestVal || bestVal < 0) + { + bestVal = value; + selectedTarget = pHut; + } + } + else + { + // Pick the closest target + int value = engineers.GetItem(0)->DistanceFrom(pHut); // Note: distance is in leptons (*256) + + if (value < bestVal || bestVal < 0) + { + bestVal = value; + selectedTarget = pHut; + } + } + } + } + } + + if (!selectedTarget) + { + pTeam->StepCompleted = true; + Debug::Log("DEBUG: [%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, + pScript->CurrentMission, + pScript->Type->ScriptActions[pScript->CurrentMission].Action, + pScript->Type->ScriptActions[pScript->CurrentMission].Argument, + pScript->CurrentMission + 1, + pScript->Type->ScriptActions[pScript->CurrentMission + 1].Action, + pScript->Type->ScriptActions[pScript->CurrentMission + 1].Argument); + pTeamData->MapPath_ValidBridgeRepairHuts.Clear(); + pTeamData->MapPath_CheckedBridgeRepairHuts.Clear(); + + return; + } + + pTeam->Focus = selectedTarget; + pTeamData->MapPath_ValidBridgeRepairHuts.Clear(); + pTeamData->MapPath_CheckedBridgeRepairHuts.Clear(); + + for (auto engineer : engineers) + { + if (engineer->Destination != selectedTarget) + { + engineer->SetTarget(selectedTarget); + engineer->QueueMission(Mission::Capture, true); + } + } + + if (otherTeamMembers.Count > 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->SetFocus(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); + } + } +} + +// Find the shortest valid path to the destination, if possible +bool ScriptExt::FindLinkedPath(TeamClass* pTeam, TechnoClass* pThis = nullptr, TechnoClass* pTarget = nullptr) +{ + auto pTeamData = TeamExt::ExtMap.Find(pTeam); + if (!pTeamData) + return false; + + CellStruct startCell = CellStruct::Empty; + CellStruct endCell = CellStruct::Empty; + + if (!pTeamData->MapPath_InProgress) + { + // First time that is execued this process + if (pThis == nullptr || pTarget == nullptr) + return false; + + pTeamData->MapPath_StartTechno = pThis; + pTeamData->MapPath_EndTechno = pTarget; + + pTeamData->MapPath_InProgress = true; + pTeamData->MapPath_Grid.clear(); + pTeamData->MapPath_Queue.clear(); + + // Creates a "map" with the same size of the original and each "cell" is a boolean value that means if the cell was evaluated or not. Evaluated "cells" get ignored in posterior checks + int matrixX = MapClass::Instance->MapCoordBounds.Right; + int matrixY = MapClass::Instance->MapCoordBounds.Bottom; + + pTeamData->MapPath_Grid = std::vector>(matrixX, std::vector(matrixY, false)); + + startCell = pThis->GetCell()->MapCoords; + endCell = pTarget->GetCell()->MapCoords; + + // The first element of this path is the unit's location + MapPathCellElement startElement; + startElement.X = startCell.X; + startElement.Y = startCell.Y; + startElement.Distance = pThis->DistanceFrom(pTarget); // Note: distance is in leptons (*256) + pTeamData->MapPath_Queue.push_back(startElement); + } + else + { + // We'll resume the previous unfinished analysis that was so time expensible that was splitted in multiple parts + if (pThis == nullptr || pTarget == nullptr) + { + pTeamData->MapPath_InProgress = false; + pTeamData->MapPath_Grid.clear(); + pTeamData->MapPath_Queue.clear(); + pTeamData->MapPath_StartTechno = nullptr; + pTeamData->MapPath_EndTechno = nullptr; + + return false; + } + + endCell = pTarget->GetCell()->MapCoords; + } + + bool found = false; + + // If we don't split this operation in multiple frames the game will stop half a second in the worst case scenarios for finding the destination (or if directly there is no valid path) and that's unaceptable. + // We'll use a number of checks limiter and when it reaches 0 we will stop the process and continue it in the next frame. + // Lower value == more frames required for calculating a valid path (if exists). + // Higher value == less game frames required for calculating a valid path but noticeable FPS drops. + int nChecksLeft = 512; + + while ((pTeamData->MapPath_Queue.size() > 0) && !found) + { + // If counter reached the limit we will stop the process and continue it in the next frame. + if (nChecksLeft <= 0) + return false; + + nChecksLeft--; + + // Extract the first element of MapPath_Queue for analyzing it + MapPathCellElement element = pTeamData->MapPath_Queue.at(0); + pTeamData->MapPath_Queue.erase(pTeamData->MapPath_Queue.begin()); + + // Check cells around the selected cell, it only stops if we reach the destination of the queue is empty + for (int i = element.X - 1; (i <= element.X + 1) && !found; i++) + { + for (int j = element.Y - 1; (j <= element.Y + 1) && !found; j++) + { + CellStruct nCell; + nCell.X = (short)i; + nCell.Y = (short)j; + + // If reached the destination end the process or the target moved into an evaluated area the process finished + if ((nCell.X == endCell.X && nCell.Y == endCell.Y) + || (pTeamData->MapPath_Grid[endCell.X][endCell.Y])) + { + found = true; + break; + } + + // Only check nonvisited cells + if (!pTeamData->MapPath_Grid[i][j]) + { + if (MapClass::Instance->IsWithinUsableArea(nCell, false)) + { + auto pCell = MapClass::Instance->TryGetCellAt(nCell); + if (pThis->IsCellOccupied(pCell, -1, -1, nullptr, false) != Move::OK) + pTeamData->MapPath_Grid[i][j] = true; + + if (!pTeamData->MapPath_Grid[i][j]) + { + // If is a valid cell we'll queue it for future checks + MapPathCellElement newElement; + newElement.X = (short)i; + newElement.Y = (short)j; + newElement.Distance = pTarget->DistanceFrom(pCell); // Note: distance is in leptons (*256) + pTeamData->MapPath_Grid[i][j] = true; + + // Find the right position in the vector. Sorted by ascendent distance; + if (pTeamData->MapPath_Queue.size() == 0) + { + pTeamData->MapPath_Queue.push_back(newElement); + } + else + { + auto index = pTeamData->MapPath_Queue.begin(); + bool inserted = false; + + for (unsigned int k = 0; k < pTeamData->MapPath_Queue.size(); k++) + { + if (newElement < pTeamData->MapPath_Queue.at(k)) + { + pTeamData->MapPath_Queue.insert(index, newElement); + inserted = true; + + break; + } + + ++index; + } + + if (!inserted) + pTeamData->MapPath_Queue.push_back(newElement); + } + } + } + else + { + // Mark the unusable cells as visited + pTeamData->MapPath_Grid[i][j] = true; + } + } + } + } + } + + // Ended. Cleanning the mess + pTeamData->MapPath_InProgress = false; + pTeamData->MapPath_Grid.clear(); + pTeamData->MapPath_Queue.clear(); + + return found; +} diff --git a/src/Ext/Script/Body.h b/src/Ext/Script/Body.h index c1a37acfdf..b5ab722bd3 100644 --- a/src/Ext/Script/Body.h +++ b/src/Ext/Script/Body.h @@ -57,6 +57,7 @@ enum class PhobosScripts : unsigned int WaitUntilFullAmmo = 10101, GatherAroundLeader = 10102, LoadIntoTransports = 10103, + CaptureDestroyedBridge = 99999, // Range 12000-12999 are suplementary/setup pre-actions WaitIfNoTarget = 12000, @@ -217,6 +218,8 @@ class ScriptExt static void VariableOperationHandler(TeamClass* pTeam, int nVariable, int Number); template static void VariableBinaryOperationHandler(TeamClass* pTeam, int nVariable, int nVarToOperate); + static void CaptureDestroyedBridge(TeamClass* pTeam, int mode); + static bool FindLinkedPath(TeamClass* pTeam, TechnoClass* pThis, TechnoClass* pTarget); static ExtContainer ExtMap; diff --git a/src/Ext/Team/Body.cpp b/src/Ext/Team/Body.cpp index 878d9c7995..22cadfb9b1 100644 --- a/src/Ext/Team/Body.cpp +++ b/src/Ext/Team/Body.cpp @@ -22,6 +22,14 @@ void TeamExt::ExtData::Serialize(T& Stm) .Process(this->ForceJump_InitialCountdown) .Process(this->ForceJump_RepeatMode) .Process(this->TeamLeader) + .Process(this->MapPath_Grid) + .Process(this->MapPath_Queue) + .Process(this->MapPath_InProgress) + .Process(this->MapPath_StartTechno) + .Process(this->MapPath_EndTechno) + .Process(this->MapPath_BridgeRepairHuts) + .Process(this->MapPath_ValidBridgeRepairHuts) + .Process(this->MapPath_CheckedBridgeRepairHuts) ; } diff --git a/src/Ext/Team/Body.h b/src/Ext/Team/Body.h index 01033f3ba6..a1f9c48ec4 100644 --- a/src/Ext/Team/Body.h +++ b/src/Ext/Team/Body.h @@ -5,6 +5,7 @@ #include #include #include +#include #include class TeamExt @@ -27,6 +28,14 @@ class TeamExt int ForceJump_InitialCountdown; bool ForceJump_RepeatMode; FootClass* TeamLeader; + std::vector> MapPath_Grid; // Used for marking visited/analyzed cells + std::vector MapPath_Queue; // Cells that will be analyzed for finding a path + bool MapPath_InProgress; + TechnoClass* MapPath_StartTechno; + TechnoClass* MapPath_EndTechno; + DynamicVectorClass MapPath_BridgeRepairHuts; + DynamicVectorClass MapPath_ValidBridgeRepairHuts; + DynamicVectorClass MapPath_CheckedBridgeRepairHuts; ExtData(TeamClass* OwnerObject) : Extension(OwnerObject) , WaitNoTargetAttempts { 0 } @@ -41,6 +50,14 @@ class TeamExt , ForceJump_InitialCountdown { -1 } , ForceJump_RepeatMode { false } , TeamLeader { nullptr } + , MapPath_Grid { } + , MapPath_Queue { } + , MapPath_InProgress { false } + , MapPath_StartTechno { nullptr } + , MapPath_EndTechno { nullptr } + , MapPath_BridgeRepairHuts { } + , MapPath_ValidBridgeRepairHuts { } + , MapPath_CheckedBridgeRepairHuts { } { } virtual ~ExtData() = default; diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 6d6f800191..96196fa9d1 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -939,6 +939,36 @@ void TechnoExt::DisplayDamageNumberString(TechnoClass* pThis, int damage, bool i pExt->DamageNumberOffset = pExt->DamageNumberOffset + width; } +CoordStruct TechnoExt::PassengerKickOutLocation(TechnoClass* pThis, FootClass* pPassenger) +{ + if (!pThis || !pPassenger) + return CoordStruct::Empty; + + auto pTypePassenger = pPassenger->GetTechnoType(); + CoordStruct finalLocation = CoordStruct::Empty; + short extraDistanceX = 1; + short extraDistanceY = 1; + SpeedType speedType = pTypePassenger->SpeedType; + MovementZone movementZone = pTypePassenger->MovementZone; + + if (pTypePassenger->WhatAmI() == AbstractType::AircraftType) + { + speedType = SpeedType::Track; + movementZone = MovementZone::Normal; + } + + CellStruct placeCoords = pThis->GetCell()->MapCoords - CellStruct { (short)(extraDistanceX / 2), (short)(extraDistanceY / 2) }; + placeCoords = MapClass::Instance->NearByLocation(placeCoords, speedType, -1, movementZone, false, extraDistanceX, extraDistanceY, true, false, false, false, CellStruct::Empty, false, false); + + if (auto pCell = MapClass::Instance->TryGetCellAt(placeCoords)) + { + pPassenger->OnBridge = pCell->ContainsBridge(); + finalLocation = pCell->GetCoordsWithBridge(); + } + + return finalLocation; +} + // ============================= // load / save diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index 3a8107ca88..14e57c844d 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -9,6 +9,43 @@ #include #include +struct MapPathCellElement +{ + int Distance = -1; + int X = -1; + int Y = -1; + + //need to define a == operator so it can be used in array classes + bool operator==(const MapPathCellElement& other) const + { + return (X == other.X && Y == other.Y); + } + + //unequality + bool operator!=(const MapPathCellElement& other) const + { + return (X != other.X || Y != other.Y); + } + + bool operator<(const MapPathCellElement& other) const + { + return (Distance < other.Distance); + } + + bool operator>(const MapPathCellElement& other) const + { + return (Distance > other.Distance); + } + + CellStruct ToCellStruct() const + { + CellStruct c; + c.X = (short)X; + c.Y = (short)Y; + return c; + } +}; + class BulletClass; class TechnoExt @@ -112,4 +149,5 @@ class TechnoExt static void DrawSelfHealPips(TechnoClass* pThis, Point2D* pLocation, RectangleStruct* pBounds); static void ApplyGainedSelfHeal(TechnoClass* pThis); static void SyncIronCurtainStatus(TechnoClass* pFrom, TechnoClass* pTo); + static CoordStruct PassengerKickOutLocation(TechnoClass* pThis, FootClass* pPassenger); }; From 6cdb227a8735df1f0372e983a6500101ddabc81d Mon Sep 17 00:00:00 2001 From: FS-21 Date: Tue, 8 Nov 2022 02:41:36 +0100 Subject: [PATCH 04/17] Point to latest YRpp with the required change --- YRpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/YRpp b/YRpp index e8794ade27..de4bb11731 160000 --- a/YRpp +++ b/YRpp @@ -1 +1 @@ -Subproject commit e8794ade27cbf6122a508f0c186f0c9a4262f7a9 +Subproject commit de4bb1173114eff380cb8d5662f2af476434a357 From e016d414a66bb24c7822dcd557baf676875c8948 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Tue, 8 Nov 2022 06:58:42 +0100 Subject: [PATCH 05/17] Script action renaming and docs Now is the action 10104 and added documentation. --- CREDITS.md | 1 + docs/AI-Scripting-and-Mapping.md | 17 +++++++++++++++++ docs/Whats-New.md | 1 + src/Ext/Script/Body.cpp | 6 +++--- src/Ext/Script/Body.h | 4 ++-- 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/CREDITS.md b/CREDITS.md index a97ac90464..3be83727b1 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -118,6 +118,7 @@ This page lists all the individual contributions to the project by their author. - Shared ammo logic - Customizable FLH when infantry is prone or deployed - Initial strength for cloned infantry + - Script action for repairing destroyed bridges - **Starkku**: - Warhead shield penetration & breaking - Strafing aircraft weapon customization diff --git a/docs/AI-Scripting-and-Mapping.md b/docs/AI-Scripting-and-Mapping.md index 7f2c3039e7..b56a4c4652 100644 --- a/docs/AI-Scripting-and-Mapping.md +++ b/docs/AI-Scripting-and-Mapping.md @@ -171,6 +171,23 @@ In `aimd.ini`: [SOMESCRIPTTYPE] ; ScriptType x=10103,0 ``` +##### `10104` 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=10104,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 diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 8da2f2834c..b29fa6c05e 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -254,6 +254,7 @@ New: - Forcing specific weapon against cloaked or disguised targets (by Starkku) - Customizable ROF random delay (by Starkku) - Animation with `Tiled=yes` now supports `CustomPalette` (by ststl) +- Script action for repairing destroyed bridges (by FS-21) Vanilla fixes: - Allow AI to repair structures built from base nodes/trigger action 125/SW delivery in single player missions (by Trsdy) diff --git a/src/Ext/Script/Body.cpp b/src/Ext/Script/Body.cpp index b9518b0f1a..bd1ccf3945 100644 --- a/src/Ext/Script/Body.cpp +++ b/src/Ext/Script/Body.cpp @@ -210,9 +210,9 @@ void ScriptExt::ProcessAction(TeamClass* pTeam) // Start Timed Jump that jumps to the same line when the countdown finish (in frames) ScriptExt::Set_ForceJump_Countdown(pTeam, true, -1); break; - case PhobosScripts::CaptureDestroyedBridge: + case PhobosScripts::RepairDestroyedBridge: // Start Timed Jump that jumps to the same line when the countdown finish (in frames) - ScriptExt::CaptureDestroyedBridge(pTeam, -1); + ScriptExt::RepairDestroyedBridge(pTeam, -1); break; default: // Do nothing because or it is a wrong Action number or it is an Ares/YR action... @@ -3093,7 +3093,7 @@ void ScriptExt::Stop_ForceJump_Countdown(TeamClass *pTeam) Debug::Log("DEBUG: [%s] [%s](line: %d = %d,%d): Stopped Timed Jump\n", pTeam->Type->ID, pScript->Type->ID, pScript->CurrentMission, pScript->Type->ScriptActions[pScript->CurrentMission].Action, pScript->Type->ScriptActions[pScript->CurrentMission].Argument); } -void ScriptExt::CaptureDestroyedBridge(TeamClass* pTeam, int mode = -1) +void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) { if (!pTeam) return; diff --git a/src/Ext/Script/Body.h b/src/Ext/Script/Body.h index b5ab722bd3..e3e34a9f65 100644 --- a/src/Ext/Script/Body.h +++ b/src/Ext/Script/Body.h @@ -57,7 +57,7 @@ enum class PhobosScripts : unsigned int WaitUntilFullAmmo = 10101, GatherAroundLeader = 10102, LoadIntoTransports = 10103, - CaptureDestroyedBridge = 99999, + RepairDestroyedBridge = 10104, // Range 12000-12999 are suplementary/setup pre-actions WaitIfNoTarget = 12000, @@ -218,7 +218,7 @@ class ScriptExt static void VariableOperationHandler(TeamClass* pTeam, int nVariable, int Number); template static void VariableBinaryOperationHandler(TeamClass* pTeam, int nVariable, int nVarToOperate); - static void CaptureDestroyedBridge(TeamClass* pTeam, int mode); + static void RepairDestroyedBridge(TeamClass* pTeam, int mode); static bool FindLinkedPath(TeamClass* pTeam, TechnoClass* pThis, TechnoClass* pTarget); From f363188233739545e43a5dcc2fb4f7b2373e9eb4 Mon Sep 17 00:00:00 2001 From: Belonit <54427022+Belonit@users.noreply.github.com> Date: Fri, 25 Nov 2022 08:36:04 +0300 Subject: [PATCH 06/17] Fix trailing whitespace --- src/Ext/Script/Body.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ext/Script/Body.cpp b/src/Ext/Script/Body.cpp index bd1ccf3945..424c537666 100644 --- a/src/Ext/Script/Body.cpp +++ b/src/Ext/Script/Body.cpp @@ -3473,7 +3473,7 @@ bool ScriptExt::FindLinkedPath(TeamClass* pTeam, TechnoClass* pThis = nullptr, T // Extract the first element of MapPath_Queue for analyzing it MapPathCellElement element = pTeamData->MapPath_Queue.at(0); pTeamData->MapPath_Queue.erase(pTeamData->MapPath_Queue.begin()); - + // Check cells around the selected cell, it only stops if we reach the destination of the queue is empty for (int i = element.X - 1; (i <= element.X + 1) && !found; i++) { From 8f88565ec6578de40236d975b53b08e8dcf240f4 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Sun, 28 May 2023 15:47:52 +0200 Subject: [PATCH 07/17] YRpp --- YRpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/YRpp b/YRpp index de4bb11731..8346eec1aa 160000 --- a/YRpp +++ b/YRpp @@ -1 +1 @@ -Subproject commit de4bb1173114eff380cb8d5662f2af476434a357 +Subproject commit 8346eec1aaea4ce9f504ddff65943e84cf5bcc6f From 68d181d50980aa6b2fac80e3fa62abd1c3f11975 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Fri, 29 Sep 2023 16:26:56 +0200 Subject: [PATCH 08/17] Code rewrite --- src/Ext/Script/Body.cpp | 418 ++++++++++------------------------------ src/Ext/Script/Body.h | 2 - src/Ext/Team/Body.cpp | 9 +- src/Ext/Team/Body.h | 18 +- 4 files changed, 110 insertions(+), 337 deletions(-) diff --git a/src/Ext/Script/Body.cpp b/src/Ext/Script/Body.cpp index 424c537666..083bfba7ce 100644 --- a/src/Ext/Script/Body.cpp +++ b/src/Ext/Script/Body.cpp @@ -3098,78 +3098,46 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) if (!pTeam) return; - auto pScript = pTeam->CurrentScript; - - auto pTeamData = TeamExt::ExtMap.Find(pTeam); + auto const pTeamData = TeamExt::ExtMap.Find(pTeam); if (!pTeamData) - { - pTeam->StepCompleted = true; - Debug::Log("DEBUG: [%s] [%s] (line: %d = %d,%d) Jump to next line: %d = %d,%d -> (Reason: ExtData found)\n", - pTeam->Type->ID, - pScript->Type->ID, - pScript->CurrentMission, - pScript->Type->ScriptActions[pScript->CurrentMission].Action, - pScript->Type->ScriptActions[pScript->CurrentMission].Argument, - pScript->CurrentMission + 1, - pScript->Type->ScriptActions[pScript->CurrentMission + 1].Action, - pScript->Type->ScriptActions[pScript->CurrentMission + 1].Argument); - return; - } - // Save all Bridge Repair Hut Structures for saving time in the team actions. The worst case-scenario is if the map doesn't have bridges - if (pTeamData->MapPath_BridgeRepairHuts.Count == 0) + auto pScript = pTeam->CurrentScript; + + // 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) - { - auto pBuilding = static_cast(pTechno); - if (!pBuilding) - continue; - - if (pBuilding->Type->BridgeRepairHut) - pTeamData->MapPath_BridgeRepairHuts.AddItem(pTechno); - } - } - } - - //DynamicVectorClass bridgeRepairHuts; - bool isReachable = false; + if (pTechno->WhatAmI() != AbstractType::Building) + continue; - if (pTeamData->MapPath_InProgress - && pTeamData->MapPath_StartTechno - && pTeamData->MapPath_EndTechno) - { - // Continue the search process - isReachable = ScriptExt::FindLinkedPath(pTeam, pTeamData->MapPath_StartTechno, pTeamData->MapPath_EndTechno); + auto pBuilding = static_cast(pTechno); + if (!pBuilding) + continue; - // The operation didn't end, it will continue in the next game frame - if (pTeamData->MapPath_InProgress) - return; + if (pBuilding->Type->BridgeRepairHut) + pTeamData->BridgeRepairHuts.push_back(pTechno); + } - if (isReachable) + if (pTeamData->BridgeRepairHuts.size() == 0) { - CellStruct cell = pTeamData->MapPath_EndTechno->GetCell()->MapCoords; - bool isLinkedBridgeDestroyed = MapClass::Instance->IsLinkedBridgeDestroyed(cell); + pTeam->StepCompleted = true; + Debug::Log("[%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, + pScript->CurrentMission, + pScript->Type->ScriptActions[pScript->CurrentMission].Action, + pScript->Type->ScriptActions[pScript->CurrentMission].Argument, + pScript->CurrentMission + 1, + pScript->Type->ScriptActions[pScript->CurrentMission + 1].Action, + pScript->Type->ScriptActions[pScript->CurrentMission + 1].Argument); - if (isLinkedBridgeDestroyed) - pTeamData->MapPath_ValidBridgeRepairHuts.AddItem(pTeamData->MapPath_EndTechno); + return; } - - if (pTeamData->MapPath_CheckedBridgeRepairHuts.FindItemIndex(pTeamData->MapPath_EndTechno) < 0) - pTeamData->MapPath_CheckedBridgeRepairHuts.AddItem(pTeamData->MapPath_EndTechno); - } - else - { - // Or is the first run or someone in the operation died so it should start again - pTeamData->MapPath_InProgress = false; - pTeamData->MapPath_StartTechno = nullptr; - pTeamData->MapPath_EndTechno = nullptr; - pTeamData->MapPath_ValidBridgeRepairHuts.Clear(); - pTeamData->MapPath_CheckedBridgeRepairHuts.Clear(); } + // Reset Team's target if the current target isn't a repair hut if (pTeam->Focus) { if (pTeam->Focus->WhatAmI() != AbstractType::Building) @@ -3188,7 +3156,7 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) { CellStruct cell = pBuilding->GetCell()->MapCoords; - // If the Bridge was repaired then isn't valid anymore + // If the Bridge was repaired then the repair hut isn't valid anymore if (!MapClass::Instance->IsLinkedBridgeDestroyed(cell)) pTeam->Focus = nullptr; } @@ -3196,39 +3164,43 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) } TechnoClass* selectedTarget = pTeam->Focus ? static_cast(pTeam->Focus) : nullptr; - DynamicVectorClass engineers; - DynamicVectorClass otherTeamMembers; + bool isEngineerAmphibious = false; + std::vector engineers; + std::vector otherTeamMembers; - // If there are no engineers end this script action + // Check if there are no engineers for (auto pUnit = pTeam->FirstUnit; pUnit; pUnit = pUnit->NextTeamMember) { - if (pUnit - && pUnit->IsAlive - && !pUnit->InLimbo - && !pUnit->Transporter - && !pUnit->TemporalTargetingMe - && !pUnit->BeingWarpedOut) + if (!IsUnitAvailable(pUnit, true)) + continue; + + if (pUnit->WhatAmI() == AbstractType::Infantry) { - if (pUnit->WhatAmI() == AbstractType::Infantry) - { - auto pInf = static_cast(pUnit); + auto pInf = static_cast(pUnit); - if (pInf->IsEngineer()) + if (pInf->IsEngineer()) + { + if (pUnit->GetTechnoType()->MovementZone == MovementZone::Amphibious + || pUnit->GetTechnoType()->MovementZone == MovementZone::AmphibiousCrusher + || pUnit->GetTechnoType()->MovementZone == MovementZone::AmphibiousDestroyer) { - engineers.AddItem(pInf); - continue; + isEngineerAmphibious = true; } - } - // These units will receive a different command - otherTeamMembers.AddItem(pUnit); + engineers.push_back(pUnit); + + continue; + } } + + // Non-engineers will receive a different command + otherTeamMembers.push_back(pUnit); } - if (engineers.Count == 0) + if (engineers.size() == 0) { pTeam->StepCompleted = true; - Debug::Log("DEBUG: [%s] [%s] (line: %d = %d,%d) Jump to next line: %d = %d,%d -> (Reason: Team has no engineers)\n", + Debug::Log("[%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, pScript->CurrentMission, @@ -3237,98 +3209,79 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) pScript->CurrentMission + 1, pScript->Type->ScriptActions[pScript->CurrentMission + 1].Action, pScript->Type->ScriptActions[pScript->CurrentMission + 1].Argument); - pTeamData->MapPath_ValidBridgeRepairHuts.Clear(); - pTeamData->MapPath_CheckedBridgeRepairHuts.Clear(); return; } + std::vector validHuts; + if (!selectedTarget) { - // Looking for BridgeRepairHut=yes structures - //auto anyEngineer = engineers.GetItem(0); - for (auto pTechno : pTeamData->MapPath_BridgeRepairHuts) + + for (auto pTechno : pTeamData->BridgeRepairHuts) { - // If it was previously inserted we will ignore it - if (pTeamData->MapPath_ValidBridgeRepairHuts.FindItemIndex(pTechno) >= 0) - continue; + CellStruct cell = pTechno->GetCell()->MapCoords; - // If it was previously checked we will ignore it - if (pTeamData->MapPath_CheckedBridgeRepairHuts.FindItemIndex(pTechno) >= 0) + // Skip all huts linked to non-destroyed bridges + if (!MapClass::Instance->IsLinkedBridgeDestroyed(cell)) continue; - auto engineer = static_cast(engineers.GetItem(0)); - isReachable = ScriptExt::FindLinkedPath(pTeam, engineer, pTechno); - - // This process didn't end. It will continue in the next game frame - if (pTeamData->MapPath_InProgress) - return; - - if (isReachable) + if (isEngineerAmphibious) { - CellStruct cell = pTechno->GetCell()->MapCoords; - bool isLinkedBridgeDestroyed = MapClass::Instance->IsLinkedBridgeDestroyed(cell); - - if (isLinkedBridgeDestroyed) - pTeamData->MapPath_ValidBridgeRepairHuts.AddItem(pTechno); + validHuts.push_back(pTechno); } + else + { + auto coords = pTechno->GetCenterCoords(); - pTeamData->MapPath_CheckedBridgeRepairHuts.AddItem(pTechno); - } - - if (pTeamData->MapPath_ValidBridgeRepairHuts.Count == 0) - { - pTeam->StepCompleted = true; - Debug::Log("DEBUG: [%s] [%s] (line: %d = %d,%d) Jump to next line: %d = %d,%d -> (Reason: This map has no Bridge Repair Huts)\n", - pTeam->Type->ID, - pScript->Type->ID, - pScript->CurrentMission, - pScript->Type->ScriptActions[pScript->CurrentMission].Action, - pScript->Type->ScriptActions[pScript->CurrentMission].Argument, - pScript->CurrentMission + 1, - pScript->Type->ScriptActions[pScript->CurrentMission + 1].Action, - pScript->Type->ScriptActions[pScript->CurrentMission + 1].Argument); - pTeamData->MapPath_ValidBridgeRepairHuts.Clear(); - pTeamData->MapPath_CheckedBridgeRepairHuts.Clear(); - - return; + // Only huts reachable by the (first) engineer are valid + if (engineers.at(0)->IsInSameZoneAsCoords(&pTechno->GetCenterCoords())) + validHuts.push_back(pTechno); + } } - if (mode < 0) - mode = pTeam->CurrentScript->Type->ScriptActions[pTeam->CurrentScript->CurrentMission].Argument; - - // Pick the nearest destroyed bridge + // Find the best repair hut int bestVal = -1; + TechnoClass* selectedTarget = nullptr; - if (mode < 0) + for (auto pTechno : validHuts) { - // Pick a random bridge - selectedTarget = pTeamData->MapPath_ValidBridgeRepairHuts.GetItem(ScenarioClass::Instance->Random.RandomRanged(0, pTeamData->MapPath_ValidBridgeRepairHuts.Count - 1)); - } - else - { - for (auto pHut : pTeamData->MapPath_ValidBridgeRepairHuts) + //auto hut = pTechno; + + /*if (mode < 0) + mode = pTeam->CurrentScript->Type->ScriptActions[pTeam->CurrentScript->CurrentMission].Argument;*/ + + if (mode < 0) { - if (mode > 0) + // Pick a random bridge + selectedTarget = validHuts.at(ScenarioClass::Instance->Random.RandomRanged(0, validHuts.size() - 1)); + break; + } + else + { + for (auto pHut : validHuts) { - // Pick the farthest target - int value = engineers.GetItem(0)->DistanceFrom(pHut); // Note: distance is in leptons (*256) - - if (value >= bestVal || bestVal < 0) + if (mode > 0) { - bestVal = value; - selectedTarget = pHut; - } - } - else - { - // Pick the closest target - int value = engineers.GetItem(0)->DistanceFrom(pHut); // Note: distance is in leptons (*256) + // Pick the farthest target + int value = engineers.at(0)->DistanceFrom(pHut); // Note: distance is in leptons (*256) - if (value < bestVal || bestVal < 0) + if (value >= bestVal || bestVal < 0) + { + bestVal = value; + selectedTarget = pHut; + } + } + else { - bestVal = value; - selectedTarget = pHut; + // Pick the closest target + int value = engineers.at(0)->DistanceFrom(pHut); // Note: distance is in leptons (*256) + + if (value < bestVal || bestVal < 0) + { + bestVal = value; + selectedTarget = pHut; + } } } } @@ -3337,8 +3290,10 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) if (!selectedTarget) { + validHuts.clear(); pTeam->StepCompleted = true; - Debug::Log("DEBUG: [%s] [%s] (line: %d = %d,%d) Jump to next line: %d = %d,%d -> (Reason: Can not select a Bridge Repair Hut)\n", + + Debug::Log("DEBUG: [%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, pScript->CurrentMission, @@ -3347,15 +3302,13 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) pScript->CurrentMission + 1, pScript->Type->ScriptActions[pScript->CurrentMission + 1].Action, pScript->Type->ScriptActions[pScript->CurrentMission + 1].Argument); - pTeamData->MapPath_ValidBridgeRepairHuts.Clear(); - pTeamData->MapPath_CheckedBridgeRepairHuts.Clear(); return; } + // Setting the team's target & mission pTeam->Focus = selectedTarget; - pTeamData->MapPath_ValidBridgeRepairHuts.Clear(); - pTeamData->MapPath_CheckedBridgeRepairHuts.Clear(); + validHuts.clear(); for (auto engineer : engineers) { @@ -3366,7 +3319,7 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) } } - if (otherTeamMembers.Count > 0) + if (otherTeamMembers.size() > 0) { double closeEnough = RulesClass::Instance->CloseEnough; // Note: this value is in leptons (*256) @@ -3397,160 +3350,3 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) } } } - -// Find the shortest valid path to the destination, if possible -bool ScriptExt::FindLinkedPath(TeamClass* pTeam, TechnoClass* pThis = nullptr, TechnoClass* pTarget = nullptr) -{ - auto pTeamData = TeamExt::ExtMap.Find(pTeam); - if (!pTeamData) - return false; - - CellStruct startCell = CellStruct::Empty; - CellStruct endCell = CellStruct::Empty; - - if (!pTeamData->MapPath_InProgress) - { - // First time that is execued this process - if (pThis == nullptr || pTarget == nullptr) - return false; - - pTeamData->MapPath_StartTechno = pThis; - pTeamData->MapPath_EndTechno = pTarget; - - pTeamData->MapPath_InProgress = true; - pTeamData->MapPath_Grid.clear(); - pTeamData->MapPath_Queue.clear(); - - // Creates a "map" with the same size of the original and each "cell" is a boolean value that means if the cell was evaluated or not. Evaluated "cells" get ignored in posterior checks - int matrixX = MapClass::Instance->MapCoordBounds.Right; - int matrixY = MapClass::Instance->MapCoordBounds.Bottom; - - pTeamData->MapPath_Grid = std::vector>(matrixX, std::vector(matrixY, false)); - - startCell = pThis->GetCell()->MapCoords; - endCell = pTarget->GetCell()->MapCoords; - - // The first element of this path is the unit's location - MapPathCellElement startElement; - startElement.X = startCell.X; - startElement.Y = startCell.Y; - startElement.Distance = pThis->DistanceFrom(pTarget); // Note: distance is in leptons (*256) - pTeamData->MapPath_Queue.push_back(startElement); - } - else - { - // We'll resume the previous unfinished analysis that was so time expensible that was splitted in multiple parts - if (pThis == nullptr || pTarget == nullptr) - { - pTeamData->MapPath_InProgress = false; - pTeamData->MapPath_Grid.clear(); - pTeamData->MapPath_Queue.clear(); - pTeamData->MapPath_StartTechno = nullptr; - pTeamData->MapPath_EndTechno = nullptr; - - return false; - } - - endCell = pTarget->GetCell()->MapCoords; - } - - bool found = false; - - // If we don't split this operation in multiple frames the game will stop half a second in the worst case scenarios for finding the destination (or if directly there is no valid path) and that's unaceptable. - // We'll use a number of checks limiter and when it reaches 0 we will stop the process and continue it in the next frame. - // Lower value == more frames required for calculating a valid path (if exists). - // Higher value == less game frames required for calculating a valid path but noticeable FPS drops. - int nChecksLeft = 512; - - while ((pTeamData->MapPath_Queue.size() > 0) && !found) - { - // If counter reached the limit we will stop the process and continue it in the next frame. - if (nChecksLeft <= 0) - return false; - - nChecksLeft--; - - // Extract the first element of MapPath_Queue for analyzing it - MapPathCellElement element = pTeamData->MapPath_Queue.at(0); - pTeamData->MapPath_Queue.erase(pTeamData->MapPath_Queue.begin()); - - // Check cells around the selected cell, it only stops if we reach the destination of the queue is empty - for (int i = element.X - 1; (i <= element.X + 1) && !found; i++) - { - for (int j = element.Y - 1; (j <= element.Y + 1) && !found; j++) - { - CellStruct nCell; - nCell.X = (short)i; - nCell.Y = (short)j; - - // If reached the destination end the process or the target moved into an evaluated area the process finished - if ((nCell.X == endCell.X && nCell.Y == endCell.Y) - || (pTeamData->MapPath_Grid[endCell.X][endCell.Y])) - { - found = true; - break; - } - - // Only check nonvisited cells - if (!pTeamData->MapPath_Grid[i][j]) - { - if (MapClass::Instance->IsWithinUsableArea(nCell, false)) - { - auto pCell = MapClass::Instance->TryGetCellAt(nCell); - if (pThis->IsCellOccupied(pCell, -1, -1, nullptr, false) != Move::OK) - pTeamData->MapPath_Grid[i][j] = true; - - if (!pTeamData->MapPath_Grid[i][j]) - { - // If is a valid cell we'll queue it for future checks - MapPathCellElement newElement; - newElement.X = (short)i; - newElement.Y = (short)j; - newElement.Distance = pTarget->DistanceFrom(pCell); // Note: distance is in leptons (*256) - pTeamData->MapPath_Grid[i][j] = true; - - // Find the right position in the vector. Sorted by ascendent distance; - if (pTeamData->MapPath_Queue.size() == 0) - { - pTeamData->MapPath_Queue.push_back(newElement); - } - else - { - auto index = pTeamData->MapPath_Queue.begin(); - bool inserted = false; - - for (unsigned int k = 0; k < pTeamData->MapPath_Queue.size(); k++) - { - if (newElement < pTeamData->MapPath_Queue.at(k)) - { - pTeamData->MapPath_Queue.insert(index, newElement); - inserted = true; - - break; - } - - ++index; - } - - if (!inserted) - pTeamData->MapPath_Queue.push_back(newElement); - } - } - } - else - { - // Mark the unusable cells as visited - pTeamData->MapPath_Grid[i][j] = true; - } - } - } - } - } - - // Ended. Cleanning the mess - pTeamData->MapPath_InProgress = false; - pTeamData->MapPath_Grid.clear(); - pTeamData->MapPath_Queue.clear(); - - return found; -} diff --git a/src/Ext/Script/Body.h b/src/Ext/Script/Body.h index e3e34a9f65..f003456133 100644 --- a/src/Ext/Script/Body.h +++ b/src/Ext/Script/Body.h @@ -219,8 +219,6 @@ class ScriptExt template static void VariableBinaryOperationHandler(TeamClass* pTeam, int nVariable, int nVarToOperate); static void RepairDestroyedBridge(TeamClass* pTeam, int mode); - static bool FindLinkedPath(TeamClass* pTeam, TechnoClass* pThis, TechnoClass* pTarget); - static ExtContainer ExtMap; diff --git a/src/Ext/Team/Body.cpp b/src/Ext/Team/Body.cpp index 22cadfb9b1..60f0f6391e 100644 --- a/src/Ext/Team/Body.cpp +++ b/src/Ext/Team/Body.cpp @@ -22,14 +22,7 @@ void TeamExt::ExtData::Serialize(T& Stm) .Process(this->ForceJump_InitialCountdown) .Process(this->ForceJump_RepeatMode) .Process(this->TeamLeader) - .Process(this->MapPath_Grid) - .Process(this->MapPath_Queue) - .Process(this->MapPath_InProgress) - .Process(this->MapPath_StartTechno) - .Process(this->MapPath_EndTechno) - .Process(this->MapPath_BridgeRepairHuts) - .Process(this->MapPath_ValidBridgeRepairHuts) - .Process(this->MapPath_CheckedBridgeRepairHuts) + .Process(this->BridgeRepairHuts) ; } diff --git a/src/Ext/Team/Body.h b/src/Ext/Team/Body.h index a1f9c48ec4..3da058d613 100644 --- a/src/Ext/Team/Body.h +++ b/src/Ext/Team/Body.h @@ -28,14 +28,7 @@ class TeamExt int ForceJump_InitialCountdown; bool ForceJump_RepeatMode; FootClass* TeamLeader; - std::vector> MapPath_Grid; // Used for marking visited/analyzed cells - std::vector MapPath_Queue; // Cells that will be analyzed for finding a path - bool MapPath_InProgress; - TechnoClass* MapPath_StartTechno; - TechnoClass* MapPath_EndTechno; - DynamicVectorClass MapPath_BridgeRepairHuts; - DynamicVectorClass MapPath_ValidBridgeRepairHuts; - DynamicVectorClass MapPath_CheckedBridgeRepairHuts; + std::vector BridgeRepairHuts; ExtData(TeamClass* OwnerObject) : Extension(OwnerObject) , WaitNoTargetAttempts { 0 } @@ -50,14 +43,7 @@ class TeamExt , ForceJump_InitialCountdown { -1 } , ForceJump_RepeatMode { false } , TeamLeader { nullptr } - , MapPath_Grid { } - , MapPath_Queue { } - , MapPath_InProgress { false } - , MapPath_StartTechno { nullptr } - , MapPath_EndTechno { nullptr } - , MapPath_BridgeRepairHuts { } - , MapPath_ValidBridgeRepairHuts { } - , MapPath_CheckedBridgeRepairHuts { } + , BridgeRepairHuts { } { } virtual ~ExtData() = default; From 6fb6873cad05d50489805975650764230f349fd4 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Fri, 29 Sep 2023 16:32:54 +0200 Subject: [PATCH 09/17] Update docs Script action number moved from 10104 to 10105. --- docs/AI-Scripting-and-Mapping.md | 25 +++++++++++++------------ src/Ext/Script/Body.h | 2 +- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/docs/AI-Scripting-and-Mapping.md b/docs/AI-Scripting-and-Mapping.md index f60c5bca7e..df3a5fc566 100644 --- a/docs/AI-Scripting-and-Mapping.md +++ b/docs/AI-Scripting-and-Mapping.md @@ -182,7 +182,18 @@ In `aimd.ini`: [SOMESCRIPTTYPE] ; ScriptType x=10103,0 ``` -##### `10104` Repair Destroyed Bridge + +##### `10104` Chronoshift to Enemy Base + +- Chronoshifts the members of the TeamType using first available `Type=Chronosphere` superweapon to a location within `[General]` -> `AISafeDistance` (plus the additional distance defined in parameter, can be negative) cells from enemy house's base. The superweapon must be charged up to atleast `[General]` -> `AIMinorSuperReadyPercent` percentage of its recharge time to be available for use by this action. + +In `aimd.ini`: +```ini +[SOMESCRIPTTYPE] ; ScriptType +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. @@ -190,7 +201,7 @@ x=10103,0 In `aimd.ini`: ```ini [SOMESCRIPTTYPE] ; ScriptType -x=10104,n ; integer, mode for selecting Bridge Repair Huts +x=10105,n ; integer, mode for selecting Bridge Repair Huts ``` - The possible argument values are: @@ -200,16 +211,6 @@ x=10104,n ; integer, mode for selecting Bridge Repair Huts | 1 | Pick the Farthest | | -1 | Pick Random | -##### `10104` Chronoshift to Enemy Base - -- Chronoshifts the members of the TeamType using first available `Type=Chronosphere` superweapon to a location within `[General]` -> `AISafeDistance` (plus the additional distance defined in parameter, can be negative) cells from enemy house's base. The superweapon must be charged up to atleast `[General]` -> `AIMinorSuperReadyPercent` percentage of its recharge time to be available for use by this action. - -In `aimd.ini`: -```ini -[SOMESCRIPTTYPE] ; ScriptType -x=10104,n ; integer, additional distance in cells -``` - ### `12000-12999` Suplementary/Setup Pre-actions #### `12000` Wait if No Target Found diff --git a/src/Ext/Script/Body.h b/src/Ext/Script/Body.h index fd99d32c34..d89968164a 100644 --- a/src/Ext/Script/Body.h +++ b/src/Ext/Script/Body.h @@ -58,7 +58,7 @@ enum class PhobosScripts : unsigned int GatherAroundLeader = 10102, LoadIntoTransports = 10103, ChronoshiftToEnemyBase = 10104, - RepairDestroyedBridge = 10104, + RepairDestroyedBridge = 10105, // Range 12000-12999 are suplementary/setup pre-actions WaitIfNoTarget = 12000, From 463391c1cad70bbbf8977815afe7fa5682f3a7fe Mon Sep 17 00:00:00 2001 From: FS-21 Date: Fri, 29 Sep 2023 18:20:48 +0200 Subject: [PATCH 10/17] fixes & tweaks --- src/Ext/Script/Body.cpp | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/Ext/Script/Body.cpp b/src/Ext/Script/Body.cpp index 901757e37f..58df4bac0f 100644 --- a/src/Ext/Script/Body.cpp +++ b/src/Ext/Script/Body.cpp @@ -1331,7 +1331,7 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) if (pTeamData->BridgeRepairHuts.size() == 0) { pTeam->StepCompleted = true; - Debug::Log("[%s] [%s] (line: %d = %d,%d) Jump to next line: %d = %d,%d -> (Reason: No repair huts found)\n", + ScriptExt::Log("AI Scripts - [%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, pScript->CurrentMission, @@ -1382,6 +1382,13 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) if (!IsUnitAvailable(pUnit, true)) continue; + if (!pTeam->Focus) + { + pUnit->SetTarget(nullptr); + pUnit->SetDestination(nullptr, false); + pUnit->ForceMission(Mission::Guard); + } + if (pUnit->WhatAmI() == AbstractType::Infantry) { auto pInf = static_cast(pUnit); @@ -1408,7 +1415,7 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) if (engineers.size() == 0) { pTeam->StepCompleted = true; - Debug::Log("[%s] [%s] (line: %d = %d,%d) Jump to next line: %d = %d,%d -> (Reason: Team has no engineers)\n", + ScriptExt::Log("AI Scripts - [%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, pScript->CurrentMission, @@ -1443,21 +1450,20 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) auto coords = pTechno->GetCenterCoords(); // Only huts reachable by the (first) engineer are valid - if (engineers.at(0)->IsInSameZoneAsCoords(&pTechno->GetCenterCoords())) + if (engineers.at(0)->IsInSameZoneAsCoords(pTechno->GetCenterCoords())) validHuts.push_back(pTechno); } } // Find the best repair hut int bestVal = -1; - TechnoClass* selectedTarget = nullptr; for (auto pTechno : validHuts) { //auto hut = pTechno; - /*if (mode < 0) - mode = pTeam->CurrentScript->Type->ScriptActions[pTeam->CurrentScript->CurrentMission].Argument;*/ + if (mode < 0) + mode = pTeam->CurrentScript->Type->ScriptActions[pTeam->CurrentScript->CurrentMission].Argument; if (mode < 0) { @@ -1496,12 +1502,13 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) } } + validHuts.clear(); + if (!selectedTarget) { - validHuts.clear(); pTeam->StepCompleted = true; - Debug::Log("DEBUG: [%s] [%s] (line: %d = %d,%d) Jump to next line: %d = %d,%d -> (Reason: Can not select a bridge repair hut)\n", + ScriptExt::Log("AI Scripts - [%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, pScript->CurrentMission, @@ -1516,7 +1523,6 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) // Setting the team's target & mission pTeam->Focus = selectedTarget; - validHuts.clear(); for (auto engineer : engineers) { @@ -1541,7 +1547,6 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) { // Reset previous command pFoot->SetTarget(nullptr); - pFoot->SetFocus(nullptr); pFoot->SetDestination(nullptr, false); pFoot->ForceMission(Mission::Guard); From 92338db118b378b796bf9d08c4c0d2e3f6133e62 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Tue, 30 Apr 2024 07:02:27 +0200 Subject: [PATCH 11/17] Script Action moved into its own file It is big enough. Also removed the struct MapPathCellElement because it was from an older obsolete feature. --- Phobos.vcxproj | 1 + src/Ext/Script/Body.BridgeRepair.cpp | 255 ++++++++++++++++++++++++++ src/Ext/Script/Body.cpp | 261 --------------------------- src/Ext/Techno/Body.h | 37 ---- 4 files changed, 256 insertions(+), 298 deletions(-) create mode 100644 src/Ext/Script/Body.BridgeRepair.cpp diff --git a/Phobos.vcxproj b/Phobos.vcxproj index 1ff7abb8a7..ce0136260b 100644 --- a/Phobos.vcxproj +++ b/Phobos.vcxproj @@ -43,6 +43,7 @@ + diff --git a/src/Ext/Script/Body.BridgeRepair.cpp b/src/Ext/Script/Body.BridgeRepair.cpp new file mode 100644 index 0000000000..fcd1d15e25 --- /dev/null +++ b/src/Ext/Script/Body.BridgeRepair.cpp @@ -0,0 +1,255 @@ +#include "Body.h" + +void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) +{ + if (!pTeam) + return; + + auto const pTeamData = TeamExt::ExtMap.Find(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; + + auto pBuilding = static_cast(pTechno); + if (!pBuilding) + continue; + + if (pBuilding->Type->BridgeRepairHut) + pTeamData->BridgeRepairHuts.push_back(pTechno); + } + + if (pTeamData->BridgeRepairHuts.size() == 0) + { + pTeam->StepCompleted = true; + ScriptExt::Log("[%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 + { + auto pBuilding = static_cast(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(pTeam->Focus) : nullptr; + bool isEngineerAmphibious = false; + std::vector engineers; + std::vector otherTeamMembers; + + // Check if there are no engineers + for (auto pUnit = pTeam->FirstUnit; pUnit; pUnit = pUnit->NextTeamMember) + { + if (!IsUnitAvailable(pUnit, true)) + continue; + + if (!pTeam->Focus) + { + pUnit->SetTarget(nullptr); + pUnit->SetDestination(nullptr, false); + pUnit->ForceMission(Mission::Guard); + } + + if (pUnit->WhatAmI() == AbstractType::Infantry) + { + auto pInf = static_cast(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("[%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 validHuts; + + if (!selectedTarget) + { + for (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 + { + auto coords = pTechno->GetCenterCoords(); + + // Only huts reachable by the (first) engineer are valid + if (engineers.at(0)->IsInSameZoneAsCoords(pTechno->GetCenterCoords())) + validHuts.push_back(pTechno); + } + } + + // Find the best repair hut + int bestVal = -1; + + for (auto pTechno : validHuts) + { + //auto hut = pTechno; + + 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)); + break; + } + else + { + for (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) + { + pTeam->StepCompleted = true; + + ScriptExt::Log("[%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); + + 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); + } + } +} diff --git a/src/Ext/Script/Body.cpp b/src/Ext/Script/Body.cpp index c716f6f030..6d16f690e0 100644 --- a/src/Ext/Script/Body.cpp +++ b/src/Ext/Script/Body.cpp @@ -1299,265 +1299,4 @@ void ScriptExt::Log(const char* pFormat, ...) va_end(args); } -void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) -{ - if (!pTeam) - return; - - auto const pTeamData = TeamExt::ExtMap.Find(pTeam); - if (!pTeamData) - return; - - auto pScript = pTeam->CurrentScript; - - // 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; - - auto pBuilding = static_cast(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 - [%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, - pScript->CurrentMission, - pScript->Type->ScriptActions[pScript->CurrentMission].Action, - pScript->Type->ScriptActions[pScript->CurrentMission].Argument, - pScript->CurrentMission + 1, - pScript->Type->ScriptActions[pScript->CurrentMission + 1].Action, - pScript->Type->ScriptActions[pScript->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 - { - auto pBuilding = static_cast(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(pTeam->Focus) : nullptr; - bool isEngineerAmphibious = false; - std::vector engineers; - std::vector otherTeamMembers; - - // Check if there are no engineers - for (auto pUnit = pTeam->FirstUnit; pUnit; pUnit = pUnit->NextTeamMember) - { - if (!IsUnitAvailable(pUnit, true)) - continue; - - if (!pTeam->Focus) - { - pUnit->SetTarget(nullptr); - pUnit->SetDestination(nullptr, false); - pUnit->ForceMission(Mission::Guard); - } - - if (pUnit->WhatAmI() == AbstractType::Infantry) - { - auto pInf = static_cast(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 - [%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, - pScript->CurrentMission, - pScript->Type->ScriptActions[pScript->CurrentMission].Action, - pScript->Type->ScriptActions[pScript->CurrentMission].Argument, - pScript->CurrentMission + 1, - pScript->Type->ScriptActions[pScript->CurrentMission + 1].Action, - pScript->Type->ScriptActions[pScript->CurrentMission + 1].Argument); - - return; - } - std::vector validHuts; - - if (!selectedTarget) - { - - for (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 - { - auto coords = pTechno->GetCenterCoords(); - - // Only huts reachable by the (first) engineer are valid - if (engineers.at(0)->IsInSameZoneAsCoords(pTechno->GetCenterCoords())) - validHuts.push_back(pTechno); - } - } - - // Find the best repair hut - int bestVal = -1; - - for (auto pTechno : validHuts) - { - //auto hut = pTechno; - - 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)); - break; - } - else - { - for (auto pHut : validHuts) - { - if (mode > 0) - { - // Pick the farthest target - int value = engineers.at(0)->DistanceFrom(pHut); // Note: distance is in leptons (*256) - - if (value >= bestVal || bestVal < 0) - { - bestVal = value; - selectedTarget = pHut; - } - } - else - { - // Pick the closest target - int value = engineers.at(0)->DistanceFrom(pHut); // Note: distance is in leptons (*256) - - if (value < bestVal || bestVal < 0) - { - bestVal = value; - selectedTarget = pHut; - } - } - } - } - } - } - - validHuts.clear(); - - if (!selectedTarget) - { - pTeam->StepCompleted = true; - - ScriptExt::Log("AI Scripts - [%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, - pScript->CurrentMission, - pScript->Type->ScriptActions[pScript->CurrentMission].Action, - pScript->Type->ScriptActions[pScript->CurrentMission].Argument, - pScript->CurrentMission + 1, - pScript->Type->ScriptActions[pScript->CurrentMission + 1].Action, - pScript->Type->ScriptActions[pScript->CurrentMission + 1].Argument); - - 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); - } - } -} diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index 7d206d0566..e538ca11e3 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -9,43 +9,6 @@ #include #include -struct MapPathCellElement -{ - int Distance = -1; - int X = -1; - int Y = -1; - - //need to define a == operator so it can be used in array classes - bool operator==(const MapPathCellElement& other) const - { - return (X == other.X && Y == other.Y); - } - - //unequality - bool operator!=(const MapPathCellElement& other) const - { - return (X != other.X || Y != other.Y); - } - - bool operator<(const MapPathCellElement& other) const - { - return (Distance < other.Distance); - } - - bool operator>(const MapPathCellElement& other) const - { - return (Distance > other.Distance); - } - - CellStruct ToCellStruct() const - { - CellStruct c; - c.X = (short)X; - c.Y = (short)Y; - return c; - } -}; - class BulletClass; class TechnoExt From 788ab042df4087af033e69c48f437694b955e6f2 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Wed, 1 May 2024 23:04:34 +0200 Subject: [PATCH 12/17] fix mistakes --- src/Ext/Script/Body.BridgeRepair.cpp | 54 ++++++++++++---------------- src/Ext/Techno/Body.cpp | 32 +---------------- src/Ext/Techno/Body.h | 3 +- 3 files changed, 25 insertions(+), 64 deletions(-) diff --git a/src/Ext/Script/Body.BridgeRepair.cpp b/src/Ext/Script/Body.BridgeRepair.cpp index fcd1d15e25..48b1a53a8d 100644 --- a/src/Ext/Script/Body.BridgeRepair.cpp +++ b/src/Ext/Script/Body.BridgeRepair.cpp @@ -91,7 +91,7 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) if (pUnit->WhatAmI() == AbstractType::Infantry) { - auto pInf = static_cast(pUnit); + auto const pInf = static_cast(pUnit); if (pInf->IsEngineer()) { @@ -131,7 +131,7 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) if (!selectedTarget) { - for (auto pTechno : pTeamData->BridgeRepairHuts) + for (auto const pTechno : pTeamData->BridgeRepairHuts) { CellStruct cell = pTechno->GetCell()->MapCoords; @@ -156,37 +156,30 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) // Find the best repair hut int bestVal = -1; - for (auto pTechno : validHuts) - { - //auto hut = pTechno; - - if (mode < 0) - mode = pTeam->CurrentScript->Type->ScriptActions[pTeam->CurrentScript->CurrentMission].Argument; + 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)); - break; - } - else + if (mode < 0) // Pick a random bridge + { + selectedTarget = validHuts.at(ScenarioClass::Instance->Random.RandomRanged(0, validHuts.size() - 1)); + } + else + { + for (auto const pHut : validHuts) { - for (auto pHut : validHuts) - { - int value = engineers.at(0)->DistanceFrom(pHut); // Note: distance is in leptons (*256) + int value = engineers.at(0)->DistanceFrom(pHut); // Note: distance is in leptons (*256) - bool isValidCandidate = false; + bool isValidCandidate = false; - if (mode <= 0) - isValidCandidate = value < bestVal; // Pick the closest target - else - isValidCandidate = value >= bestVal; // Pick the farthest target + 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; - } + if (isValidCandidate || bestVal < 0) + { + bestVal = value; + selectedTarget = pHut; } } } @@ -196,9 +189,7 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) if (!selectedTarget) { - pTeam->StepCompleted = true; - - ScriptExt::Log("[%s][%s] (line: %d = %d,%d) Jump to next line: %d = %d,%d -> (Reason: Can not select a bridge repair hut).\n", + ScriptExt::Log("[%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, @@ -208,6 +199,7 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) pScript->Type->ScriptActions[currentMission + 1].Action, pScript->Type->ScriptActions[currentMission + 1].Argument); + pTeam->StepCompleted = true; return; } diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index ab1891bb6c..a8b760e2ac 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -114,7 +114,7 @@ double TechnoExt::GetCurrentSpeedMultiplier(FootClass* pThis) (pThis->HasAbility(Ability::Faster) ? RulesClass::Instance->VeteranSpeed : 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; @@ -362,36 +362,6 @@ bool TechnoExt::IsTypeImmune(TechnoClass* pThis, TechnoClass* pSource) return false; } -CoordStruct TechnoExt::PassengerKickOutLocation(TechnoClass* pThis, FootClass* pPassenger) -{ - if (!pThis || !pPassenger) - return CoordStruct::Empty; - - auto pTypePassenger = pPassenger->GetTechnoType(); - CoordStruct finalLocation = CoordStruct::Empty; - short extraDistanceX = 1; - short extraDistanceY = 1; - SpeedType speedType = pTypePassenger->SpeedType; - MovementZone movementZone = pTypePassenger->MovementZone; - - if (pTypePassenger->WhatAmI() == AbstractType::AircraftType) - { - speedType = SpeedType::Track; - movementZone = MovementZone::Normal; - } - - CellStruct placeCoords = pThis->GetCell()->MapCoords - CellStruct { (short)(extraDistanceX / 2), (short)(extraDistanceY / 2) }; - placeCoords = MapClass::Instance->NearByLocation(placeCoords, speedType, -1, movementZone, false, extraDistanceX, extraDistanceY, true, false, false, false, CellStruct::Empty, false, false); - - if (auto pCell = MapClass::Instance->TryGetCellAt(placeCoords)) - { - pPassenger->OnBridge = pCell->ContainsBridge(); - finalLocation = pCell->GetCoordsWithBridge(); - } - - return finalLocation; -} - // ============================= // load / save diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index e538ca11e3..a556cbed9b 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -138,13 +138,12 @@ class TechnoExt static void DrawInsignia(TechnoClass* pThis, Point2D* pLocation, RectangleStruct* pBounds); static void ApplyGainedSelfHeal(TechnoClass* pThis); static void SyncIronCurtainStatus(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); static bool CanDeployIntoBuilding(UnitClass* pThis, bool noDeploysIntoDefaultValue = false); static bool IsTypeImmune(TechnoClass* pThis, TechnoClass* pSource); - static CoordStruct PassengerKickOutLocation(TechnoClass* pThis, FootClass* pPassenger); // WeaponHelpers.cpp static int PickWeaponIndex(TechnoClass* pThis, TechnoClass* pTargetTechno, AbstractClass* pTarget, int weaponIndexOne, int weaponIndexTwo, bool allowFallback = true, bool allowAAFallback = true); From fad5f95b537eaa6ad70258a7ebf8157e0835d4dd Mon Sep 17 00:00:00 2001 From: FS-21 Date: Fri, 3 May 2024 11:05:56 +0200 Subject: [PATCH 13/17] Tweak --- src/Ext/Script/Body.BridgeRepair.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ext/Script/Body.BridgeRepair.cpp b/src/Ext/Script/Body.BridgeRepair.cpp index 48b1a53a8d..ba8ac52167 100644 --- a/src/Ext/Script/Body.BridgeRepair.cpp +++ b/src/Ext/Script/Body.BridgeRepair.cpp @@ -79,7 +79,7 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) // Check if there are no engineers for (auto pUnit = pTeam->FirstUnit; pUnit; pUnit = pUnit->NextTeamMember) { - if (!IsUnitAvailable(pUnit, true)) + if (!TechnoExt::IsUnitAvailable(pUnit, true)) continue; if (!pTeam->Focus) From 79dbaad57b470e0370ecc6c12c18a700f76a4732 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Fri, 3 May 2024 11:07:24 +0200 Subject: [PATCH 14/17] Fix previous commit --- src/Ext/Script/Body.BridgeRepair.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ext/Script/Body.BridgeRepair.cpp b/src/Ext/Script/Body.BridgeRepair.cpp index ba8ac52167..2420ed66d1 100644 --- a/src/Ext/Script/Body.BridgeRepair.cpp +++ b/src/Ext/Script/Body.BridgeRepair.cpp @@ -79,7 +79,7 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) // Check if there are no engineers for (auto pUnit = pTeam->FirstUnit; pUnit; pUnit = pUnit->NextTeamMember) { - if (!TechnoExt::IsUnitAvailable(pUnit, true)) + if (!ScriptExt::IsUnitAvailable(pUnit, true)) continue; if (!pTeam->Focus) From 85ce43dcc886939f42d46c6ac44ae3b640cdc697 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Fri, 10 May 2024 11:15:52 +0200 Subject: [PATCH 15/17] Fix crash --- src/Ext/Script/Body.BridgeRepair.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/Ext/Script/Body.BridgeRepair.cpp b/src/Ext/Script/Body.BridgeRepair.cpp index 2420ed66d1..e4ecce16de 100644 --- a/src/Ext/Script/Body.BridgeRepair.cpp +++ b/src/Ext/Script/Body.BridgeRepair.cpp @@ -153,6 +153,22 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) } } + if (validHuts.size() == 0) + { + ScriptExt::Log("[%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; From 2a3e968e7d0e299126b227940b6fa82d6214a0d9 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Fri, 30 Aug 2024 01:05:33 +0200 Subject: [PATCH 16/17] Small tweaks and FA2 documentation --- docs/Whats-New.md | 1 + src/Ext/Script/Body.BridgeRepair.cpp | 26 +++++++++++--------------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 4ad6634374..c72c9afea6 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -215,6 +215,7 @@ SaveGameOnScenarioStart=true ; 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] 18000=Local variable set,22,0,1,[LONG DESC] 18001=Local variable add,22,0,1,[LONG DESC] 18002=Local variable minus,22,0,1,[LONG DESC] diff --git a/src/Ext/Script/Body.BridgeRepair.cpp b/src/Ext/Script/Body.BridgeRepair.cpp index e4ecce16de..0501868214 100644 --- a/src/Ext/Script/Body.BridgeRepair.cpp +++ b/src/Ext/Script/Body.BridgeRepair.cpp @@ -2,10 +2,7 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) { - if (!pTeam) - return; - - auto const pTeamData = TeamExt::ExtMap.Find(pTeam); + auto pTeamData = TeamExt::ExtMap.Find(pTeam); if (!pTeamData) return; @@ -20,7 +17,7 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) if (pTechno->WhatAmI() != AbstractType::Building) continue; - auto pBuilding = static_cast(pTechno); + const auto pBuilding = abstract_cast(pTechno); if (!pBuilding) continue; @@ -31,7 +28,7 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) if (pTeamData->BridgeRepairHuts.size() == 0) { pTeam->StepCompleted = true; - ScriptExt::Log("[%s][%s] (line: %d = %d,%d) Jump to next line: %d = %d,%d -> (Reason: No repair huts found).\n", + 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, @@ -54,7 +51,7 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) } else { - auto pBuilding = static_cast(pTeam->Focus); + const auto pBuilding = static_cast(pTeam->Focus); if (!pBuilding->Type->BridgeRepairHut) { @@ -91,7 +88,7 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) if (pUnit->WhatAmI() == AbstractType::Infantry) { - auto const pInf = static_cast(pUnit); + const auto pInf = static_cast(pUnit); if (pInf->IsEngineer()) { @@ -114,7 +111,7 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) if (engineers.size() == 0) { pTeam->StepCompleted = true; - ScriptExt::Log("[%s][%s] (line: %d = %d,%d) Jump to next line: %d = %d,%d -> (Reason: Team has no engineers).\n", + 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, @@ -131,7 +128,7 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) if (!selectedTarget) { - for (auto const pTechno : pTeamData->BridgeRepairHuts) + for (const auto pTechno : pTeamData->BridgeRepairHuts) { CellStruct cell = pTechno->GetCell()->MapCoords; @@ -145,7 +142,7 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) } else { - auto coords = pTechno->GetCenterCoords(); + CoordStruct coords = pTechno->GetCenterCoords(); // Only huts reachable by the (first) engineer are valid if (engineers.at(0)->IsInSameZoneAsCoords(pTechno->GetCenterCoords())) @@ -155,7 +152,7 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) if (validHuts.size() == 0) { - ScriptExt::Log("[%s][%s] (line: %d = %d,%d) Jump to next line: %d = %d,%d (Reason: Can not select a bridge repair hut).\n", + 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, @@ -181,10 +178,9 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) } else { - for (auto const pHut : validHuts) + for (const auto pHut : validHuts) { int value = engineers.at(0)->DistanceFrom(pHut); // Note: distance is in leptons (*256) - bool isValidCandidate = false; if (mode == 0) @@ -205,7 +201,7 @@ void ScriptExt::RepairDestroyedBridge(TeamClass* pTeam, int mode = -1) if (!selectedTarget) { - ScriptExt::Log("[%s][%s] (line: %d = %d,%d) Jump to next line: %d = %d,%d (Reason: Can not select a bridge repair hut).\n", + 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, From 6ee311500822c34a776dcc1241d7d5b7d496617c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=9D=E5=8D=83=E5=A4=A9=E5=8D=8E?= <1065703286@qq.com> Date: Sun, 21 Dec 2025 22:22:39 +0800 Subject: [PATCH 17/17] [doc] Move the Whats-New entries to the correct positions and link them to the corresponding locations in the document --- docs/Whats-New.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Whats-New.md b/docs/Whats-New.md index c4cfa0eaa8..964c53f65a 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -472,6 +472,7 @@ New: - 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) Vanilla fixes: - Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya) @@ -645,7 +646,6 @@ New: - Allow toggling `Infantry/UnitsGainSelfHeal` for `MultiplayPassive=true` houses (by Starkku) - Customizable straight trajectory detonation & snap distance and pass-through option (by Starkku) - Airstrike & spy plane fixed spawn distance & height (by Starkku) -- Script action for repairing destroyed bridges (by FS-21) - Allow enabling application of `Verses` and `PercentAtMax` for negative damage (by Starkku) - In addition to `PlacementGrid.Translucency`, allow to set the transparency of the grid when `PlacementPreview` is enabled, using the `PlacementGrid.TranslucencyWithPreview` tag (by Belonit) - Show briefing screen on singleplayer mission start (by Starkku)