Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4bfddc3
Weapon Select and Negative Damage
Thrifinesma Apr 11, 2021
25056ae
Small change
Thrifinesma Apr 19, 2021
7f094fd
Weapon Select and Negative Damage
Thrifinesma Apr 19, 2021
6676609
Change weapon select
Thrifinesma Apr 19, 2021
a93b11b
Fix DeploysInto issue
Thrifinesma Apr 19, 2021
ec37117
Fix UndeploysInto issue
Thrifinesma Apr 20, 2021
8a040b9
Update docs
Thrifinesma Apr 20, 2021
328681d
Fix Versus (?)
Thrifinesma Apr 20, 2021
0503432
Remove unused include
Thrifinesma Apr 20, 2021
3cd2f7a
Shield Targeting now recognizes warhead, rather than damage
Thrifinesma Apr 21, 2021
3879528
Fix targeting issue
Thrifinesma Apr 21, 2021
4f4cc33
Null check for anim
Thrifinesma Apr 21, 2021
68041e7
Shield health bar will be more like normal health bar
Thrifinesma Apr 21, 2021
6f73be2
CONVEEEEEEEEEEEERT!
Thrifinesma Apr 22, 2021
69ada7a
Small fix for UndeploysInto
Thrifinesma Apr 22, 2021
f323e91
Don't update shield when without, rather than delete
Thrifinesma Apr 22, 2021
656176c
Small change for convert
Thrifinesma Apr 22, 2021
e075130
Stupid issue
Thrifinesma Apr 22, 2021
99d5ea9
FIX stupid again
Thrifinesma Apr 22, 2021
8b67902
Refactor code and add info to docs
Metadorius Apr 22, 2021
0d44a59
More support for Shield Armor
Thrifinesma Apr 22, 2021
48d21c5
Additional check and Docs fix
Thrifinesma Apr 22, 2021
415c132
Damage fix
Thrifinesma Apr 22, 2021
c9a69b6
Move the check out
Thrifinesma Apr 22, 2021
86fcef2
Pass the check
Thrifinesma Apr 22, 2021
b6922af
fix more
Thrifinesma Apr 22, 2021
89c1ab4
Damn it
Thrifinesma Apr 22, 2021
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
7 changes: 6 additions & 1 deletion docs/New-or-Enhanced-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,13 @@ Promote.IncludeSpawns=no ; boolean
*Buildings, Infantries and Vehicles with Shield in [Fantasy ADVENTURE](https://www.moddb.com/mods/fantasy-adventure)*

- Now you can have a shield for any TechnoType if `Shield.Strength` is set greater than 0. It serves as a second health pool with independent `Armor` and `Strength` values.
- Negative damage will recover shield, unless shield has been broken. If shield isn't full, all negative damage will be absorbed by shield.
- When the TechnoType with a unbroken shield, `Shield.Armor` will replace `Armor` for game calculation.
- When executing `DeploysInto` or `UndeploysInto`, if both of the TechnoClasses have shields, the transformed unit/building would keep relative shield health (in percents), same as with `Strength`. If one of the TechnoTypes doesn't have shields, it's shield's state on conversion will be preserved until converted back.
- This also works with Ares' `Convert.*`.
- `Shield.AbsorbOverDamage`controls whether or not the shield absorbs damage dealt beyond shield's current strength when the shield breaks.
- `Shield.SelfHealing` and `Shield.Respawn` respect the following settings: 0.0 disables the feature, 1%-100% recovers/respawns the shield strength in percentage, other number recovers/respawns the shield strength directly. Specially, `Shield.SelfHealing` with a negative number deducts the shield strength.
- If you want shield recovers/respawns 1 HP per time, currently you need to set tag value to any number between 1 and 2, like `1.1`.
- `Shield.SelfHealing.Rate` and `Shield.Respawn.Rate` respect the following settings: 0.0 instantly recovers the shield, other values determine the frequency of shield recovers/respawns in ingame minutes.
- `Shield.IdleAnim`, if set, will be played while the shield is intact. This animation is automatically set to loop indefinitely.
- `Shield.BreakAnim`, if set, will be played when the shield has been broken.
Expand All @@ -88,7 +93,7 @@ Promote.IncludeSpawns=no ; boolean
- `Pips.Shield` can be used to specify which pip frame should be used as shield strength. If only 1 digit set, then it will always display it, or if 3 digits set, it will respect `ConditionYellow` and `ConditionRed`. `Pips.Shield.Building` is used for BuildingTypes.
- `pipbrd.shp` will use its 4th frame to display an infantry's shield strength and the 3th frame for other units if `pipbrd.shp` has extra 2 frames. And `Shield.BracketDelta` can be used as additonal `PixelSelectionBracketDelta` for shield strength.
- Warheads have new options that interact with shields.
- `PenetratesShield` allows the warhead ignore the shield and always deal full damage to the TechnoType itself. It also allows targeting the TechnoType even if weapons using the warhead cannot target or damage the shield.
- `PenetratesShield` allows the warhead ignore the shield and always deal full damage to the TechnoType itself. It also allows targeting the TechnoType as if shield isn't existed.
- `BreaksShield` allows the warhead to always break shields of TechnoTypes, regardless of the amount of strength the shield has remaining or the damage dealt, assuming it affects the shield's armor type. Residual damage, if there is any, still respects `Shield.AbsorbOverDamage`.

In `rulesmd.ini`:
Expand Down
80 changes: 43 additions & 37 deletions src/Ext/Techno/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,65 @@
#include <TechnoClass.h>

#include <Helpers/Macro.h>
#include "../_Container.hpp"
#include "../../Utilities/TemplateDef.h"
#include <Ext/_Container.hpp>
#include <Utilities/TemplateDef.h>

#include "../../Misc/Shield.h"
#include <Misc/Shield.h>

class BulletClass;

class TechnoExt
{
public:
using base_type = TechnoClass;
using base_type = TechnoClass;

class ExtData final : public Extension<TechnoClass>
{
public:
Valueable<BulletClass*> InterceptedBullet;
std::unique_ptr<ShieldTechnoClass> ShieldData;
class ExtData final : public Extension<TechnoClass>
{
public:
Valueable<BulletClass*> InterceptedBullet;
std::unique_ptr<ShieldTechnoClass> ShieldData;

ExtData(TechnoClass* OwnerObject) : Extension<TechnoClass>(OwnerObject),
InterceptedBullet(nullptr),
ShieldData()
{ }
ExtData(TechnoClass* OwnerObject) : Extension<TechnoClass>(OwnerObject)
,InterceptedBullet(nullptr)
,ShieldData()
{ }

virtual ~ExtData() = default;
virtual void InvalidatePointer(void* ptr, bool bRemoved) override {}
virtual void LoadFromStream(PhobosStreamReader& Stm) override;
virtual void SaveToStream(PhobosStreamWriter& Stm) override;
private:
template <typename T>
void Serialize(T& Stm);
};
virtual ~ExtData() = default;

class ExtContainer final : public Container<TechnoExt> {
public:
ExtContainer();
~ExtContainer();
virtual void InvalidatePointer(void* ptr, bool bRemoved) override
{
this->ShieldData->InvalidatePointer(ptr);
}

virtual void InvalidatePointer(void* ptr, bool bRemoved) override;
};
virtual void LoadFromStream(PhobosStreamReader& Stm) override;
virtual void SaveToStream(PhobosStreamWriter& Stm) override;

static ExtContainer ExtMap;
private:
template <typename T>
void Serialize(T& Stm);
};

static bool LoadGlobals(PhobosStreamReader& Stm);
static bool SaveGlobals(PhobosStreamWriter& Stm);
class ExtContainer final : public Container<TechnoExt>
{
public:
ExtContainer();
~ExtContainer();

static bool IsHarvesting(TechnoClass* pThis);
static bool HasAvailableDock(TechnoClass* pThis);
virtual void InvalidatePointer(void* ptr, bool bRemoved) override;
};

static void TransferMindControlOnDeploy(TechnoClass* pTechnoFrom, TechnoClass* pTechnoTo);
static ExtContainer ExtMap;

static void ApplyMindControlRangeLimit(TechnoClass* pThis);
static void ApplyInterceptor(TechnoClass* pThis);
static void ApplyPowered_KillSpawns(TechnoClass* pThis);
static void ApplySpawn_LimitRange(TechnoClass* pThis);
static bool LoadGlobals(PhobosStreamReader& Stm);
static bool SaveGlobals(PhobosStreamWriter& Stm);

static bool IsHarvesting(TechnoClass* pThis);
static bool HasAvailableDock(TechnoClass* pThis);

static void TransferMindControlOnDeploy(TechnoClass* pTechnoFrom, TechnoClass* pTechnoTo);

static void ApplyMindControlRangeLimit(TechnoClass* pThis);
static void ApplyInterceptor(TechnoClass* pThis);
static void ApplyPowered_KillSpawns(TechnoClass* pThis);
static void ApplySpawn_LimitRange(TechnoClass* pThis);
};
151 changes: 127 additions & 24 deletions src/Ext/Techno/Hooks.Shield.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#include "Body.h"
#include <SpecificStructures.h>

#include <Utilities/GeneralUtils.h>
#include "../TechnoType/Body.h"
#include "../WarheadType/Body.h"

// #issue 88 : shield logic
DEFINE_HOOK(701900, TechnoClass_ReceiveDamage_Shield, 6)
Expand All @@ -13,46 +15,125 @@ DEFINE_HOOK(701900, TechnoClass_ReceiveDamage_Shield, 6)
//GET_STACK(HouseClass*, pSourceHouse, -0x1C);
auto pExt = TechnoExt::ExtMap.Find(pThis);

if (auto pShieldData = pExt->ShieldData.get()) {
auto nDamageLeft = pShieldData->ReceiveDamage(args);
if (auto pShieldData = pExt->ShieldData.get())
{
if (!pShieldData->Available())
return 0;

if (nDamageLeft >= 0) {
auto nDamageLeft = pShieldData->ReceiveDamage(args);
if (nDamageLeft >= 0)
*args->Damage = nDamageLeft;
}
}

return 0;
}

DEFINE_HOOK(6FC339, TechnoClass_CanFire_Shield, 6)
DEFINE_HOOK_AGAIN(70CF39, TechnoClass_ReplaceArmorWithShields, 6) //TechnoClass_EvalThreatRating_Shield
DEFINE_HOOK_AGAIN(6F7D31, TechnoClass_ReplaceArmorWithShields, 6) //TechnoClass_CanAutoTargetObject_Shield
DEFINE_HOOK_AGAIN(6FCB64, TechnoClass_ReplaceArmorWithShields, 6) //TechnoClass_CanFire_Shield
DEFINE_HOOK(708AEB, TechnoClass_ReplaceArmorWithShields, 6) //TechnoClass_ShouldRetaliate_Shield
{
GET_STACK(TechnoClass*, pTarget, STACK_OFFS(0x20, -0x4));
GET(TechnoClass*, pThis, ESI);
GET(WeaponTypeClass*, pWeapon, EDI);
WeaponTypeClass* pWeapon = nullptr;
if (R->Origin() == 0x708AEB)
pWeapon = R->ESI<WeaponTypeClass*>();
else if (R->Origin() == 0x6F7D31)
pWeapon = R->EBP<WeaponTypeClass*>();
else
pWeapon = R->EBX<WeaponTypeClass*>();

if (auto pWHExt = WarheadTypeExt::ExtMap.Find(pWeapon->Warhead))
if (pWHExt->PenetratesShield)
return 0;

TechnoClass* pTarget = nullptr;
if (R->Origin() == 0x6F7D31 || R->Origin() == 0x70CF39)
pTarget = R->ESI<TechnoClass*>();
else
pTarget = R->EBP<TechnoClass*>();

if (auto pExt = TechnoExt::ExtMap.Find(pTarget))
{
if (auto pShieldData = pExt->ShieldData.get())
{
if (pShieldData->Available() && pShieldData->GetShieldHP())
{
R->EAX(TechnoTypeExt::ExtMap.Find(pTarget->GetTechnoType())->Shield_Armor);
return R->Origin() + 6;
}
}
}

if (auto pExt = TechnoExt::ExtMap.Find(pTarget)) {
if (auto pShieldData = pExt->ShieldData.get()) {
if (!pShieldData->CanBeTargeted(pWeapon, pThis)) {
return 0x6FCB7E;
return 0;
}

//Abandoned because of Ares!!!! - Uranusian
/*
DEFINE_HOOK_AGAIN(6F3725, TechnoClass_WhatWeaponShouldIUse_Shield, 6)
DEFINE_HOOK(6F36F2, TechnoClass_WhatWeaponShouldIUse_Shield, 6)
{
GET(TechnoClass*, pTarget, EBP);
if (auto pExt = TechnoExt::ExtMap.Find(pTarget))
{
if (auto pShieldData = pExt->ShieldData.get())
{
if (pShieldData->GetShieldHP())
{
auto pTypeExt = TechnoTypeExt::ExtMap.Find(pTarget->GetTechnoType());

if (R->Origin() == 0x6F36F2)
R->ECX(pTypeExt->Shield_Armor);
else
R->EAX(pTypeExt->Shield_Armor);

return R->Origin() + 6;
}
}
}
return 0;
}
*/

DEFINE_HOOK(6F36DB, TechnoClass_WhatWeaponShouldIUse_Shield, 8)
{
GET(TechnoClass*, pThis, ESI);
GET(TechnoClass*, pTarget, EBP);

if (!pTarget)
return 0x6F37AD; //Target invalid, skip. (test ebp,ebp jz loc_6F37AD)

if (auto pExt = TechnoExt::ExtMap.Find(pTarget))
{
if (auto pShieldData = pExt->ShieldData.get())
{
if (pShieldData->Available() && pShieldData->GetShieldHP())
{
if (pThis->GetWeapon(1))
{
if (!pShieldData->CanBeTargeted(pThis->GetWeapon(0)->WeaponType/*, pThis*/))
return 0x6F3745; //Primary cannot attack, always use Secondary

return 0x6F3754; //Further check in vanilla function
}

return 0x6F37AD; //Don't have Secondary, always use Primary
}
}
}
return 0x6F36E3; //Target doesn't have a shield, back
}

DEFINE_HOOK(6F9E50, TechnoClass_AI_Shield, 5)
{
GET(TechnoClass*, pThis, ECX);
auto pExt = TechnoExt::ExtMap.Find(pThis);
auto pTypeData = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType());

if (pTypeData->Shield_Strength) {
if (!pExt->ShieldData) {
pExt->ShieldData = std::make_unique<ShieldTechnoClass>(pThis);
}
if (pTypeData->Shield_Strength && !pExt->ShieldData)
pExt->ShieldData = std::make_unique<ShieldTechnoClass>(pThis);

if (pExt->ShieldData)
pExt->ShieldData->AI();
}

return 0;
}

Expand All @@ -61,36 +142,58 @@ DEFINE_HOOK(6F6AC4, TechnoClass_Remove_Shield, 5)
GET(TechnoClass*, pThis, ECX);
auto pExt = TechnoExt::ExtMap.Find(pThis);

if (pExt->ShieldData) {
if (pExt->ShieldData &&
!(pThis->WhatAmI() == AbstractType::Building && pThis->GetTechnoType()->UndeploysInto && pThis->CurrentMission == Mission::Selling))
{
pExt->ShieldData = nullptr;
}

return 0;
}

DEFINE_HOOK(6F65D1, TechnoClass_DrawHealthBar_DrawBuildingShieldBar, 6)
DEFINE_HOOK_AGAIN(44A03C, DeploysInto_UndeploysInto_SyncShieldStatus, 6) //BuildingClass_Mi_Selling_SyncShieldStatus
DEFINE_HOOK(739956, DeploysInto_UndeploysInto_SyncShieldStatus, 6) //UnitClass_Deploy_SyncShieldStatus
{
GET(TechnoClass*, pThis, EBP);
GET(TechnoClass*, pInto, EBX);
auto pThisExt = TechnoExt::ExtMap.Find(pThis);
auto pIntoTypeExt = TechnoTypeExt::ExtMap.Find(pInto->GetTechnoType());

if (pThisExt->ShieldData && pIntoTypeExt->Shield_Strength)
{
ShieldTechnoClass::SyncShieldToAnother(pThis, pInto);
}

if (pThis->WhatAmI() == AbstractType::Building && pThisExt->ShieldData)
pThisExt->ShieldData = nullptr;

return 0;
}


DEFINE_HOOK(6F65D1, TechnoClass_DrawHealthBar_DrawBuildingShieldBar, 6)
{
GET(TechnoClass*, pThis, ESI);
GET(int, iLength, EBX);
GET_STACK(Point2D*, pLocation, STACK_OFFS(0x4C, -0x4));
GET_STACK(RectangleStruct*, pBound, STACK_OFFS(0x4C, -0x8));
auto pExt = TechnoExt::ExtMap.Find(pThis);
if (pExt->ShieldData) {

if (pExt->ShieldData && pExt->ShieldData->Available())
pExt->ShieldData->DrawShieldBar(iLength, pLocation, pBound);
}

return 0;
}

DEFINE_HOOK(6F683C,TechnoClass_DrawHealthBar_DrawOtherShieldBar,7)
DEFINE_HOOK(6F683C, TechnoClass_DrawHealthBar_DrawOtherShieldBar, 7)
{
GET(TechnoClass*, pThis, ESI);
GET_STACK(Point2D*, pLocation, STACK_OFFS(0x4C, -0x4));
GET_STACK(RectangleStruct*, pBound, STACK_OFFS(0x4C, -0x8));
auto pExt = TechnoExt::ExtMap.Find(pThis);

if (pExt->ShieldData) {
if (pExt->ShieldData && pExt->ShieldData->Available())
{
int iLength = pThis->WhatAmI() == AbstractType::Infantry ? 8 : 17;
pExt->ShieldData->DrawShieldBar(iLength, pLocation, pBound);
}
Expand Down
Loading