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 @@ -411,6 +411,7 @@ This page lists all the individual contributions to the project by their author.
- Vehicle Deployment Enhancement
- Fix an issue where miners affected by `Passengers/DeployFire` were unable to unload minerals
- Fix an issue where mining vehicles could not move after leaving a tank bunker
- Separation of AutoTarget for `DeployFireWeapon`, `OpenTransportWeapon`, and `NoAmmoWeapon`
- **NetsuNegi**:
- Forbidding parallel AI queues by type
- Jumpjet crash speed fix when crashing onto building
Expand Down
11 changes: 11 additions & 0 deletions docs/Fixed-or-Improved-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -1799,6 +1799,17 @@ In `rulesmd.ini`:
RadarInvisibleToHouse= ; Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all), default to enemy if RadarInvisible=true, none otherwise
```

### Separation AutoTarget

- In vanilla, when a unit's `TurretCount` is not greater than 0 and `IsGattling=yes` is not set, it calculates the sum of all weapons' `Damage` and all weapons' `AmbientDamage`, then divides by the number of weapons (rounding towards zero). If the result is less than 0, the unit is considered to use a repair weapon; otherwise, it is an offensive weapon. This may cause units that have both repair and damage weapons to be unable to properly use `DeployFireWeapon`, `OpenTransportWeapon`, and `NoAmmoWeapon`. Now this issue can be resolved by the newly added `SeparateWeaponTypes` list.
- When using separation auto target, if `GuardRange` is not set, the effective range will use the current weapon's own `Range`, rather than the maximum `Range` among all the unit's weapons. Functions such as whether it can attack ground or air targets, and whether it uses repair behavior, which were originally based on average calculations, are now entirely determined by the current weapon's own settings.

In `rulesmd.ini`:
```ini
[SOMETECHNO] ; TechnoType
SeparateWeaponTypes=none ; List of SeparateWeaponType Enumeration (none|deployfireweapon|opentransport|noammo|all)
```

### Subterranean unit travel height and speed

- It is now possible to control the height at which units with subterranean (Tunnel) `Locomotor` travel, globally or per TechnoType.
Expand Down
1 change: 1 addition & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,7 @@ New:
- [Penetrates damage on transporter](New-or-Enhanced-Logics.md#penetrates-damage-on-transporter) (by NetsuNegi)
- Added amount limit of `LimboKill` (by NetsuNegi)
- [Customizations for techno type target scan/guard range](Fixed-or-Improved-Logics.md#target-scan-guard-range-customizations) (by Starkku)
- [Separation of AutoTarget for `DeployFireWeapon`, `OpenTransportWeapon`, and `NoAmmoWeapon`](Fixed-or-Improved-Logics.md#separation-autotarget) (by FlyStar)

Vanilla fixes:
- Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya)
Expand Down
10 changes: 10 additions & 0 deletions src/Ext/Infantry/Hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,16 @@ DEFINE_HOOK(0x51EE6B, InfantryClass_WhatAction_ObjectClass_InfiltrateForceAttack
return WhatActionObjectTemp::Fire ? 0x51F05E : 0;
}

// Setting Ares' NoSelfGuardArea to yes will also disable this feature. I'm not sure if it should be removed.
DEFINE_HOOK(0x51E748, InfantryClass_WhatAction_ObjectClass_SkipGuardArea, 0x8)
{
GET(InfantryClass* const, pThis, EDI);
GET(const Action, action, EBP);
enum { SkipGameCode = 0x51E7A6 };

return (action == Action::Self_Deploy || pThis->IsDeployed()) ? SkipGameCode : 0;
}

DEFINE_HOOK(0x51ECC0, InfantryClass_WhatAction_ObjectClass_IsAreaFire, 0xA)
{
enum { IsAreaFire = 0x51ECE5, NotAreaFire = 0x51ECEC };
Expand Down
30 changes: 14 additions & 16 deletions src/Ext/TechnoType/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -452,33 +452,27 @@ void TechnoTypeExt::ExtData::UpdateAdditionalAttributes()
if (!pWeapon)
return;

const int combatDamage = (pWeapon->Damage + pWeapon->AmbientDamage);
const ThreatType threats = pWeapon->Projectile ? pWeapon->AllowedThreats() : ThreatType::Normal;
const bool attackFriendlies = WeaponTypeExt::ExtMap.Find(pWeapon)->AttackFriendlies.Get(false);

if (isElite)
{
if (pWeapon->Projectile)
this->ThreatTypes.Y |= pWeapon->AllowedThreats();

this->CombatDamages.Y += (pWeapon->Damage + pWeapon->AmbientDamage);
this->ThreatTypes.Y |= threats;
this->CombatDamages.Y += combatDamage;
eliteNum++;

if (!this->AttackFriendlies.Y
&& WeaponTypeExt::ExtMap.Find(pWeapon)->AttackFriendlies.Get(false))
{
if (!this->AttackFriendlies.Y && attackFriendlies)
this->AttackFriendlies.Y = true;
}
}
else
{
if (pWeapon->Projectile)
this->ThreatTypes.X |= pWeapon->AllowedThreats();

this->CombatDamages.X += (pWeapon->Damage + pWeapon->AmbientDamage);
this->ThreatTypes.X |= threats;
this->CombatDamages.X += combatDamage;
num++;

if (!this->AttackFriendlies.X
&& WeaponTypeExt::ExtMap.Find(pWeapon)->AttackFriendlies.Get(false))
{
if (!this->AttackFriendlies.X && attackFriendlies)
this->AttackFriendlies.X = true;
}
}
};

Expand Down Expand Up @@ -1157,6 +1151,8 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
this->PenetratesTransport_FatalRateMultiplier.Read(exINI, pSection, "PenetratesTransport.FatalRateMultiplier");
this->PenetratesTransport_DamageMultiplier.Read(exINI, pSection, "PenetratesTransport.DamageMultiplier");

this->SeparateWeaponTypes.Read(exINI, pSection, "SeparateWeaponTypes");

// Ares 0.2
this->RadarJamRadius.Read(exINI, pSection, "RadarJamRadius");

Expand Down Expand Up @@ -1865,6 +1861,8 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm)
.Process(this->PenetratesTransport_PassThroughMultiplier)
.Process(this->PenetratesTransport_FatalRateMultiplier)
.Process(this->PenetratesTransport_DamageMultiplier)

.Process(this->SeparateWeaponTypes)
;
}
void TechnoTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm)
Expand Down
4 changes: 4 additions & 0 deletions src/Ext/TechnoType/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,8 @@ class TechnoTypeExt
Valueable<double> PenetratesTransport_FatalRateMultiplier;
Valueable<double> PenetratesTransport_DamageMultiplier;

Valueable<SeparateWeaponType> SeparateWeaponTypes;

ExtData(TechnoTypeClass* OwnerObject) : Extension<TechnoTypeClass>(OwnerObject)
, HealthBar_Hide { false }
, HealthBar_HidePips { false }
Expand Down Expand Up @@ -917,6 +919,8 @@ class TechnoTypeExt
, PenetratesTransport_PassThroughMultiplier { 1.0 }
, PenetratesTransport_FatalRateMultiplier { 1.0 }
, PenetratesTransport_DamageMultiplier { 1.0 }

, SeparateWeaponTypes { SeparateWeaponType::None }
{ }

virtual ~ExtData() = default;
Expand Down
143 changes: 140 additions & 3 deletions src/Ext/TechnoType/Hooks.MultiWeapon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,24 @@ DEFINE_HOOK(0x7090A0, TechnoClass_VoiceAttack, 0x7)
return 0x7091C7;
}

bool __forceinline IsDeployed(TechnoClass* const pThis, const AbstractType rtti)
{
switch (rtti)
{
case AbstractType::Infantry:
return static_cast<InfantryClass*>(pThis)->IsDeployed();
case AbstractType::Unit:
{
auto const pUnit = static_cast<UnitClass*>(pThis);

return pUnit->Deployed || (pUnit->Type->DeployFire &&
pThis->CurrentMission == Mission::Unload);
}
default:
return false;
}
}

static __forceinline ThreatType GetThreatType(TechnoClass* pThis, TechnoTypeExt::ExtData* pTypeExt, ThreatType result)
{
const ThreatType flags = pThis->Veterancy.IsElite() ? pTypeExt->ThreatTypes.Y : pTypeExt->ThreatTypes.X;
Expand All @@ -157,9 +175,9 @@ DEFINE_HOOK(0x7431C9, FootClass_SelectAutoTarget_MultiWeapon, 0x7) // UnitClas
GET(FootClass*, pThis, ESI);
GET(const ThreatType, result, EDI);

const bool isUnit = R->Origin() == 0x7431C9;
const auto pTypeExt = TechnoExt::ExtMap.Find(pThis)->TypeExtData;
const auto pType = pTypeExt->OwnerObject();
const bool isUnit = R->Origin() == 0x7431C9;

if (isUnit
&& !pType->IsGattling && pType->TurretCount > 0
Expand All @@ -168,6 +186,57 @@ DEFINE_HOOK(0x7431C9, FootClass_SelectAutoTarget_MultiWeapon, 0x7) // UnitClas
return UnitGunner;
}

const SeparateWeaponType weaponTypes = pTypeExt->SeparateWeaponTypes;

if (weaponTypes & SeparateWeaponType::DeployFire)
{
const AbstractType rtti = isUnit ? AbstractType::Unit : AbstractType::Infantry;
const int deployFireWeapon = pType->DeployFireWeapon;

if (IsDeployed(pThis, rtti) && pType->DeployFire && deployFireWeapon >= 0)
{
ThreatType flags = result;

if (const auto pWeapon = pThis->GetWeapon(deployFireWeapon)->WeaponType)
flags |= pWeapon->AllowedThreats();

R->EDI(flags);
return isUnit ? UnitReturn : InfantryReturn;
}
}

if (weaponTypes & SeparateWeaponType::OpenTransport)
{
const int openTransportWeapon = pType->OpenTransportWeapon;

if (pThis->InOpenToppedTransport && openTransportWeapon >= 0)
{
ThreatType flags = result;

if (const auto pWeapon = pThis->GetWeapon(openTransportWeapon)->WeaponType)
flags |= pWeapon->AllowedThreats();

R->EDI(flags);
return isUnit ? UnitReturn : InfantryReturn;
}
}

if (weaponTypes & SeparateWeaponType::NoAmmo)
{
const int noAmmoWeapon = pTypeExt->NoAmmoWeapon;

if (pType->Ammo >= 0 && noAmmoWeapon >= 0 && pThis->Ammo <= pTypeExt->NoAmmoAmount)
{
ThreatType flags = result;

if (const auto pWeapon = pThis->GetWeapon(noAmmoWeapon)->WeaponType)
flags |= pWeapon->AllowedThreats();

R->EDI(flags);
return isUnit ? UnitReturn : InfantryReturn;
}
}

R->EDI(GetThreatType(pThis, pTypeExt, result));
return isUnit ? UnitReturn : InfantryReturn;
}
Expand All @@ -185,7 +254,26 @@ DEFINE_HOOK(0x445F04, BuildingClass_SelectAutoTarget_MultiWeapon, 0xA)
return Continue;
}

R->EDI(GetThreatType(pThis, TechnoTypeExt::ExtMap.Find(pThis->Type), result));
const auto pType = pThis->Type;
const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pType);

if (pTypeExt->SeparateWeaponTypes & SeparateWeaponType::NoAmmo)
{
const int noAmmoWeapon = pTypeExt->NoAmmoWeapon;

if (pType->Ammo >= 0 && noAmmoWeapon >= 0 && pThis->Ammo <= pTypeExt->NoAmmoAmount)
{
ThreatType flags = result;

if (const auto pWeapon = pThis->GetWeapon(noAmmoWeapon)->WeaponType)
flags |= pWeapon->AllowedThreats();

R->EDI(flags);
return ReturnThreatType;
}
}

R->EDI(GetThreatType(pThis, pTypeExt, result));
return ReturnThreatType;
}

Expand Down Expand Up @@ -215,7 +303,56 @@ DEFINE_HOOK(0x6F398E, TechnoClass_CombatDamage_MultiWeapon, 0x7)
return GunnerDamage;
}

const SeparateWeaponType weaponTypes = pTypeExt->SeparateWeaponTypes;

if (weaponTypes & SeparateWeaponType::DeployFire)
{
const int deployFireWeapon = pType->DeployFireWeapon;

if (IsDeployed(pThis, rtti) && pType->DeployFire && deployFireWeapon >= 0)
{
int damage = 0;

if (auto const pWeapon = pThis->GetWeapon(deployFireWeapon)->WeaponType)
damage = (pWeapon->Damage + pWeapon->AmbientDamage);

R->EAX(damage);
return ReturnDamage;
}
}

if (weaponTypes & SeparateWeaponType::OpenTransport)
{
const int openTransportWeapon = pType->OpenTransportWeapon;

if (pThis->InOpenToppedTransport && openTransportWeapon >= 0)
{
int damage = 0;

if (auto const pWeapon = pThis->GetWeapon(openTransportWeapon)->WeaponType)
damage = (pWeapon->Damage + pWeapon->AmbientDamage);

R->EAX(damage);
return ReturnDamage;
}
}

if (weaponTypes & SeparateWeaponType::NoAmmo)
{
const int noAmmoWeapon = pTypeExt->NoAmmoWeapon;

if (pType->Ammo >= 0 && noAmmoWeapon >= 0 && pThis->Ammo <= pTypeExt->NoAmmoAmount)
{
int damage = 0;

if (auto const pWeapon = pThis->GetWeapon(noAmmoWeapon)->WeaponType)
damage = (pWeapon->Damage + pWeapon->AmbientDamage);

R->EAX(damage);
return ReturnDamage;
}
}

R->EAX(pThis->Veterancy.IsElite() ? pTypeExt->CombatDamages.Y : pTypeExt->CombatDamages.X);
return ReturnDamage;
}

12 changes: 12 additions & 0 deletions src/Utilities/Enum.h
Original file line number Diff line number Diff line change
Expand Up @@ -394,3 +394,15 @@ class MouseCursorHotSpotY
return false;
}
};

enum class SeparateWeaponType : unsigned char
{
None = 0x0,
DeployFire = 0x1,
OpenTransport = 0x2,
NoAmmo = 0x4,

All = DeployFire | OpenTransport | NoAmmo
};

MAKE_ENUM_FLAGS(SeparateWeaponType);
44 changes: 44 additions & 0 deletions src/Utilities/TemplateDef.h
Original file line number Diff line number Diff line change
Expand Up @@ -1508,6 +1508,50 @@ if(_strcmpi(parser.value(), #name) == 0){ value = __uuidof(name ## LocomotionCla
Debug::INIParseFailed(pSection, pKey, pCur);
}
}

template <>
inline bool read<SeparateWeaponType>(SeparateWeaponType& value, INI_EX& parser, const char* pSection, const char* pKey)
{
if (parser.ReadString(pSection, pKey))
{
static const std::pair<const char*, SeparateWeaponType> Names[] =
{
{"deployfire", SeparateWeaponType::DeployFire},
{"opentransport", SeparateWeaponType::OpenTransport},
{"noammo", SeparateWeaponType::NoAmmo},
{"all", SeparateWeaponType::All},
{ "none", SeparateWeaponType::None },
};

auto parsed = SeparateWeaponType::None;
for (auto&& part : std::string_view { parser.value() } | std::views::split(','))
{
std::string_view&& cur { part.begin(), part.end() };
*const_cast<char*>(cur.data() + cur.find_last_not_of(" \t\r") + 1) = 0;
auto pCur = cur.data() + cur.find_first_not_of(" \t\r");
bool matched = false;
for (auto const& [name, val] : Names)
{
if (_strcmpi(pCur, name) == 0)
{
parsed |= val;
matched = true;
break;
}
}
if (!matched)
{
Debug::INIParseFailed(pSection, pKey, pCur, "Expected an separate weapon type");
return false;
}
}

value = parsed;
return true;
}

return false;
}
}


Expand Down