diff --git a/README.md b/README.md index 7ca803ca5f..3d957b9f59 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ Credits - **secsome (SEC-SOME)** - debug info dump hotkey, refactoring & porting of Ares helper code, introducing more Ares-derived stuff, disguise removal warhead, Mind Control removal warhead, Mind Control enhancement, shields, AnimList.PickRandom, MoveToCell fix, unlimited waypoints, Build At trigger action buildup anim fix, Undeploy building into a unit plays `EVA_NewRallyPointEstablished` fix, custom ore gathering anim, TemporaryClass related crash, Retry dialog on mission failure, Default disguise for individual InfantryTypes, PowerPlant Enhancer, SaveGame Trigger Action, QuickSave command, Numeric variables, Custom gravity for projectiles, Retint map actions bugfix - **Otamaa (Fahroni, BoredEXE)** - help with CellSpread, ported and fixed custom RadType code, togglable ElectricBolt bolts, customizable Chrono Locomotor properties per TechnoClass, DebrisMaximums fixes, Anim-to-Unit, NotHuman anim sequences improvements, Customizable OpenTopped Properties, hooks for ScriptType Actions 92 & 93, ore stage threshold for `HideIfNoOre` - **E1 Elite** - TileSet 255 and above bridge repair fix -- **FS-21** - Dump Object Info enhancements, Powered.KillSpawns, Spawner.LimitRange, ScriptType Actions 71, 72, 73, 74 to 81, 92, 93, 94, 95 to 98, 111, 112, MC deployer fixes, help with docs, Automatic Passenger Deletion +- **FS-21** - Dump Object Info enhancements, Powered.KillSpawns, Spawner.LimitRange, ScriptType Actions 71 to 112, MC deployer fixes, help with docs, Automatic Passenger Deletion - **AutoGavy** - interceptor logic, warhead critical damage system - **ChrisLv_CN** - interceptor logic, LaserTrails, laser fixes, general assistance (work relicensed under [following permission](images/ChrisLv-relicense.png)) - **Xkein** - general assistance, YRpp edits diff --git a/YRpp b/YRpp index bdd7427105..ac18d0c5c2 160000 --- a/YRpp +++ b/YRpp @@ -1 +1 @@ -Subproject commit bdd7427105ffb066e2339832a512cdadc3c20f1f +Subproject commit ac18d0c5c259d5138b16b5614c8656733a8450ae diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 2a332fafc1..7ffc7de278 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -869,7 +869,7 @@ In `aimd.ini`: x=83,n ``` -### `84-91` `AITargetTypes` Attack Action +### `84-91`, `104-105` `AITargetTypes` Attack Action - These Actions instruct the TeamType to use the TaskForce to approach and attack the target specified by the second parameter which is an index of a modder-defined group from `AITargetTypess`. Look at the tables below for the possible Actions (first parameter value) and Arguments (the second parameter value). - For threat-based attack actions `TargetSpecialThreatCoefficientDefault` and `EnemyHouseThreatBonus` tags from `rulesmd.ini` are accounted. @@ -878,7 +878,7 @@ x=83,n In `aimd.ini`: ```ini [SOMESCRIPTTYPE] ; ScriptType -x=i,n ; where 84 <= i <= 91 +x=i,n ; where 84 <= i <= 91 or 104 <= i <= 105 ``` | *Action* | *Argument* | *Repeats* | *Target Priority* | *Description* | @@ -891,6 +891,8 @@ x=i,n ; where 84 <= i <= 91 89 | `AITargetTypes` index# | No | Farther, higher threat | Ends when a team member kill the designated target | 90 | `AITargetTypes` index# | No | Closer | Ends when a team member kill the designated target | 91 | `AITargetTypes` index# | No | Farther | Ends when a team member kill the designated target | +104 | `AITargetTypes` index# | Yes | Closer | Picks 1 random target from the list | +105 | `AITargetTypes` index# | Yes | Farther | Picks 1 random target from the list | - The second parameter with a 0-based index for the `AITargetTypes` section specifies the list of possible `VehicleTypes`, `AircraftTypes`, `InfantryTypes` and `BuildingTypes` that can be evaluated. The new `AITargetTypes` section must be declared in `rulesmd.ini` for making this script work: @@ -942,14 +944,14 @@ In `rulesmd.ini`: ; ... ``` -### `95-98` Moving Team to techno location +### `95-98`, `106-109` Moving Team to techno location - These Actions instructs the TeamType to use the TaskForce to approach the target specified by the second parameter. Look at the tables below for the possible Actions (first parameter value). In `aimd.ini`: ```ini [SOMESCRIPTTYPE] ; ScriptType -x=i,n ; where 95 <= i <= 98 +x=i,n ; where 95 <= i <= 98 or 106 <= i <= 109 ``` | *Action* | *Argument* | Target Owner | *Target Priority* | *Description* | @@ -958,6 +960,42 @@ x=i,n ; where 95 <= i <= 98 96 | Target Type# | Enemy | Farther, higher threat | | 97 | Target Type# | Friendly | Closer | | 98 | Target Type# | Friendly | Farther | | +99 | [AITargetType] index# | Enemy | Closer, higher threat | | +100 | [AITargetType] index# | Enemy | Farther, higher threat | | +101 | [AITargetType] index# | Friendly | Closer | | +102 | [AITargetType] index# | Friendly | Farther | | +106 | [AITargetType] index# | Enemy | Closer | Picks 1 random target from the selected list | +107 | [AITargetType] index# | Enemy | Farther | Picks 1 random target from the selected list | +108 | [AITargetType] index# | Friendly | Closer | Picks 1 random target from the selected list | +109 | [AITargetType] index# | Friendly | Farther | Picks 1 random target from the selected list | + +### `103` Modify Target Distance + +- By default Movement actions `95-102` & `106-109` ends when the Team Leader reaches a distance declared in rulesmd.ini called CloseEnough. When this action is executed before the Movement actions `95-102` overwrites CloseEnough value. This action works only the first time and CloseEnough will be used again the next Movement action. + +In `aimd.ini`: +```ini +[SOMESCRIPTTYPE] ; ScriptType +x=103,n +``` + +### `110` Set Move Action End Mode + +- Sets how the Movement actions ends and jumps to the next line. This action works only the first time and CloseEnough will be used again the next Movement action. + +In `aimd.ini`: +```ini +[SOMESCRIPTTYPE] ; ScriptType +x=110,n +``` + +- The possible argument values are: + +| *Argument* | *Action ends when...* | +| :------: | :-------------------------------------------: | +0 | Team Leader reaches the minimum distance | +1 | One unit reaches the minimum distance | +2 | All team members reached the minimum distance | ### `111` Un-register Team success diff --git a/docs/Whats-New.md b/docs/Whats-New.md index d99b64e3ba..da0f926ed8 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -237,12 +237,14 @@ New: - XDrawOffset for animations (by Morton) - Customizable OpenTopped properties (by Otamaa) - Automatic Passenger Deletion (by FS-21) -- Script Action 74 to 81 and 84 to 91 for new AI attacks (by FS-21) +- Script Action 74 to 81, 84 to 91 and 104 to 105 for new AI attacks (by FS-21) - Script Actions 82 & 83 for modifying AI Trigger Current Weight (by FS-21) - Script Action 92 for waiting & repeat the same new AI attack if no target was found (by FS-21) - Script Action 93 that modifies the Team's Trigger Weight when ends the new attack action (by FS-21) - Script Action 94 for picking a random script from a list (by FS-21) -- Script Action 95 to 98 for new AI movements towards certain objects (by FS-21) +- Script Action 95 to 102 and 106 to 109 for new AI movements towards certain objects (by FS-21) +- Script Action 103 that Modify Target Distance in the new move actions (by FS-21) +- Script Action 110 that Modify how ends the new move actions (by FS-21) - Script Action 111 that un-register Team success, is just the opposite effect of Action 49 (by FS-21) - Script Action 112 to regroup temporarily around the Team Leader (by FS-21) - ObjectInfo now shows current Target and AI Trigger data (by FS-21) diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp index 135b349c7a..09a2cc142b 100644 --- a/src/Ext/Rules/Body.cpp +++ b/src/Ext/Rules/Body.cpp @@ -73,7 +73,7 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI) this->MissingCameo.Read(pINI, "AudioVisual", "MissingCameo"); this->JumpjetAllowLayerDeviation.Read(exINI, "JumpjetControls", "AllowLayerDeviation"); - // Section AITargetType + // Section AITargetTypes int itemsCount = pINI->GetKeyCount(sectionAITargetTypes); for (int i = 0; i < itemsCount; ++i) { diff --git a/src/Ext/Script/Body.cpp b/src/Ext/Script/Body.cpp index 53c79fffae..057cbdc305 100644 --- a/src/Ext/Script/Body.cpp +++ b/src/Ext/Script/Body.cpp @@ -120,6 +120,9 @@ void ScriptExt::ProcessAction(TeamClass* pTeam) case 93: ScriptExt::TeamWeightReward(pTeam, 0); break; + case 94: + ScriptExt::PickRandomScript(pTeam, -1); + break; case 95: // Move to the closest enemy target ScriptExt::Mission_Move(pTeam, 2, false, -1, -1); @@ -136,6 +139,53 @@ void ScriptExt::ProcessAction(TeamClass* pTeam) // Move to the farther friendly target ScriptExt::Mission_Move(pTeam, 3, true, -1, -1); break; + case 99: + // Move to the closest specific enemy target + ScriptExt::Mission_Move_List(pTeam, 2, false, -1); + break; + case 100: + // Move to the farther specific enemy target + ScriptExt::Mission_Move_List(pTeam, 3, false, -1); + case 101: + // Move to the closest specific friendly target + ScriptExt::Mission_Move_List(pTeam, 2, true, -1); + break; + case 102: + // Move to the farther specific friendly target + ScriptExt::Mission_Move_List(pTeam, 3, true, -1); + break; + case 103: + // AISafeDistance equivalent for Mission_Move() + ScriptExt::SetCloseEnoughDistance(pTeam, -1); + break; + case 104: + // Pick 1 closer random objective from specific list for attacking it + ScriptExt::Mission_Attack_List1Random(pTeam, true, 2, -1); + break; + case 105: + // Pick 1 farther random objective from specific list for attacking it + ScriptExt::Mission_Attack_List1Random(pTeam, true, 3, -1); + break; + case 106: + // Pick 1 closer enemy random objective from specific list for moving to it + ScriptExt::Mission_Move_List1Random(pTeam, 2, false, -1, -1); + break; + case 107: + // Pick 1 farther enemy random objective from specific list for moving to it + ScriptExt::Mission_Move_List1Random(pTeam, 3, false, -1, -1); + break; + case 108: + // Pick 1 closer friendly random objective from specific list for moving to it + ScriptExt::Mission_Move_List1Random(pTeam, 2, true, -1, -1); + break; + case 109: + // Pick 1 farther friendly random objective from specific list for moving to it + ScriptExt::Mission_Move_List1Random(pTeam, 3, true, -1, -1); + break; + case 110: + // Set the condition for ending the Mission_Move Actions. + ScriptExt::SetMoveMissionEndMode(pTeam, -1); + break; case 111: // Un-register success for AITrigger weight adjustment (this is the opposite of 49,0) ScriptExt::UnregisterGreatSuccess(pTeam); @@ -143,6 +193,15 @@ void ScriptExt::ProcessAction(TeamClass* pTeam) case 112: ScriptExt::Mission_Gather_NearTheLeader(pTeam, -1); break; + default: + // Do nothing because or it is a wrong Action number or it is an Ares/YR action... + if (action > 70 && !(action >= PhobosScripts::LocalVariableAdd && action <= PhobosScripts::GlobalVariableAndByGlobal)) + { + // Unknown new action. This action finished + pTeam->StepCompleted = true; + Debug::Log("[%s] [%s] (line %d): Unknown Script Action: %d\n", pTeam->Type->ID, pTeam->CurrentScript->Type->ID, pTeam->CurrentScript->idxCurrentLine, pTeam->CurrentScript->Type->ScriptActions[pTeam->CurrentScript->idxCurrentLine].Action); + } + break; } if (action >= PhobosScripts::LocalVariableAdd && action <= PhobosScripts::GlobalVariableAndByGlobal) @@ -299,12 +358,12 @@ void ScriptExt::WaitUntilFullAmmoAction(TeamClass* pTeam) void ScriptExt::Mission_Gather_NearTheLeader(TeamClass *pTeam, int countdown = -1) { FootClass *pLeaderUnit = nullptr; - int bestUnitLeadershipValue = -1; int initialCountdown = pTeam->CurrentScript->Type->ScriptActions[pTeam->CurrentScript->idxCurrentLine].Argument; bool gatherUnits = false; + auto pTeamData = TeamExt::ExtMap.Find(pTeam); // This team has no units! END - if (!pTeam) + if (!pTeam || !pTeamData) { // This action finished pTeam->StepCompleted = true; @@ -312,19 +371,8 @@ void ScriptExt::Mission_Gather_NearTheLeader(TeamClass *pTeam, int countdown = - } // Load countdown - auto pTeamData = TeamExt::ExtMap.Find(pTeam); - if (pTeamData) - { - if (pTeamData->Countdown_RegroupAtLeader >= 0) - countdown = pTeamData->Countdown_RegroupAtLeader; - } - else - { - // Looks like an error... - // This action finished - pTeam->StepCompleted = true; - return; - } + if (pTeamData->Countdown_RegroupAtLeader >= 0) + countdown = pTeamData->Countdown_RegroupAtLeader; // Gather permanently until all the team members are near of the Leader if (initialCountdown == 0) @@ -369,30 +417,9 @@ void ScriptExt::Mission_Gather_NearTheLeader(TeamClass *pTeam, int countdown = - int nTogether = 0; int nUnits = -1; // Leader counts here double closeEnough; - - // Find the leader - for (auto pUnit = pTeam->FirstUnit; pUnit; pUnit = pUnit->NextTeamMember) - { - if (pUnit && pUnit->IsAlive - && pUnit->Health > 0 - && !pUnit->InLimbo - && pUnit->IsOnMap - && !pUnit->Absorbed) - { - auto pUnitType = pUnit->GetTechnoType(); - - if (pUnitType) - { - // The team leader will be used for selecting targets, if there are living Team Members then always exists 1 Leader. - int unitLeadershipRating = pUnitType->LeadershipRating; - if (unitLeadershipRating > bestUnitLeadershipValue) - { - pLeaderUnit = pUnit; - bestUnitLeadershipValue = unitLeadershipRating; - } - } - } - } + + // Find the Leader + pLeaderUnit = FindTheTeamLeader(pTeam); if (!pLeaderUnit) { @@ -503,11 +530,19 @@ void ScriptExt::Mission_Attack(TeamClass *pTeam, bool repeatAction = true, int c bool noWaitLoop = false; FootClass *pLeaderUnit = nullptr; TechnoTypeClass* pLeaderUnitType = nullptr; - int bestUnitLeadershipValue = -1; bool bAircraftsWithoutAmmo = false; TechnoClass* pFocus = nullptr; bool agentMode = false; bool pacifistTeam = true; + auto pTeamData = TeamExt::ExtMap.Find(pTeam); + + if (!pTeamData) + { + pTeam->StepCompleted = true; + Debug::Log("DEBUG: [%s] [%s] (line: %d) Jump to next line: %d = %d,%d -> (Reason: ExtData found)\n", pTeam->Type->ID, pScript->idxCurrentLine, pScript->Type->ID, pScript->idxCurrentLine + 1, pScript->Type->ScriptActions[pScript->idxCurrentLine + 1].Action, pScript->Type->ScriptActions[pScript->idxCurrentLine + 1].Argument); + + return; + } // When the new target wasn't found it sleeps some few frames before the new attempt. This can save cycles and cycles of unnecessary executed lines. if (pTeam->GuardAreaTimer.TimeLeft != 0 || pTeam->GuardAreaTimer.InProgress()) @@ -518,12 +553,8 @@ void ScriptExt::Mission_Attack(TeamClass *pTeam, bool repeatAction = true, int c pTeam->GuardAreaTimer.Stop(); // Needed noWaitLoop = true; - auto pTeamData = TeamExt::ExtMap.Find(pTeam); - if (pTeamData) - { - if (pTeamData->WaitNoTargetAttempts > 0) - pTeamData->WaitNoTargetAttempts--; - } + if (pTeamData->WaitNoTargetAttempts > 0) + pTeamData->WaitNoTargetAttempts--; } else { @@ -535,31 +566,39 @@ void ScriptExt::Mission_Attack(TeamClass *pTeam, bool repeatAction = true, int c if (!pTeam) { pTeam->StepCompleted = true; - Debug::Log("DEBUG: ScripType: [%s] [%s] Jump to NEXT line: %d = %d,%d -> (Reason: No team members alive)\n", pTeam->Type->ID, pScript->Type->ID, pScript->idxCurrentLine, pScript->Type->ScriptActions[pScript->idxCurrentLine].Action, pScript->Type->ScriptActions[pScript->idxCurrentLine].Argument); + Debug::Log("DEBUG: [%s] [%s] (line: %d) Jump to next line: %d = %d,%d -> (Reason: No team members alive)\n", pTeam->Type->ID, pScript->Type->ID, pScript->idxCurrentLine, pScript->idxCurrentLine + 1, pScript->Type->ScriptActions[pScript->idxCurrentLine + 1].Action, pScript->Type->ScriptActions[pScript->idxCurrentLine + 1].Argument); return; } + pFocus = pTeamData->SelectedTarget; + if (pFocus && pFocus->IsAlive + && !pFocus->InLimbo + && pFocus->IsOnMap + && !pFocus->Absorbed) + { } + else + { + pTeamData->SelectedTarget = nullptr; + pFocus = nullptr; + } + for (auto pUnit = pTeam->FirstUnit; pUnit; pUnit = pUnit->NextTeamMember) { auto pKillerTechnoData = TechnoExt::ExtMap.Find(pUnit); if (pKillerTechnoData && pKillerTechnoData->LastKillWasTeamTarget) { // Time for Team award check! (if set any) - auto pTeamData = TeamExt::ExtMap.Find(pTeam); - if (pTeamData) + if (pTeamData->NextSuccessWeightAward > 0) { - if (pTeamData->NextSuccessWeightAward > 0) - { - IncreaseCurrentTriggerWeight(pTeam, false, pTeamData->NextSuccessWeightAward); - pTeamData->NextSuccessWeightAward = 0; - } + IncreaseCurrentTriggerWeight(pTeam, false, pTeamData->NextSuccessWeightAward); + pTeamData->NextSuccessWeightAward = 0; } // Let's clean the Killer mess - pTeam->QueuedFocus = nullptr; - pTeam->Focus = nullptr; + selectedTarget = nullptr; pKillerTechnoData->LastKillWasTeamTarget = false; + pTeamData->SelectedTarget = nullptr; if (!repeatAction) { @@ -580,13 +619,11 @@ void ScriptExt::Mission_Attack(TeamClass *pTeam, bool repeatAction = true, int c } } - //auto pTeamData = TeamExt::ExtMap.Find(pTeam); - if (pTeamData) - pTeamData->IdxSelectedObjectFromAIList = -1; + pTeamData->IdxSelectedObjectFromAIList = -1; // This action finished pTeam->StepCompleted = true; - Debug::Log("DEBUG: [%s] [%s]: Force the jump to NEXT line: %d = %d,%d (No repeatAction set)\n", pTeam->Type->ID, pScript->Type->ID, pScript->idxCurrentLine + 1, pScript->Type->ScriptActions[pScript->idxCurrentLine + 1].Action, pScript->Type->ScriptActions[pScript->idxCurrentLine + 1].Argument); + Debug::Log("DEBUG: [%s] [%s] (line: %d) Force the jump to next line: %d = %d,%d (This action wont repeat)\n", pTeam->Type->ID, pScript->Type->ID, pScript->idxCurrentLine, pScript->idxCurrentLine + 1, pScript->Type->ScriptActions[pScript->idxCurrentLine + 1].Action, pScript->Type->ScriptActions[pScript->idxCurrentLine + 1].Argument); return; } @@ -595,7 +632,7 @@ void ScriptExt::Mission_Attack(TeamClass *pTeam, bool repeatAction = true, int c for (auto pUnit = pTeam->FirstUnit; pUnit; pUnit = pUnit->NextTeamMember) { - if (pUnit && pUnit->IsAlive && pUnit->Health > 0 && !pUnit->InLimbo) + if (pUnit && pUnit->IsAlive && !pUnit->InLimbo) { auto pUnitType = pUnit->GetTechnoType(); if (pUnitType) @@ -630,41 +667,30 @@ void ScriptExt::Mission_Attack(TeamClass *pTeam, bool repeatAction = true, int c } // Any Team member (infantry) is a special agent? If yes ignore some checks based on Weapons. - bool isAgent = false; if (pUnitType->WhatAmI() == AbstractType::InfantryType) { auto pTypeInf = abstract_cast(pUnitType); - if (pTypeInf->Agent || pTypeInf->Infiltrate || pTypeInf->Engineer) + if ((pTypeInf->Agent && pTypeInf->Infiltrate) || pTypeInf->Engineer) { agentMode = true; - isAgent = true; } } - - // The team leader will be used for selecting targets, if there are living Team Members then always exists 1 Leader. - int unitLeadershipRating = pUnitType->LeadershipRating; - if (unitLeadershipRating > bestUnitLeadershipValue && (!pacifistUnit || isAgent)) - { - pLeaderUnit = pUnit; - bestUnitLeadershipValue = unitLeadershipRating; - } } } } - if (!pLeaderUnit || bAircraftsWithoutAmmo || pacifistTeam) + // Find the Leader + pLeaderUnit = FindTheTeamLeader(pTeam); + + if (!pLeaderUnit || bAircraftsWithoutAmmo || (pacifistTeam && !agentMode)) { - auto pTeamData = TeamExt::ExtMap.Find(pTeam); - if (pTeamData) - { - pTeamData->IdxSelectedObjectFromAIList = -1; - if (pTeamData->WaitNoTargetAttempts != 0) - pTeamData->WaitNoTargetAttempts = 0; - } + pTeamData->IdxSelectedObjectFromAIList = -1; + if (pTeamData->WaitNoTargetAttempts != 0) + pTeamData->WaitNoTargetAttempts = 0; // This action finished pTeam->StepCompleted = true; - Debug::Log("DEBUG: ScripType: [%s] [%s] Jump to NEXT line: %d = %d,%d -> (Reason: No Leader found | Exists Aircrafts without ammo | Team members have no weapons)\n", pTeam->Type->ID, pScript->Type->ID, pScript->idxCurrentLine, pScript->Type->ScriptActions[pScript->idxCurrentLine].Action, pScript->Type->ScriptActions[pScript->idxCurrentLine].Argument); + Debug::Log("DEBUG: [%s] [%s] (line: %d) Jump to next line: %d = %d,%d -> (Reason: No Leader found | Exists Aircrafts without ammo | Team members have no weapons)\n", pTeam->Type->ID, pScript->Type->ID, pScript->idxCurrentLine, pScript->idxCurrentLine + 1, pScript->Type->ScriptActions[pScript->idxCurrentLine + 1].Action, pScript->Type->ScriptActions[pScript->idxCurrentLine + 1].Argument); return; } @@ -745,7 +771,6 @@ void ScriptExt::Mission_Attack(TeamClass *pTeam, bool repeatAction = true, int c } } - pFocus = abstract_cast(pTeam->Focus); if (!pFocus && !bAircraftsWithoutAmmo) { // Favorite Enemy House case. If set, AI will focus against that House @@ -757,16 +782,13 @@ void ScriptExt::Mission_Attack(TeamClass *pTeam, bool repeatAction = true, int c if (selectedTarget) { - Debug::Log("DEBUG: [%s] [%s](line: %d = %d,%d): Leader [%s] selected [%s] as target.\n", pTeam->Type->ID, pScript->Type->ID, pScript->idxCurrentLine, pScript->Type->ScriptActions[pScript->idxCurrentLine].Action, pScript->Type->ScriptActions[pScript->idxCurrentLine].Argument, pLeaderUnit->GetTechnoType()->get_ID(), selectedTarget->GetTechnoType()->get_ID()); - pTeam->Focus = selectedTarget; - - auto pTeamData = TeamExt::ExtMap.Find(pTeam); - if (pTeamData) - pTeamData->WaitNoTargetAttempts = 0; // Disable Script Waits if there are any because a new target was selected + Debug::Log("DEBUG: [%s] [%s] (line: %d = %d,%d) Leader [%s] (UID: %lu) selected [%s] (UID: %lu) as target.\n", pTeam->Type->ID, pScript->Type->ID, pScript->idxCurrentLine, pScript->Type->ScriptActions[pScript->idxCurrentLine].Action, pScript->Type->ScriptActions[pScript->idxCurrentLine].Argument, pLeaderUnit->GetTechnoType()->get_ID(), pLeaderUnit->UniqueID, selectedTarget->GetTechnoType()->get_ID(), selectedTarget->UniqueID); + pTeamData->SelectedTarget = selectedTarget; + pTeamData->WaitNoTargetAttempts = 0; // Disable Script Waits if there are any because a new target was selected for (auto pUnit = pTeam->FirstUnit; pUnit; pUnit = pUnit->NextTeamMember) { - if (pUnit->IsAlive && pUnit->Health > 0 && !pUnit->InLimbo) + if (pUnit->IsAlive && !pUnit->InLimbo) { auto pUnitType = pUnit->GetTechnoType(); if (pUnitType && pUnit != selectedTarget && pUnit->Target != selectedTarget) @@ -816,6 +838,19 @@ void ScriptExt::Mission_Attack(TeamClass *pTeam, bool repeatAction = true, int c } } + // Spy case + if (pUnitType->WhatAmI() == AbstractType::InfantryType) + { + auto pInfantryType = abstract_cast(pUnitType); + + if (pInfantryType && pInfantryType->Infiltrate && pInfantryType->Agent) + { + // Check if target is an structure and see if spiable + if (pUnit->GetCurrentMission() != Mission::Enter) + pUnit->Mission_Enter(); + } + } + // Tanya / Commando C4 case if ((pUnitType->WhatAmI() == AbstractType::InfantryType && (abstract_cast(pUnitType)->C4 || pUnit->HasAbility(Ability::C4))) && pUnit->GetCurrentMission() != Mission::Sabotage) { @@ -837,22 +872,18 @@ void ScriptExt::Mission_Attack(TeamClass *pTeam, bool repeatAction = true, int c if (!noWaitLoop) pTeam->GuardAreaTimer.Start(16); - auto pTeamData = TeamExt::ExtMap.Find(pTeam); - if (pTeamData) - { - if (pTeamData->IdxSelectedObjectFromAIList >= 0) - pTeamData->IdxSelectedObjectFromAIList = -1; + if (pTeamData->IdxSelectedObjectFromAIList >= 0) + pTeamData->IdxSelectedObjectFromAIList = -1; - if (pTeamData->WaitNoTargetAttempts != 0) - { - pTeam->GuardAreaTimer.Start(16); // No target? let's wait some frames - return; - } + if (pTeamData->WaitNoTargetAttempts != 0) + { + pTeam->GuardAreaTimer.Start(16); // No target? let's wait some frames + return; } // This action finished pTeam->StepCompleted = true; - Debug::Log("DEBUG: Next script action line for [%s] (%s) will be: %d = %d,%d (Reason: New target NOT FOUND)\n", pTeam->Type->ID, pScript->Type->ID, pScript->idxCurrentLine + 1, pScript->Type->ScriptActions[pScript->idxCurrentLine + 1].Action, pScript->Type->ScriptActions[pScript->idxCurrentLine + 1].Argument); + Debug::Log("DEBUG: [%s] [%s] (line: %d) Jump to next line: %d = %d,%d (new target NOT FOUND)\n", pTeam->Type->ID, pScript->Type->ID, pScript->idxCurrentLine, pScript->idxCurrentLine + 1, pScript->Type->ScriptActions[pScript->idxCurrentLine + 1].Action, pScript->Type->ScriptActions[pScript->idxCurrentLine + 1].Argument); return; } @@ -887,11 +918,12 @@ void ScriptExt::Mission_Attack(TeamClass *pTeam, bool repeatAction = true, int c if (auto pUnitType = pUnit->GetTechnoType()) { if (pUnit->IsAlive + && !pUnit->Health > 0 && !pUnit->InLimbo && (pUnitType->WhatAmI() == AbstractType::AircraftType && abstract_cast(pUnitType)->AirportBound) && pUnit->Ammo > 0 - && (pUnit->Target != pTeam->Focus && !pUnit->InAir)) + && (pUnit->Target != pFocus && !pUnit->InAir)) { pUnit->SetTarget(pFocus); } @@ -920,7 +952,10 @@ void ScriptExt::Mission_Attack(TeamClass *pTeam, bool repeatAction = true, int c if (pUnit->Ammo > 0) { pUnit->QueueMission(Mission::Attack, true); - pUnit->ClickedAction(Action::Attack, pFocus, false); + + if (pFocus) + pUnit->ClickedAction(Action::Attack, pFocus, false); + pUnit->Mission_Attack(); } else @@ -937,7 +972,10 @@ void ScriptExt::Mission_Attack(TeamClass *pTeam, bool repeatAction = true, int c if (pUnit->Ammo > 0) { pUnit->QueueMission(Mission::Attack, true); - pUnit->ClickedAction(Action::Attack, pFocus, false); + + if (pFocus) + pUnit->ClickedAction(Action::Attack, pFocus, false); + pUnit->Mission_Attack(); } else @@ -963,10 +1001,11 @@ void ScriptExt::Mission_Attack(TeamClass *pTeam, bool repeatAction = true, int c } else { - pTeam->Focus = nullptr; - pTeam->QueuedFocus = nullptr; + pTeamData->SelectedTarget = nullptr; + + if (pFocus) + pUnit->ClickedAction(Action::Attack, pFocus, false); - pUnit->ClickedAction(Action::Attack, pFocus, false); pUnit->CurrentTargets.Clear(); pUnit->SetTarget(nullptr); pUnit->SetFocus(nullptr); @@ -981,12 +1020,9 @@ void ScriptExt::Mission_Attack(TeamClass *pTeam, bool repeatAction = true, int c if (bForceNextAction) { - auto pTeamData = TeamExt::ExtMap.Find(pTeam); - if (pTeamData) - pTeamData->IdxSelectedObjectFromAIList = -1; - + pTeamData->IdxSelectedObjectFromAIList = -1; pTeam->StepCompleted = true; - Debug::Log("DEBUG: ScripType: [%s] [%s] Jump to NEXT line: %d = %d,%d -> (Reason: Naval is unable to target ground)\n", pTeam->Type->ID, pScript->Type->ID, pScript->idxCurrentLine + 1, pScript->Type->ScriptActions[pScript->idxCurrentLine + 1].Action, pScript->Type->ScriptActions[pScript->idxCurrentLine + 1].Argument); + Debug::Log("DEBUG: [%s] [%s] (line: %d) Jump to NEXT line: %d = %d,%d (Naval is unable to target ground)\n", pTeam->Type->ID, pScript->Type->ID, pScript->idxCurrentLine, pScript->idxCurrentLine + 1, pScript->Type->ScriptActions[pScript->idxCurrentLine + 1].Action, pScript->Type->ScriptActions[pScript->idxCurrentLine + 1].Argument); return; } @@ -1020,8 +1056,18 @@ TechnoClass* ScriptExt::GreatestThreat(TechnoClass *pTechno, int method, int cal if ((weaponType && weaponType->Projectile->AG) || agentMode) unitWeaponsHaveAG = true; + int weaponDamage = 0; + + if (weaponType) + { + if (weaponType->AmbientDamage > 0) + weaponDamage = MapClass::GetTotalDamage(weaponType->AmbientDamage, weaponType->Warhead, objectType->Armor, 0) + MapClass::GetTotalDamage(weaponType->Damage, weaponType->Warhead, objectType->Armor, 0); + else + weaponDamage = MapClass::GetTotalDamage(weaponType->Damage, weaponType->Warhead, objectType->Armor, 0); + } + // If the target can't be damaged then isn't a valid target - if (weaponType && GeneralUtils::GetWarheadVersusArmor(weaponType->Warhead, objectType->Armor) == 0.0 && !agentMode) + if (weaponType && weaponDamage <= 0 && !agentMode) continue; if (!agentMode) @@ -1063,6 +1109,7 @@ TechnoClass* ScriptExt::GreatestThreat(TechnoClass *pTechno, int method, int cal if (object != pTechno && object->IsAlive + && !object->Health > 0 && !object->InLimbo && !objectType->Immune && !object->Transporter @@ -1179,7 +1226,7 @@ bool ScriptExt::EvaluateObjectWithMask(TechnoClass *pTechno, int mask, int attac double distanceToTarget = 0; TechnoClass* pTarget = nullptr; - // Special case: validate target if is part of a technos list in [AITargetType] section + // Special case: validate target if is part of a technos list in [AITargetTypes] section if (attackAITargetType >= 0 && RulesExt::Global()->AITargetTypesLists.Count > 0) { DynamicVectorClass objectsList = RulesExt::Global()->AITargetTypesLists.GetItem(attackAITargetType); @@ -1368,7 +1415,7 @@ bool ScriptExt::EvaluateObjectWithMask(TechnoClass *pTechno, int mask, int attac for (int i = 0; i < NeutralTechBuildings.Count; i++) { auto pTechObject = NeutralTechBuildings.GetItem(i); - if (pTechObject->ID == pTechno->get_ID()) + if (_stricmp(pTechObject->ID, pTechno->get_ID()) == 0) return true; } } @@ -1526,10 +1573,11 @@ bool ScriptExt::EvaluateObjectWithMask(TechnoClass *pTechno, int mask, int attac case 20: pTypeBuilding = abstract_cast(pTechnoType); - // Vehicle Factory + // Land Vehicle Factory if (!pTechno->Owner->IsNeutral() && pTechnoType->WhatAmI() == AbstractType::BuildingType - && pTypeBuilding->Factory == AbstractType::UnitType) + && pTypeBuilding->Factory == AbstractType::UnitType + && !pTypeBuilding->Naval) { return true; } @@ -1573,7 +1621,7 @@ bool ScriptExt::EvaluateObjectWithMask(TechnoClass *pTechno, int mask, int attac for (int i = 0; i < BuildTech.Count; i++) { auto pTechObject = BuildTech.GetItem(i); - if (pTechObject->ID == pTechno->get_ID()) + if (_stricmp(pTechObject->ID, pTechno->get_ID()) == 0) return true; } } @@ -1631,7 +1679,7 @@ bool ScriptExt::EvaluateObjectWithMask(TechnoClass *pTechno, int mask, int attac for (int i = 0; i < BaseUnit.Count; i++) { auto pMCVObject = BaseUnit.GetItem(i); - if (pMCVObject->ID == pTechno->get_ID()) + if (_stricmp(pMCVObject->ID, pTechno->get_ID()) == 0) return true; } } @@ -1716,10 +1764,10 @@ bool ScriptExt::EvaluateObjectWithMask(TechnoClass *pTechno, int mask, int attac pTypeBuilding = abstract_cast(pTechnoType); // Capturable Structure or Repair Hut - if (pTechnoType->WhatAmI() == AbstractType::BuildingType - && pTypeBuilding->Capturable - || (pTypeBuilding->BridgeRepairHut - && pTypeBuilding->Repairable)) + if (pTypeBuilding + && (pTypeBuilding->Capturable + || (pTypeBuilding->BridgeRepairHut + && pTypeBuilding->Repairable))) { return true; } @@ -1742,6 +1790,19 @@ bool ScriptExt::EvaluateObjectWithMask(TechnoClass *pTechno, int mask, int attac break; + case 35: + pTypeBuilding = abstract_cast(pTechnoType); + + // Land Vehicle Factory & Naval Factory + if (!pTechno->Owner->IsNeutral() + && pTechnoType->WhatAmI() == AbstractType::BuildingType + && pTypeBuilding->Factory == AbstractType::UnitType) + { + return true; + } + + break; + default: break; } @@ -1935,7 +1996,6 @@ void ScriptExt::Mission_Move(TeamClass *pTeam, int calcThreatMode = 0, bool pick bool noWaitLoop = false; FootClass *pLeaderUnit = nullptr; TechnoTypeClass* pLeaderUnitType = nullptr; - int bestUnitLeadershipValue = -1; bool bAircraftsWithoutAmmo = false; TechnoClass* pFocus = nullptr; @@ -1973,14 +2033,14 @@ void ScriptExt::Mission_Move(TeamClass *pTeam, int calcThreatMode = 0, bool pick // This action finished pTeam->StepCompleted = true; - Debug::Log("DEBUG: ScripType: [%s] [%s] Jump to NEXT line: %d = %d,%d -> (Reason: No team members alive)\n", pTeam->Type->ID, pScript->Type->ID, pScript->idxCurrentLine, pScript->Type->ScriptActions[pScript->idxCurrentLine].Action, pScript->Type->ScriptActions[pScript->idxCurrentLine].Argument); + Debug::Log("DEBUG: ScripType: [%s] [%s] Jump to NEXT line: %d = %d,%d -> (Reason: No team members alive)\n", pTeam->Type->ID, pScript->Type->ID, pScript->idxCurrentLine + 1, pScript->Type->ScriptActions[pScript->idxCurrentLine + 1].Action, pScript->Type->ScriptActions[pScript->idxCurrentLine + 1].Argument); return; } for (auto pUnit = pTeam->FirstUnit; pUnit; pUnit = pUnit->NextTeamMember) { - if (pUnit && pUnit->IsAlive && pUnit->Health > 0 && !pUnit->InLimbo) + if (pUnit && pUnit->IsAlive && !pUnit->InLimbo) { auto pUnitType = pUnit->GetTechnoType(); if (pUnitType) @@ -1993,18 +2053,13 @@ void ScriptExt::Mission_Move(TeamClass *pTeam, int calcThreatMode = 0, bool pick bAircraftsWithoutAmmo = true; pUnit->CurrentTargets.Clear(); } - - // The Team Leader will be used for selecting targets, if there are living Team Members then always exists 1 Leader. - int unitLeadershipRating = pUnitType->LeadershipRating; - if (unitLeadershipRating > bestUnitLeadershipValue) - { - pLeaderUnit = pUnit; - bestUnitLeadershipValue = unitLeadershipRating; - } } } } + // Find the Leader + pLeaderUnit = FindTheTeamLeader(pTeam); + if (!pLeaderUnit || bAircraftsWithoutAmmo) { auto pTeamData = TeamExt::ExtMap.Find(pTeam); @@ -2018,7 +2073,7 @@ void ScriptExt::Mission_Move(TeamClass *pTeam, int calcThreatMode = 0, bool pick // This action finished pTeam->StepCompleted = true; - Debug::Log("DEBUG: ScripType: [%s] [%s] Jump to NEXT line: %d = %d,%d -> (Reasons: No Leader | Aircrafts without ammo)\n", pTeam->Type->ID, pScript->Type->ID, pScript->idxCurrentLine, pScript->Type->ScriptActions[pScript->idxCurrentLine].Action, pScript->Type->ScriptActions[pScript->idxCurrentLine].Argument); + Debug::Log("DEBUG: ScripType: [%s] [%s] Jump to NEXT line: %d = %d,%d -> (Reasons: No Leader | Aircrafts without ammo)\n", pTeam->Type->ID, pScript->Type->ID, pScript->idxCurrentLine + 1, pScript->Type->ScriptActions[pScript->idxCurrentLine + 1].Action, pScript->Type->ScriptActions[pScript->idxCurrentLine + 1].Argument); return; } @@ -2041,7 +2096,7 @@ void ScriptExt::Mission_Move(TeamClass *pTeam, int calcThreatMode = 0, bool pick for (auto pUnit = pTeam->FirstUnit; pUnit; pUnit = pUnit->NextTeamMember) { - if (pUnit->IsAlive && pUnit->Health > 0 && !pUnit->InLimbo) + if (pUnit->IsAlive && pUnit->IsOnMap && !pUnit->InLimbo) { auto pUnitType = pUnit->GetTechnoType(); @@ -2110,50 +2165,21 @@ void ScriptExt::Mission_Move(TeamClass *pTeam, int calcThreatMode = 0, bool pick } else { - double closeEnough = RulesClass::Instance->CloseEnough / 256.0; + int moveDestinationMode = 0; auto pTeamData = TeamExt::ExtMap.Find(pTeam); - if (pTeamData && pTeamData->CloseEnough > 0) - closeEnough = pTeamData->CloseEnough; - - bool bForceNextAction = true; - - // Team already have a focused target - for (auto pUnit = pTeam->FirstUnit; pUnit; pUnit = pUnit->NextTeamMember) + if (pTeamData) { - if (pUnit - && pUnit->IsAlive - && !pUnit->InLimbo) - { - if (!pUnit->Locomotor->Is_Moving_Now()) - pUnit->SetDestination(pFocus, false); - - if (pUnit->DistanceFrom(pUnit->Destination) / 256.0 > closeEnough) - { - bForceNextAction = false; - - if (pUnit->GetTechnoType()->WhatAmI() == AbstractType::AircraftType && pUnit->Ammo > 0) - pUnit->QueueMission(Mission::Move, false); - - continue; - } - else - { - if (pUnit->GetTechnoType()->WhatAmI() == AbstractType::AircraftType && pUnit->Ammo <= 0) - { - pUnit->QueueMission(Mission::Return, false); - pUnit->Mission_Enter(); - - continue; - } - } - } + moveDestinationMode = pTeamData->MoveMissionEndMode; } + bool bForceNextAction = ScriptExt::MoveMissionEndStatus(pTeam, pFocus, pLeaderUnit, moveDestinationMode); + if (bForceNextAction) { if (pTeamData) { + pTeamData->MoveMissionEndMode = 0; pTeamData->IdxSelectedObjectFromAIList = -1; if (pTeamData->CloseEnough >= 0) @@ -2323,12 +2349,333 @@ TechnoClass* ScriptExt::FindBestObject(TechnoClass *pTechno, int method, int cal return bestObject; } +void ScriptExt::Mission_Attack_List1Random(TeamClass *pTeam, bool repeatAction, int calcThreatMode, int attackAITargetType) +{ + bool selected = false; + int idxSelectedObject = -1; + DynamicVectorClass validIndexes; + + auto pTeamData = TeamExt::ExtMap.Find(pTeam); + if (pTeamData && pTeamData->IdxSelectedObjectFromAIList >= 0) + { + idxSelectedObject = pTeamData->IdxSelectedObjectFromAIList; + selected = true; + } + + if (attackAITargetType < 0) + attackAITargetType = pTeam->CurrentScript->Type->ScriptActions[pTeam->CurrentScript->idxCurrentLine].Argument; + + if (attackAITargetType >= 0 + && attackAITargetType < RulesExt::Global()->AITargetTypesLists.Count) + { + DynamicVectorClass objectsList = RulesExt::Global()->AITargetTypesLists.GetItem(attackAITargetType); + + if (idxSelectedObject < 0 && objectsList.Count > 0 && !selected) + { + // Finding the objects from the list that actually exists in the map + for (int i = 0; i < TechnoClass::Array->Count; i++) + { + auto pTechno = TechnoClass::Array->GetItem(i); + auto pTechnoType = TechnoClass::Array->GetItem(i)->GetTechnoType(); + bool found = false; + + for (int j = 0; j < objectsList.Count && !found; j++) + { + auto objectFromList = objectsList.GetItem(j); + + if (pTechnoType == objectFromList + && pTechno->IsAlive + && !pTechno->InLimbo + && pTechno->IsOnMap + && !pTechno->Absorbed + && (!pTeam->FirstUnit->Owner->IsAlliedWith(pTechno) + || (pTeam->FirstUnit->Owner->IsAlliedWith(pTechno) + && pTechno->IsMindControlled() + && !pTeam->FirstUnit->Owner->IsAlliedWith(pTechno->MindControlledBy)))) + { + validIndexes.AddItem(j); + found = true; + } + } + } + + if (validIndexes.Count > 0) + { + idxSelectedObject = validIndexes.GetItem(ScenarioClass::Instance->Random.RandomRanged(0, validIndexes.Count - 1)); + selected = true; + Debug::Log("DEBUG: [%s] [%s] Picked a random Techno from the list index [AITargetTypes][%d][%d] = %s\n", pTeam->Type->ID, pTeam->CurrentScript->Type->ID, attackAITargetType, idxSelectedObject, objectsList.GetItem(idxSelectedObject)->ID); + } + } + + if (selected) + pTeamData->IdxSelectedObjectFromAIList = idxSelectedObject; + + Mission_Attack(pTeam, repeatAction, calcThreatMode, attackAITargetType, idxSelectedObject); + } + + // This action finished + if (!selected) + { + pTeam->StepCompleted = true; + Debug::Log("DEBUG: [%s] [%s] Failed to pick a random Techno from the list index [AITargetTypes][%d]! Valid Technos in the list: %d\n", pTeam->Type->ID, pTeam->CurrentScript->Type->ID, attackAITargetType, validIndexes.Count); + } +} + +void ScriptExt::Mission_Move_List(TeamClass *pTeam, int calcThreatMode, bool pickAllies, int attackAITargetType) +{ + auto pTeamData = TeamExt::ExtMap.Find(pTeam); + if (pTeamData) + pTeamData->IdxSelectedObjectFromAIList = -1; + + if (attackAITargetType < 0) + attackAITargetType = pTeam->CurrentScript->Type->ScriptActions[pTeam->CurrentScript->idxCurrentLine].Argument; + + if (RulesExt::Global()->AITargetTypesLists.Count > 0 + && RulesExt::Global()->AITargetTypesLists.GetItem(attackAITargetType).Count > 0) + { + Mission_Move(pTeam, calcThreatMode, pickAllies, attackAITargetType, -1); + } +} + +void ScriptExt::Mission_Move_List1Random(TeamClass *pTeam, int calcThreatMode, bool pickAllies, int attackAITargetType, int idxAITargetTypeItem = -1) +{ + bool selected = false; + int idxSelectedObject = -1; + DynamicVectorClass validIndexes; + + auto pTeamData = TeamExt::ExtMap.Find(pTeam); + if (pTeamData && pTeamData->IdxSelectedObjectFromAIList >= 0) + { + idxSelectedObject = pTeamData->IdxSelectedObjectFromAIList; + selected = true; + } + + if (attackAITargetType < 0) + attackAITargetType = pTeam->CurrentScript->Type->ScriptActions[pTeam->CurrentScript->idxCurrentLine].Argument; + + if (attackAITargetType >= 0 + && attackAITargetType < RulesExt::Global()->AITargetTypesLists.Count) + { + DynamicVectorClass objectsList = RulesExt::Global()->AITargetTypesLists.GetItem(attackAITargetType); + + // Still no random target selected + if (idxSelectedObject < 0 && objectsList.Count > 0 && !selected) + { + // Finding the objects from the list that actually exists in the map + for (int i = 0; i < TechnoClass::Array->Count; i++) + { + auto pTechno = TechnoClass::Array->GetItem(i); + auto pTechnoType = TechnoClass::Array->GetItem(i)->GetTechnoType(); + bool found = false; + + for (int j = 0; j < objectsList.Count && !found; j++) + { + auto objectFromList = objectsList.GetItem(j); + + if (pTechnoType == objectFromList + && pTechno->IsAlive + && !pTechno->InLimbo + && pTechno->IsOnMap + && !pTechno->Absorbed + && ((pickAllies + && pTeam->FirstUnit->Owner->IsAlliedWith(pTechno)) + || (!pickAllies + && !pTeam->FirstUnit->Owner->IsAlliedWith(pTechno)))) + { + validIndexes.AddItem(j); + found = true; + } + } + } + + if (validIndexes.Count > 0) + { + idxSelectedObject = validIndexes.GetItem(ScenarioClass::Instance->Random.RandomRanged(0, validIndexes.Count - 1)); + selected = true; + Debug::Log("DEBUG: [%s] [%s] Picked a random Techno from the list index [AITargetTypes][%d][%d] = %s\n", pTeam->Type->ID, pTeam->CurrentScript->Type->ID, attackAITargetType, idxSelectedObject, objectsList.GetItem(idxSelectedObject)->ID); + } + } + + if (selected) + pTeamData->IdxSelectedObjectFromAIList = idxSelectedObject; + + Mission_Move(pTeam, calcThreatMode, pickAllies, attackAITargetType, idxSelectedObject); + } + + // This action finished + if (!selected) + { + pTeam->StepCompleted = true; + Debug::Log("DEBUG: [%s] [%s] Failed to pick a random Techno from the list index [AITargetTypes][%d]! Valid Technos in the list: %d\n", pTeam->Type->ID, pTeam->CurrentScript->Type->ID, attackAITargetType, validIndexes.Count); + } +} + +void ScriptExt::SetCloseEnoughDistance(TeamClass *pTeam, double distance = -1) +{ + // This passive method replaces the CloseEnough value from rulesmd.ini by a custom one. Used by Mission_Move() + if (distance <= 0) + distance = pTeam->CurrentScript->Type->ScriptActions[pTeam->CurrentScript->idxCurrentLine].Argument; + + auto pTeamData = TeamExt::ExtMap.Find(pTeam); + if (pTeamData) + { + if (distance > 0) + pTeamData->CloseEnough = distance; + } + + if (distance <= 0) + pTeamData->CloseEnough = RulesClass::Instance->CloseEnough / 256.0; + + // This action finished + pTeam->StepCompleted = true; + + return; +} + void ScriptExt::UnregisterGreatSuccess(TeamClass* pTeam) { pTeam->AchievedGreatSuccess = false; pTeam->StepCompleted = true; // This action finished - FS-21 } +void ScriptExt::SetMoveMissionEndMode(TeamClass* pTeam, int mode = 0) +{ + // This passive method replaces the CloseEnough value from rulesmd.ini by a custom one. Used by Mission_Move() + if (mode < 0 || mode > 2) + mode = pTeam->CurrentScript->Type->ScriptActions[pTeam->CurrentScript->idxCurrentLine].Argument; + + auto pTeamData = TeamExt::ExtMap.Find(pTeam); + if (pTeamData) + { + if (mode >= 0 && mode <= 2) + pTeamData->MoveMissionEndMode = mode; + } + + // This action finished + pTeam->StepCompleted = true; + + return; +} + +bool ScriptExt::MoveMissionEndStatus(TeamClass* pTeam, TechnoClass* pFocus, FootClass* pLeader = nullptr, int mode = 0) +{ + if (!pTeam || !pFocus || mode < 0) + return false; + + if (mode != 2 && mode != 1 && !pLeader) + return false; + + double closeEnough = RulesClass::Instance->CloseEnough / 256.0; + + auto pTeamData = TeamExt::ExtMap.Find(pTeam); + if (pTeamData && pTeamData->CloseEnough > 0) + closeEnough = pTeamData->CloseEnough; + + bool bForceNextAction; + + if (mode == 2) + bForceNextAction = true; + else + bForceNextAction = false; + + // Team already have a focused target + for (auto pUnit = pTeam->FirstUnit; pUnit; pUnit = pUnit->NextTeamMember) + { + if (pUnit + && pUnit->IsAlive + && !pUnit->InLimbo + && !pUnit->TemporalTargetingMe + && !pUnit->BeingWarpedOut) + { + if (!pUnit->Locomotor->Is_Moving_Now()) + pUnit->SetDestination(pFocus, false); + + if (mode == 2) + { + // Default mode: all members in range + if (pUnit->DistanceFrom(pUnit->Destination) / 256.0 > closeEnough) + { + bForceNextAction = false; + + if (pUnit->GetTechnoType()->WhatAmI() == AbstractType::AircraftType && pUnit->Ammo > 0) + pUnit->QueueMission(Mission::Move, false); + + continue; + } + else + { + if (pUnit->GetTechnoType()->WhatAmI() == AbstractType::AircraftType && pUnit->Ammo <= 0) + { + pUnit->QueueMission(Mission::Return, false); + pUnit->Mission_Enter(); + + continue; + } + } + } + else + { + if (mode == 1) + { + // Any member in range + if (pUnit->DistanceFrom(pUnit->Destination) / 256.0 > closeEnough) + { + if (pUnit->GetTechnoType()->WhatAmI() == AbstractType::AircraftType && pUnit->Ammo > 0) + pUnit->QueueMission(Mission::Move, false); + + continue; + } + else + { + bForceNextAction = true; + + if (pUnit->GetTechnoType()->WhatAmI() == AbstractType::AircraftType && pUnit->Ammo <= 0) + { + pUnit->QueueMission(Mission::Return, false); + pUnit->Mission_Enter(); + + continue; + } + } + } + else + { + // All other cases: Team Leader mode in range + if (pLeader) + { + if (pUnit->DistanceFrom(pUnit->Destination) / 256.0 > closeEnough) + { + if (pUnit->GetTechnoType()->WhatAmI() == AbstractType::AircraftType && pUnit->Ammo > 0) + pUnit->QueueMission(Mission::Move, false); + + continue; + } + else + { + if (pUnit == pLeader) + bForceNextAction = true; + + if (pUnit->GetTechnoType()->WhatAmI() == AbstractType::AircraftType && pUnit->Ammo <= 0) + { + pUnit->QueueMission(Mission::Return, false); + pUnit->Mission_Enter(); + + continue; + } + } + } + else + { + break; + } + } + } + } + } + + return bForceNextAction; +} + void ScriptExt::VariablesHandler(TeamClass* pTeam, PhobosScripts eAction, int nArg) { struct operation_set { int operator()(const int& a, const int& b) { return b; } }; @@ -2519,4 +2866,49 @@ void ScriptExt::VariableBinaryOperationHandler(TeamClass* pTeam, int nVariable, VariableOperationHandler(pTeam, nVariable, itr->second.Value); pTeam->StepCompleted = true; -} \ No newline at end of file +} + +FootClass* ScriptExt::FindTheTeamLeader(TeamClass* pTeam) +{ + FootClass* pLeaderUnit = nullptr; + int bestUnitLeadershipValue = -1; + + if (!pTeam) + { + return pLeaderUnit; + } + + // Find the Leader or promote a new one + for (auto pUnit = pTeam->FirstUnit; pUnit; pUnit = pUnit->NextTeamMember) + { + if (pUnit && pUnit->IsAlive + && !pUnit->InLimbo + && pUnit->IsOnMap + && !pUnit->Absorbed) + { + if (pUnit->IsTeamLeader) + { + pLeaderUnit = pUnit; + break; + } + + auto pUnitType = pUnit->GetTechnoType(); + + if (pUnitType) + { + // The team Leader will be used for selecting targets, if there are living Team Members then always exists 1 Leader. + int unitLeadershipRating = pUnitType->LeadershipRating; + if (unitLeadershipRating > bestUnitLeadershipValue) + { + pLeaderUnit = pUnit; + bestUnitLeadershipValue = unitLeadershipRating; + } + } + } + } + + if (pLeaderUnit) + pLeaderUnit->IsTeamLeader = true; + + return pLeaderUnit; +} diff --git a/src/Ext/Script/Body.h b/src/Ext/Script/Body.h index 22b38a187b..695fe9dc40 100644 --- a/src/Ext/Script/Body.h +++ b/src/Ext/Script/Body.h @@ -142,12 +142,18 @@ class ScriptExt static void UnregisterGreatSuccess(TeamClass * pTeam); static void Mission_Attack_List(TeamClass *pTeam, bool repeatAction, int calcThreatMode, int attackAITargetType); + static void Mission_Attack_List1Random(TeamClass *pTeam, bool repeatAction, int calcThreatMode, int attackAITargetType); + static void Mission_Move_List(TeamClass *pTeam, int calcThreatMode, bool pickAllies, int attackAITargetType); + static void Mission_Move_List1Random(TeamClass *pTeam, int calcThreatMode, bool pickAllies, int attackAITargetType, int idxAITargetTypeItem); + static void SetCloseEnoughDistance(TeamClass *pTeam, double distance); + static void SetMoveMissionEndMode(TeamClass* pTeam, int mode); static void VariablesHandler(TeamClass* pTeam, PhobosScripts eAction, int nArg); template static void VariableOperationHandler(TeamClass* pTeam, int nVariable, int Number); template static void VariableBinaryOperationHandler(TeamClass* pTeam, int nVariable, int nVarToOperate); + static FootClass* FindTheTeamLeader(TeamClass* pTeam); static void LocalVariableAdd(TeamClass* pTeam, int nVariable, int Number); static void LocalVariableMultiply(TeamClass* pTeam, int nVariable, int Number); @@ -174,4 +180,5 @@ class ScriptExt private: static void ModifyCurrentTriggerWeight(TeamClass* pTeam, bool forceJumpLine, double modifier); + static bool MoveMissionEndStatus(TeamClass* pTeam, TechnoClass* pFocus, FootClass* pLeader, int mode); }; diff --git a/src/Ext/Team/Body.cpp b/src/Ext/Team/Body.cpp index 690b026248..fb568a0bd7 100644 --- a/src/Ext/Team/Body.cpp +++ b/src/Ext/Team/Body.cpp @@ -15,6 +15,8 @@ void TeamExt::ExtData::Serialize(T& Stm) .Process(this->IdxSelectedObjectFromAIList) .Process(this->CloseEnough) .Process(this->Countdown_RegroupAtLeader) + .Process(this->MoveMissionEndMode) + .Process(this->SelectedTarget) ; } diff --git a/src/Ext/Team/Body.h b/src/Ext/Team/Body.h index dca3e98e9f..648ff1f6b2 100644 --- a/src/Ext/Team/Body.h +++ b/src/Ext/Team/Body.h @@ -20,6 +20,8 @@ class TeamExt int IdxSelectedObjectFromAIList; double CloseEnough; int Countdown_RegroupAtLeader; + int MoveMissionEndMode; + TechnoClass* SelectedTarget; ExtData(TeamClass* OwnerObject) : Extension(OwnerObject) , WaitNoTargetAttempts { 0 } @@ -27,6 +29,8 @@ class TeamExt , IdxSelectedObjectFromAIList { -1 } , CloseEnough { -1 } , Countdown_RegroupAtLeader { -1 } + , MoveMissionEndMode { 0 } + , SelectedTarget { nullptr } { } virtual ~ExtData() = default; diff --git a/src/Ext/Techno/Hooks.cpp b/src/Ext/Techno/Hooks.cpp index 5f78936451..6a57894401 100644 --- a/src/Ext/Techno/Hooks.cpp +++ b/src/Ext/Techno/Hooks.cpp @@ -38,6 +38,17 @@ DEFINE_HOOK(0x6F42F7, TechnoClass_Init_NewEntities, 0x2) return 0; } +DEFINE_HOOK(0x702E4E, TechnoClass_Save_Killer_Techno, 0x6) +{ + GET(TechnoClass*, pKiller, EDI); + GET(TechnoClass*, pVictim, ECX); + + if (pKiller && pVictim) + TechnoExt::ObjectKilledBy(pVictim, pKiller); + + return 0; +} + DEFINE_HOOK_AGAIN(0x7355C0, TechnoClass_Init_InitialStrength, 0x6) // UnitClass_Init DEFINE_HOOK_AGAIN(0x517D69, TechnoClass_Init_InitialStrength, 0x6) // InfantryClass_Init DEFINE_HOOK_AGAIN(0x442C7B, TechnoClass_Init_InitialStrength, 0x6) // BuildingClass_Init