Skip to content
Merged
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ Credits
- Xkein - general assistance, YRpp edits
- thomassneddon - general assistance
- Starkku - Warhead shield penetration & breaking, strafing aircraft weapon customization
- SukaHati (Erzoid) - Minimum interceptor guard range

Thanks to everyone who uses Phobos, tests changes and reports bugs! You can show your appreciation and help project by displaying the logo (monochrome version can be found [here](https://github.com/Phobos-developers/Phobos/logo-mono.png)) in your client/launcher, contributing or donating to us via links on the right and the `Sponsor` button on top of the repo.

Expand Down
14 changes: 10 additions & 4 deletions docs/New-or-Enhanced-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,13 +273,19 @@ SplashList.PickRandom=no ; play a random animation from the list? boolean, defau
*Interception logic used in [Tiberium Crisis](https://www.moddb.com/mods/tiberium-crisis) mod*

- Projectiles can now be made targetable by certain TechnoTypes. Interceptor TechnoType's projectile must be `Inviso=yes` in order for it to work and the projectile must be used in a primary Weapon.
- `Interceptor.GuardRange` is maximum range of the unit to intercept projectile. The unit weapon range will limit the unit interception range though.
- `Interceptor.EliteGuardRange` value is used if the unit veterancy is Elite.
- `Interceptor.MinimumGuardRange` is the minimum range of the unit to intercept projectile. Any projectile under this range will not be intercepted.
- `Interceptor.EliteMinimumGuardRange` value is used if the unit veterancy is Elite.

In `rulesmd.ini`:
```ini
[SOMETECHNO] ; TechnoType
Interceptor=no ; boolean
Interceptor.GuardRange=0.0 ; double
Interceptor.EliteGuardRange=0.0 ; double
[SOMETECHNO] ; TechnoType
Interceptor=no ; boolean
Interceptor.GuardRange=0.0 ; double
Interceptor.EliteGuardRange=0.0 ; double
Interceptor.MinimumGuardRange=0.0 ; double
Interceptor.EliteMinimumGuardRange=0.0 ; double

[SOMEPROJECTILE] ; Projectile
Interceptable=no ; boolean
Expand Down
280 changes: 143 additions & 137 deletions src/Ext/Techno/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,141 +14,147 @@ TechnoExt::ExtContainer TechnoExt::ExtMap;

void TechnoExt::ApplyMindControlRangeLimit(TechnoClass* pThis)
{
if (auto Capturer = pThis->MindControlledBy)
{
auto pCapturerExt = TechnoTypeExt::ExtMap.Find(Capturer->GetTechnoType());
if (pCapturerExt && pCapturerExt->MindControlRangeLimit > 0 && pThis->DistanceFrom(Capturer) > pCapturerExt->MindControlRangeLimit * 256.0)
{
Capturer->CaptureManager->FreeUnit(pThis);

if (!pThis->IsHumanControlled)
pThis->QueueMission(Mission::Hunt, 0);
}
}
if (auto Capturer = pThis->MindControlledBy)
{
auto pCapturerExt = TechnoTypeExt::ExtMap.Find(Capturer->GetTechnoType());
if (pCapturerExt && pCapturerExt->MindControlRangeLimit > 0 && pThis->DistanceFrom(Capturer) > pCapturerExt->MindControlRangeLimit * 256.0)
{
Capturer->CaptureManager->FreeUnit(pThis);

if (!pThis->IsHumanControlled)
pThis->QueueMission(Mission::Hunt, 0);
}
}
}

void TechnoExt::ApplyInterceptor(TechnoClass* pThis)
{
auto pData = TechnoExt::ExtMap.Find(pThis);
auto pTypeData = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType());
if (pData && pTypeData && pTypeData->Interceptor && !pThis->Target &&
!(pThis->WhatAmI() == AbstractType::Aircraft && pThis->GetHeight() <= 0))
{
for (auto const& pBullet : *BulletClass::Array)
{
if (auto pBulletTypeData = BulletTypeExt::ExtMap.Find(pBullet->Type))
{
if (!pBulletTypeData->Interceptable)
continue;
}

const double guardRange = pThis->Veterancy.IsElite() ?
pTypeData->Interceptor_EliteGuardRange * 256 : pTypeData->Interceptor_GuardRange * 256;

if (pBullet->Location.DistanceFrom(pThis->Location) > guardRange)
continue;

if (pBullet->Location.DistanceFrom(pBullet->TargetCoords) >
double(ScenarioClass::Instance->Random.RandomRanged(128, (int)guardRange / 10)) * 10)
{
continue;
}

if (!pThis->Owner->IsAlliedWith(pBullet->Owner))
{
pThis->SetTarget(pBullet);
pData->InterceptedBullet = pBullet;
break;
}
}
}
auto pData = TechnoExt::ExtMap.Find(pThis);
auto pTypeData = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType());

if (pData && pTypeData && pTypeData->Interceptor && !pThis->Target &&
!(pThis->WhatAmI() == AbstractType::Aircraft && pThis->GetHeight() <= 0))
{
for (auto const& pBullet : *BulletClass::Array)
{
if (auto pBulletTypeData = BulletTypeExt::ExtMap.Find(pBullet->Type))
{
if (!pBulletTypeData->Interceptable)
continue;
}

const double guardRange = pThis->Veterancy.IsElite() ?
pTypeData->Interceptor_EliteGuardRange * 256 : pTypeData->Interceptor_GuardRange * 256;
const double minguardRange = pThis->Veterancy.IsElite() ?
pTypeData->Interceptor_EliteMinimumGuardRange * 256 : pTypeData->Interceptor_MinimumGuardRange * 256;

double distance = pBullet->Location.DistanceFrom(pThis->Location);
if (distance > guardRange || distance < minguardRange)
continue;

/*
if (pBullet->Location.DistanceFrom(pBullet->TargetCoords) >
double(ScenarioClass::Instance->Random.RandomRanged(128, (int)guardRange / 10)) * 10)
{
continue;
}
*/

if (!pThis->Owner->IsAlliedWith(pBullet->Owner))
{
pThis->SetTarget(pBullet);
pData->InterceptedBullet = pBullet;
break;
}
}
}
}

void TechnoExt::ApplyPowered_KillSpawns(TechnoClass* pThis)
{
auto pTypeData = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType());
if (pTypeData && pThis->WhatAmI() == AbstractType::Building)
{
auto pBuilding = abstract_cast<BuildingClass*>(pThis);
if (pTypeData->Powered_KillSpawns && pBuilding->Type->Powered && !pBuilding->IsPowerOnline())
{
if (auto pManager = pBuilding->SpawnManager)
{
pManager->ResetTarget();
for (auto pItem : pManager->SpawnedNodes)
{
if (pItem->Status == SpawnNodeStatus::Attacking || pItem->Status == SpawnNodeStatus::Returning)
{
pItem->Unit->ReceiveDamage(&pItem->Unit->Health, 0,
RulesClass::Global()->C4Warhead, nullptr, false, false, nullptr);
}
}
}
}
}
auto pTypeData = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType());
if (pTypeData && pThis->WhatAmI() == AbstractType::Building)
{
auto pBuilding = abstract_cast<BuildingClass*>(pThis);
if (pTypeData->Powered_KillSpawns && pBuilding->Type->Powered && !pBuilding->IsPowerOnline())
{
if (auto pManager = pBuilding->SpawnManager)
{
pManager->ResetTarget();
for (auto pItem : pManager->SpawnedNodes)
{
if (pItem->Status == SpawnNodeStatus::Attacking || pItem->Status == SpawnNodeStatus::Returning)
{
pItem->Unit->ReceiveDamage(&pItem->Unit->Health, 0,
RulesClass::Global()->C4Warhead, nullptr, false, false, nullptr);
}
}
}
}
}
}

void TechnoExt::ApplySpawn_LimitRange(TechnoClass* pThis)
{
auto pTypeData = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType());
if (pTypeData && pTypeData->Spawn_LimitedRange)
{
if (auto pManager = pThis->SpawnManager)
{
auto pTechnoType = pThis->GetTechnoType();
int weaponRange = 0;
int weaponRangeExtra = pTypeData->Spawn_LimitedExtraRange * 256;

auto setWeaponRange = [&weaponRange](WeaponTypeClass* pWeaponType)
{
if (pWeaponType && pWeaponType->Spawner && pWeaponType->Range > weaponRange)
weaponRange = pWeaponType->Range;
};

setWeaponRange(pTechnoType->Weapon[0].WeaponType);
setWeaponRange(pTechnoType->Weapon[1].WeaponType);
setWeaponRange(pTechnoType->EliteWeapon[0].WeaponType);
setWeaponRange(pTechnoType->EliteWeapon[1].WeaponType);

weaponRange += weaponRangeExtra;

if (pManager->Target && (pThis->DistanceFrom(pManager->Target) > weaponRange))
pManager->ResetTarget();
}
}
auto pTypeData = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType());
if (pTypeData && pTypeData->Spawn_LimitedRange)
{
if (auto pManager = pThis->SpawnManager)
{
auto pTechnoType = pThis->GetTechnoType();
int weaponRange = 0;
int weaponRangeExtra = pTypeData->Spawn_LimitedExtraRange * 256;

auto setWeaponRange = [&weaponRange](WeaponTypeClass* pWeaponType)
{
if (pWeaponType && pWeaponType->Spawner && pWeaponType->Range > weaponRange)
weaponRange = pWeaponType->Range;
};

setWeaponRange(pTechnoType->Weapon[0].WeaponType);
setWeaponRange(pTechnoType->Weapon[1].WeaponType);
setWeaponRange(pTechnoType->EliteWeapon[0].WeaponType);
setWeaponRange(pTechnoType->EliteWeapon[1].WeaponType);

weaponRange += weaponRangeExtra;

if (pManager->Target && (pThis->DistanceFrom(pManager->Target) > weaponRange))
pManager->ResetTarget();
}
}
}

bool TechnoExt::IsHarvesting(TechnoClass* pThis)
{
if (!pThis || pThis->InLimbo)
return false;

auto slave = pThis->SlaveManager;
if (slave && slave->State != SlaveManagerStatus::Ready)
return true;
if (!pThis || pThis->InLimbo)
return false;

if (pThis->WhatAmI() == AbstractType::Building && pThis->IsPowerOnline())
return true;
auto slave = pThis->SlaveManager;
if (slave && slave->State != SlaveManagerStatus::Ready)
return true;

auto mission = pThis->GetCurrentMission();
if ((mission == Mission::Harvest || mission == Mission::Unload || mission == Mission::Enter)
&& TechnoExt::HasAvailableDock(pThis))
{
return true;
}
if (pThis->WhatAmI() == AbstractType::Building && pThis->IsPowerOnline())
return true;

return false;
auto mission = pThis->GetCurrentMission();
if ((mission == Mission::Harvest || mission == Mission::Unload || mission == Mission::Enter)
&& TechnoExt::HasAvailableDock(pThis))
{
return true;
}

return false;
}

bool TechnoExt::HasAvailableDock(TechnoClass* pThis)
{
for (auto pBld : pThis->GetTechnoType()->Dock)
{
if (pThis->Owner->CountOwnedAndPresent(pBld))
return true;
}
for (auto pBld : pThis->GetTechnoType()->Dock)
{
if (pThis->Owner->CountOwnedAndPresent(pBld))
return true;
}

return false;
return false;
}

// =============================
Expand All @@ -157,34 +163,34 @@ bool TechnoExt::HasAvailableDock(TechnoClass* pThis)
template <typename T>
void TechnoExt::ExtData::Serialize(T& Stm)
{
Stm
.Process(this->InterceptedBullet)
.Process(this->ShieldData)
;
Stm
.Process(this->InterceptedBullet)
.Process(this->ShieldData)
;
}

void TechnoExt::ExtData::LoadFromStream(PhobosStreamReader& Stm)
{
Extension<TechnoClass>::LoadFromStream(Stm);
this->Serialize(Stm);
Extension<TechnoClass>::LoadFromStream(Stm);
this->Serialize(Stm);
}

void TechnoExt::ExtData::SaveToStream(PhobosStreamWriter& Stm)
{
Extension<TechnoClass>::SaveToStream(Stm);
this->Serialize(Stm);
Extension<TechnoClass>::SaveToStream(Stm);
this->Serialize(Stm);
}

bool TechnoExt::LoadGlobals(PhobosStreamReader& Stm)
{
return Stm
.Success();
return Stm
.Success();
}

bool TechnoExt::SaveGlobals(PhobosStreamWriter& Stm)
{
return Stm
.Success();
return Stm
.Success();
}

// =============================
Expand All @@ -201,43 +207,43 @@ void TechnoExt::ExtContainer::InvalidatePointer(void* ptr, bool bRemoved) { }

DEFINE_HOOK(6F3260, TechnoClass_CTOR, 5)
{
GET(TechnoClass*, pItem, ESI);
GET(TechnoClass*, pItem, ESI);

TechnoExt::ExtMap.FindOrAllocate(pItem);
TechnoExt::ExtMap.FindOrAllocate(pItem);

return 0;
return 0;
}

DEFINE_HOOK(6F4500, TechnoClass_DTOR, 5)
{
GET(TechnoClass*, pItem, ECX);
GET(TechnoClass*, pItem, ECX);

TechnoExt::ExtMap.Remove(pItem);
TechnoExt::ExtMap.Remove(pItem);

return 0;
return 0;
}

DEFINE_HOOK_AGAIN(70C250, TechnoClass_SaveLoad_Prefix, 8)
DEFINE_HOOK(70BF50, TechnoClass_SaveLoad_Prefix, 5)
{
GET_STACK(TechnoClass*, pItem, 0x4);
GET_STACK(IStream*, pStm, 0x8);
GET_STACK(TechnoClass*, pItem, 0x4);
GET_STACK(IStream*, pStm, 0x8);

TechnoExt::ExtMap.PrepareStream(pItem, pStm);
TechnoExt::ExtMap.PrepareStream(pItem, pStm);

return 0;
return 0;
}

DEFINE_HOOK(70C249, TechnoClass_Load_Suffix, 5)
{
TechnoExt::ExtMap.LoadStatic();
TechnoExt::ExtMap.LoadStatic();

return 0;
return 0;
}

DEFINE_HOOK(70C264, TechnoClass_Save_Suffix, 5)
{
TechnoExt::ExtMap.SaveStatic();
TechnoExt::ExtMap.SaveStatic();

return 0;
return 0;
}
Loading