Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ This page lists all the individual contributions to the project by their author.
- Warhead activation target health thresholds enhancements
- Event 606: AttachEffect is attaching to a Techno
- Linked superweapons
- Engineer logics on Warheads
- Unit & infantry auto-conversion on ammo change
- Restore the ScriptType action#24 `Play speech` from Tiberian Sun
- Modify ammo on impact
Expand Down
19 changes: 19 additions & 0 deletions docs/New-or-Enhanced-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -1990,6 +1990,7 @@ FLHKEY.BurstN= ; integer - Forward,Lateral,Height. FLHKey refers to weapon-spec
- `ForceWeapon.Cloaked` forces specified weapon to be used against any cloaked targets.
- `ForceWeapon.Disguised` forces specified weapon to be used against any disguised targets.
- `ForceWeapon.UnderEMP` forces specified weapon to be used if the target is under EMP effect.
- `ForceWeapon.Capture` forces specified weapon to be used if the target building is capturable.
- `ForceWeapon.InRange` forces specified a list of weapons to be used once the target is within their `Range`. If `ForceWeapon.InRange.TechnoOnly` set to true, it'll only be forced on TechnoTypes like other forced weapons, otherwise it'll also be forced when attacking empty grounds. The first weapon in the listed order satisfied will be selected. Can be applied to both ground and air target if `ForceAAWeapon.InRange` is not set.
- `ForceAAWeapon.InRange` does the same thing but only for air target. Taking priority to `ForceWeapon.InRange`, which means that it can only be applied to ground target when they're both set.
- `Force(AA)Weapon.InRange.Overrides` overrides the range when decides which weapon to use. Value from position matching the position from `Force(AA)Weapon.InRange` is used if found, or the weapon's own `Range` if not found or set to a value below 0.
Expand All @@ -2012,6 +2013,7 @@ ForceWeapon.Naval.Decloaked=-1 ; integer, -1 to disable
ForceWeapon.Cloaked=-1 ; integer, -1 to disable
ForceWeapon.Disguised=-1 ; integer, -1 to disable
ForceWeapon.UnderEMP=-1 ; integer, -1 to disable
ForceWeapon.Capture=-1 ; integer, -1 to disable
ForceWeapon.InRange= ; List of integers
ForceWeapon.InRange.TechnoOnly= ; boolean, default to [General] -> ForceWeapon.InRange.TechnoOnly
ForceWeapon.InRange.Overrides= ; List of floating-point values
Expand Down Expand Up @@ -3025,6 +3027,23 @@ DetonateOnAllMapObjects.RequireVerses=false ; boolean
While this feature can provide better performance than a large `CellSpread` value, it still has potential to slow down the game, especially if used in conjunction with things like animations, alpha lights etc. Modder discretion and use of the filter keys (`AffectsTarget/House/Types` etc.) is advised.
```

### Engineer logics on Warheads

- Now any `InfantryType`, `VehicleType`, `BuildingType` or `AircraftType` can execute some operations engineers do without losing the firer in the process.
- `FakeEngineer.CanRepairBridges`, if set to true, when a building with `BridgeRepairHut=yes` linked to a bridge is affected by the Warhead then all destroyed bridge sections will be fixed.
- `FakeEngineer.CanDestroyBridges`, if set to true, when a building with `BridgeRepairHut=yes` linked to a bridge is affected by the Warhead then all the bridge will be destroyed.
- `FakeEngineer.CanCaptureBuildings`, if set to true, a building with `Capturable=true` is affected by the Warhead then the building will be captured by the house's firer.
- `FakeEngineer.BombDisarm`, if set to true, an attached bomb will be removed if the target is affected by the Warhead.

In `rulesmd.ini`:
```ini
[SOMEWARHEAD] ; WarheadType
FakeEngineer.CanRepairBridges=false ; boolean
FakeEngineer.CanDestroyBridges=false ; boolean
FakeEngineer.CanCaptureBuildings=false ; boolean
FakeEngineer.BombDisarm=false ; boolean
```

### Fire weapon when Warhead kills something

- `KillWeapon` will be fired at the target TechnoType's location once it's killed by this Warhead.
Expand Down
1 change: 1 addition & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,7 @@ HideShakeEffects=false ; boolean
- [Units can customize the attack voice that plays when using more weapons](New-or-Enhanced-Logics.md#multi-voiceattack) (by FlyStar)
- Customize squid grapple animation (by NetsuNegi)
- [Auto deploy for GI-like infantry](Fixed-or-Improved-Logics.md#auto-deploy-for-gi-like-infantry) (by TaranDahl)
- Engineer logics on Warheads (by FS-21)
- [Vehicles that have lost their target can customize the turret direction to align with the vehicle body](New-or-Enhanced-Logics.md#turret-response) (by FlyStar)
- [Reverse engineer warhead](New-or-Enhanced-Logics.md#reverse-engineer-warhead) (by CrimRecya)
- [AI base construction modification](Fixed-or-Improved-Logics.md#ai-base-construction-modification) (by CrimRecya)
Expand Down
15 changes: 15 additions & 0 deletions src/Ext/Infantry/Hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,18 @@ DEFINE_HOOK(0x51A002, InfantryClass_UpdatePosition_InfiltrateBuilding, 0x6)

return 0;
}

// Pass actual target to SelectWeapon instead of -1 during What_Action evaluation
DEFINE_HOOK(0x51E6D8, InfantryClass_WhatAction_WhatWeaponShouldIUse, 0x9)
{
GET(InfantryClass*, pThis, EDI);
GET(AbstractClass*, pTarget, ESI);

int weaponIdx = (pThis && pTarget) ? pThis->SelectWeapon(pTarget) : -1;

R->EAX(weaponIdx);

return 0x51E6E1;
}


171 changes: 171 additions & 0 deletions src/Ext/Techno/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
#include <Ext/Event/Body.h>

#include <Utilities/AresFunctions.h>
#include <Ext/WarheadType/Body.h>
#include <RadarEventClass.h>
#include <Utilities/AresHelper.h>
#include <Interop/TechnoExt.h>

Expand Down Expand Up @@ -672,6 +674,175 @@
return placed;
}

AircraftTypeClass* TechnoExt::GetAircraftTypeExtra(AircraftClass* pAircraft)
{
if (pAircraft->IsGreenHP())
{
return pAircraft->Type;
}
else if (pAircraft->IsYellowHP())
{
auto const pData = TechnoTypeExt::ExtMap.Find(pAircraft->Type);

Check warning on line 685 in src/Ext/Techno/Body.cpp

View workflow job for this annotation

GitHub Actions / build

'CompatExtMap<TechnoTypeExt,TechnoTypeClass>::Find': use the extension class's Fetch instead [D:\a\Phobos\Phobos\Phobos.vcxproj]

if (auto const imageYellow = pData->Image_ConditionYellow)
return abstract_cast<AircraftTypeClass*, true>(imageYellow);
}
else
{
auto const pType = pAircraft->Type;
auto const pData = TechnoTypeExt::ExtMap.Find(pType);

Check warning on line 693 in src/Ext/Techno/Body.cpp

View workflow job for this annotation

GitHub Actions / build

'CompatExtMap<TechnoTypeExt,TechnoTypeClass>::Find': use the extension class's Fetch instead [D:\a\Phobos\Phobos\Phobos.vcxproj]

if (auto const imageRed = pData->Image_ConditionRed)
return abstract_cast<AircraftTypeClass*, true>(imageRed);
else if (auto const imageYellow = pData->Image_ConditionYellow)
return abstract_cast<AircraftTypeClass*, true>(imageYellow);
}

return pAircraft->Type;

}

bool TechnoExt::CanBeAffectedByFakeEngineer(TechnoClass* pThis, TechnoClass* pTarget, bool checkBridge, bool checkCapturableBuilding, bool checkAttachedBombs)
{
if (!pThis || !pTarget)
return false;

const auto pBuilding = abstract_cast<BuildingClass*>(pTarget);
const bool isBuilding = pBuilding && pBuilding->IsAlive && pBuilding->Health > 0;

auto checkWeaponWarhead = [&](WeaponTypeClass* pWeapon) -> bool
{
if (!pWeapon || !pWeapon->Warhead)
return false;

const auto pWHExt = WarheadTypeExt::Fetch(pWeapon->Warhead);
if (!pWHExt->FakeEngineer_CanCaptureBuildings
&& !pWHExt->FakeEngineer_CanRepairBridges
&& !pWHExt->FakeEngineer_CanDestroyBridges
&& !pWHExt->FakeEngineer_BombDisarm)
{
return false;
}

if (checkAttachedBombs
&& pWHExt->FakeEngineer_BombDisarm
&& pTarget->AttachedBomb)
{
return true;
}

if (checkBridge && isBuilding && pBuilding->Type->BridgeRepairHut)
{
CellStruct bridgeRepairHutCell = pBuilding->GetMapCoords();
bool isBridgeDamaged = MapClass::Instance.IsLinkedBridgeDestroyed(bridgeRepairHutCell);

if ((isBridgeDamaged && pWHExt->FakeEngineer_CanRepairBridges)
|| (!isBridgeDamaged && pWHExt->FakeEngineer_CanDestroyBridges))
{
return true;
}
}

if (checkCapturableBuilding
&& isBuilding
&& pWHExt->FakeEngineer_CanCaptureBuildings
&& (pBuilding->Type->Capturable || pBuilding->Type->NeedsEngineer)
&& pBuilding->Owner != pThis->Owner
&& (!pThis->Owner->IsAlliedWith(pBuilding) || pBuilding->Owner->IsNeutral()))
{
return true;
}

return false;
};

// 1. Check Primary and Secondary weapons
if (const auto pPrimary = pThis->GetWeapon(0))
{
if (checkWeaponWarhead(pPrimary->WeaponType))
return true;
}
if (const auto pSecondary = pThis->GetWeapon(1))
{
if (checkWeaponWarhead(pSecondary->WeaponType))
return true;
}

// 2. Check deploy fire weapon
int deployWeaponIdx = -1;
if (auto const pDeployWeapon = TechnoExt::GetDeployFireWeapon(pThis, pThis->GetTechnoType(), deployWeaponIdx))
{
if (checkWeaponWarhead(pDeployWeapon))
return true;
}

return false;
}

void TechnoExt::RepairOrDestroyBridgeHut(BuildingClass* pBuilding, TechnoClass* pOwner, HouseClass* pFiringHouse, bool destroyBridge)
{
if (!pBuilding || !pBuilding->Type->BridgeRepairHut || !pBuilding->IsAlive || pBuilding->Health <= 0)
return;

const CoordStruct targetCoords = pBuilding->GetCenterCoords();
const CellStruct baseCell = pBuilding->GetMapCoords();

// Send engineer's "enter" event
auto const pTag = pBuilding->AttachedTag;

if (pTag && pOwner)
pTag->RaiseEvent(TriggerEvent::EnteredBy, pOwner, CellStruct::Empty);

// Check a 5x5 area for bridge tiles to determine if we should repair or destroy
bool foundWoodBridge = false;

for (int y = -2; y <= 2; ++y)
{
for (int x = -2; x <= 2; ++x)
{
CellStruct checkCellCoords = { static_cast<short>(baseCell.X + x), static_cast<short>(baseCell.Y + y) };
auto const checkCell = MapClass::Instance.GetCellAt(checkCellCoords);

if (checkCell && (checkCell->Tile_Is_WoodBridge() || (checkCell->OverlayTypeIndex >= 74 && checkCell->OverlayTypeIndex <= 101)))
foundWoodBridge = true;

if (foundWoodBridge)
break;
}

if (foundWoodBridge)
break;
}

// Destroying bridges
if (destroyBridge)
{
if (foundWoodBridge) // Destroy wood bridges
MapClass::Instance.DestroyWoodBridgeAt(baseCell);
else // Destroy concrete bridges
MapClass::Instance.DestroyConcreteBridgeAt(baseCell);

return;
}

auto const pFiringOwner = pOwner ? pOwner->Owner : pFiringHouse;

// Repairing bridges
if (pFiringOwner && pFiringOwner->IsControlledByCurrentPlayer())
{
if (RadarEventClass::Create(RadarEventType::BridgeRepaired, baseCell))
VoxClass::PlayIndex(VoxClass::FindIndex("EVA_BridgeRepaired"));
}

if (RulesClass::Instance->RepairBridgeSound != -1)
VocClass::PlayAt(RulesClass::Instance->RepairBridgeSound, targetCoords, nullptr);

if (foundWoodBridge) // Repair wood bridges
MapClass::Instance.RepairWoodBridgeAt(baseCell);
else // Repair concrete bridges
MapClass::Instance.RepairConcreteBridgeAt(baseCell);
}

void TechnoExt::ResetDelayedFireTimer()
{
this->DelayedFireTimer.Stop();
Expand Down
5 changes: 5 additions & 0 deletions src/Ext/Techno/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,11 @@ class TechnoExt : public RadioExt, public Detach::Listener<AirstrikeClass>
static void CreateDelayedFireAnim(TechnoClass* pThis, AnimTypeClass* pAnimType, int weaponIndex, bool attach, bool center, bool removeOnNoDelay, bool onTurret, CoordStruct firingCoords);
static bool HandleDelayedFireWithPauseSequence(TechnoClass* pThis, WeaponTypeClass* pWeapon, int weaponIndex, int frame, int firingFrame);
static bool IsHealthInThreshold(TechnoClass* pObject, double min, double max);
static UnitTypeClass* GetUnitTypeExtra(UnitClass* pUnit);
static AircraftTypeClass* GetAircraftTypeExtra(AircraftClass* pAircraft);
static bool CanBeAffectedByFakeEngineer(TechnoClass* pThis, TechnoClass* pBuilding, bool checkBridge = false, bool checkCapturableBuilding = false, bool checkAttachedBombs = false);
static void RepairOrDestroyBridgeHut(BuildingClass* pBuilding, TechnoClass* pOwner = nullptr, HouseClass* pFiringHouse = nullptr, bool destroyBridge = false);
static bool CannotMove(UnitClass* pThis);
static void ShowPromoteAnim(TechnoClass* pThis);
static void ClickedApproachObject(FootClass* pThis, ObjectClass* pObject);
static bool CanBeRecruitedFix(FootClass* pThis, HouseClass* pHouse);
Expand Down
36 changes: 35 additions & 1 deletion src/Ext/Techno/Hooks.Firing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,12 @@ DEFINE_HOOK(0x6F3428, TechnoClass_WhatWeaponShouldIUse_ForceWeapon, 0x6)
{
enum { UseWeaponIndex = 0x6F37AF };

GET(TechnoClass*, pThis, ECX);
GET(TechnoClass*, pThis, ESI);
GET_STACK(AbstractClass*, pTarget, STACK_OFFSET(0x18, 0x4));

if (!pThis || !pTarget)
return 0;

auto const pTypeExt = TechnoExt::Fetch(pThis)->TypeExtData;

// Force weapon
Expand Down Expand Up @@ -137,6 +140,37 @@ DEFINE_HOOK(0x6F36DB, TechnoClass_WhatWeaponShouldIUse, 0x8)
if (!pTargetTechno)
return Primary;

if (TechnoExt::CanBeAffectedByFakeEngineer(pThis, pTargetTechno, true, true, true))
{
auto const pSecWeapon = pThis->GetWeapon(1);
if (pSecWeapon && pSecWeapon->WeaponType && pSecWeapon->WeaponType->Warhead)
{
auto const pSecWHExt = WarheadTypeExt::Fetch(pSecWeapon->WeaponType->Warhead);
const bool secHasFakeEngineer = pSecWHExt->FakeEngineer_CanCaptureBuildings
|| pSecWHExt->FakeEngineer_CanRepairBridges
|| pSecWHExt->FakeEngineer_CanDestroyBridges
|| pSecWHExt->FakeEngineer_BombDisarm;

if (secHasFakeEngineer)
{
auto const pPriWeapon = pThis->GetWeapon(0);
if (!pPriWeapon || !pPriWeapon->WeaponType || !pPriWeapon->WeaponType->Warhead)
return Secondary;

auto const pPriWHExt = WarheadTypeExt::Fetch(pPriWeapon->WeaponType->Warhead);
const bool priHasFakeEngineer = pPriWHExt->FakeEngineer_CanCaptureBuildings
|| pPriWHExt->FakeEngineer_CanRepairBridges
|| pPriWHExt->FakeEngineer_CanDestroyBridges
|| pPriWHExt->FakeEngineer_BombDisarm;

if (!priHasFakeEngineer)
return Secondary;
}
}

return Primary;
}

const auto pTargetExt = TechnoExt::Fetch(pTargetTechno);

if (const auto pShield = pTargetExt->Shield.get())
Expand Down
26 changes: 26 additions & 0 deletions src/Ext/Techno/Hooks.ReceiveDamage.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
#include "Body.h"

#include <TacticalClass.h>
#include <RadarEventClass.h>
#include <BombClass.h>
#include <Ext/Building/Body.h>
#include <Ext/House/Body.h>
#include <Ext/InfantryType/Body.h>
Expand Down Expand Up @@ -81,6 +86,27 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6)
}
}

auto const pBuilding = abstract_cast<BuildingClass*>(pThis);

// Capture enemy/neutral buildings
const bool canCaptureHouse = pBuilding && pBuilding->Owner != pSourceHouse
&& (!pSourceHouse->IsAlliedWith(pTargetHouse) || pBuilding->Owner->IsNeutral());

if (canCaptureHouse && pWHExt->FakeEngineer_CanCaptureBuildings
&& (pBuilding->Type->Capturable || pBuilding->Type->NeedsEngineer))
{
// Send engineer's "enter" event
auto const pTag = pBuilding->AttachedTag;
if (args->Attacker && pTag)
pTag->RaiseEvent(TriggerEvent::EnteredBy, args->Attacker, CellStruct::Empty);

pBuilding->SetOwningHouse(pSourceHouse, true);
}

// Disarm bomb
if (pThis->AttachedBomb && pWHExt->FakeEngineer_BombDisarm)
pThis->AttachedBomb->Disarm();

// Raise Combat Alert
if (RulesExt::Global()->CombatAlert && damage > 1)
{
Expand Down
Loading
Loading