Skip to content

[New feature]Tiberium eater logic #1619

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 20 commits into
base: develop
Choose a base branch
from
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 @@ -383,6 +383,7 @@ This page lists all the individual contributions to the project by their author.
- Separate the AirstrikeClass pointer between the attacker/aircraft and the target to avoid erroneous overwriting issues
- Fix the bug that buildings will always be tinted as airstrike owner
- Fix the bug that 'AllowAirstrike=no' cannot completely prevent air strikes from being launched against it
- Tiberium eater logic
- **Apollo** - Translucent SHP drawing patches
- **ststl**:
- Customizable `ShowTimer` priority of superweapons
Expand Down
2 changes: 2 additions & 0 deletions Phobos.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<ClCompile Include="src\Ext\Unit\Hooks.Sinking.cpp" />
<ClCompile Include="src\Misc\Hooks.AlphaImage.cpp" />
<ClCompile Include="src\New\Entity\AttachEffectClass.cpp" />
<ClCompile Include="src\New\Type\Affiliated\TiberiumEaterTypeClass.cpp" />
<ClCompile Include="src\New\Type\AttachEffectTypeClass.cpp" />
<ClCompile Include="src\Commands\Commands.cpp" />
<ClCompile Include="src\Commands\DamageDisplay.cpp" />
Expand Down Expand Up @@ -193,6 +194,7 @@
<ItemGroup>
<ClInclude Include="src\Blowfish\blowfish.h" />
<ClInclude Include="src\New\Entity\AttachEffectClass.h" />
<ClInclude Include="src\New\Type\Affiliated\TiberiumEaterTypeClass.h" />
<ClInclude Include="src\New\Type\AttachEffectTypeClass.h" />
<ClInclude Include="src\Commands\FrameByFrame.h" />
<ClInclude Include="src\Commands\FrameStep.h" />
Expand Down
27 changes: 27 additions & 0 deletions docs/New-or-Enhanced-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -1768,6 +1768,33 @@ AmphibiousEnter= ; boolean
AmphibiousUnload= ; boolean
```

### Tiberium eater

- Units can convert the ore underneath them into cash in real time, like GDI's EPIC unit MARV in Command & Conquer 3 Kane's Wrath, when `TiberiumEater.TransDelay` is 0 or larger.
- `TiberiumEater.TransDelay` specifies the interval in game frames between two mining "processes", 0 means eat in every frame.
- `TiberiumEater.AmountPerCell` controls how many "bails" of ore can be mined at each cell at once, <= 0 means no limit.
- By default, ore mined this way is worth the same as if it was harvested and refined the normal way. This can be adjusted with `TiberiumEater.CashMultiplier`.
- `TiberiumEater.Display=true` will create a flying text displaying the total cash amount received each mining process. `TiberiumEater.Display.Houses` controlls who can see this text.
- An animation will be played at each interval at each mined cell. If `TiberiumEater.Anims` contains 8 entries, then an entry will be picked according to unit facing. Otherwise, an entry will be chosen at random.
- If `TiberiumEater.AnimMove=true`, the animations will move with the unit.

In `rulesmd.ini`:
```ini
[SOMETECHNO] ; InfantryType, VehicleType or AircraftType
TiberiumEater.TransDelay=-1 ; integer
TiberiumEater.CashMultiplier=1.0 ; floating point value
TiberiumEater.AmountPerCell=0 ; integer
TiberiumEater.CellN= ; x, y , use cell as unit, multiple values mean that they are effective in multiple cells at the same time. N is zero-based.
TiberiumEater.Display=true ; boolean
TiberiumEater.Display.Houses=all ; AffectedHouse enumeration
TiberiumEater.Anims= ; List of AnimationTypes
TiberiumEater.Anims.Tiberium0= ; List of AnimationTypes
TiberiumEater.Anims.Tiberium1= ; List of AnimationTypes
TiberiumEater.Anims.Tiberium2= ; List of AnimationTypes
TiberiumEater.Anims.Tiberium3= ; List of AnimationTypes
TiberiumEater.AnimMove=true ; boolean
```

## Terrain

### Destroy animation & sound
Expand Down
1 change: 1 addition & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ New:
- Customize airstrike targets (by NetsuNegi)
- Aggressive attack move mission (by CrimRecya)
- Amphibious access vehicle (by CrimRecya)
- Tiberium eater logic (by NetsuNegi)

Vanilla fixes:
- Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya)
Expand Down
92 changes: 92 additions & 0 deletions src/Ext/Techno/Body.Update.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <SpawnManagerClass.h>
#include <ParticleSystemClass.h>
#include <Conversions.h>

#include <Ext/Anim/Body.h>
#include <Ext/Bullet/Body.h>
Expand Down Expand Up @@ -338,6 +339,97 @@ void TechnoExt::ExtData::EatPassengers()
}
}

void TechnoExt::ExtData::UpdateTiberiumEater()
{
const auto pEaterType = this->TypeExtData->TiberiumEaterType.get();

if (!pEaterType)
return;

const int transDelay = pEaterType->TransDelay;

if (transDelay && this->TiberiumEater_Timer.InProgress())
return;

const auto pThis = this->OwnerObject();
const auto pOwner = pThis->Owner;
bool active = false;
const bool displayCash = pEaterType->Display && pThis->IsClearlyVisibleTo(HouseClass::CurrentPlayer);
int facing = pThis->PrimaryFacing.Current().GetFacing<8>();

if (facing >= 7)
facing = 0;
else
facing++;

const int cellCount = static_cast<int>(pEaterType->Cells.size());

for (int idx = 0; idx < cellCount; idx++)
{
const auto& cellOffset = pEaterType->Cells[idx];
const auto pos = TechnoExt::GetFLHAbsoluteCoords(pThis, CoordStruct { cellOffset.X, cellOffset.Y, 0 }, false);
const auto pCell = MapClass::Instance.TryGetCellAt(pos);

if (!pCell)
continue;

if (const int contained = pCell->GetContainedTiberiumValue())
{
const int tiberiumIdx = pCell->GetContainedTiberiumIndex();
const int tiberiumValue = TiberiumClass::Array[tiberiumIdx]->Value;
const int tiberiumAmount = static_cast<int>(static_cast<double>(contained) / tiberiumValue);
const int amount = pEaterType->AmountPerCell > 0 ? std::min(pEaterType->AmountPerCell.Get(), tiberiumAmount) : tiberiumAmount;
pCell->ReduceTiberium(amount);
const float multiplier = pEaterType->CashMultiplier * (1.0f + pOwner->NumOrePurifiers * RulesClass::Instance->PurifierBonus);
const int value = static_cast<int>(std::round(amount * tiberiumValue * multiplier));
pOwner->TransactMoney(value);
active = true;

if (displayCash)
{
auto cellCoords = pCell->GetCoords();
cellCoords.Z = std::max(pThis->Location.Z, cellCoords.Z);
FlyingStrings::AddMoneyString(value, pOwner, pEaterType->DisplayToHouse, cellCoords, pEaterType->DisplayOffset);
}

const auto& anims = pEaterType->Anims_Tiberiums[tiberiumIdx].GetElements(pEaterType->Anims);
const int animCount = static_cast<int>(anims.size());

if (animCount == 0)
continue;

AnimTypeClass* pAnimType = nullptr;

switch (animCount)
{
case 1:
pAnimType = anims[0];
break;

case 8:
pAnimType = anims[facing];
break;

default:
pAnimType = anims[ScenarioClass::Instance->Random.RandomRanged(0, animCount - 1)];
break;
}

if (pAnimType)
{
const auto pAnim = GameCreate<AnimClass>(pAnimType, pos);
AnimExt::SetAnimOwnerHouseKind(pAnim, pThis->Owner, nullptr, false, true);

if (pEaterType->AnimMove)
pAnim->SetOwnerObject(pThis);
}
}
}

if (active && transDelay)
this->TiberiumEater_Timer.Start(pEaterType->TransDelay);
}

void TechnoExt::ExtData::UpdateShield()
{
auto const pTypeExt = this->TypeExtData;
Expand Down
1 change: 1 addition & 0 deletions src/Ext/Techno/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,7 @@ void TechnoExt::ExtData::Serialize(T& Stm)
.Process(this->IsBeingChronoSphered)
.Process(this->KeepTargetOnMove)
.Process(this->LastSensorsMapCoords)
.Process(this->TiberiumEater_Timer)
.Process(this->AirstrikeTargetingMe)
;
}
Expand Down
3 changes: 3 additions & 0 deletions src/Ext/Techno/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class TechnoExt
bool IsBeingChronoSphered; // Set to true on units currently being ChronoSphered, does not apply to Ares-ChronoSphere'd buildings or Chrono reinforcements.
bool KeepTargetOnMove;
CellStruct LastSensorsMapCoords;
CDTimerClass TiberiumEater_Timer;

AirstrikeClass* AirstrikeTargetingMe;

Expand Down Expand Up @@ -112,6 +113,7 @@ class TechnoExt
, IsBeingChronoSphered { false }
, KeepTargetOnMove { false }
, LastSensorsMapCoords { CellStruct::Empty }
, TiberiumEater_Timer {}
, AirstrikeTargetingMe { nullptr }
{ }

Expand All @@ -121,6 +123,7 @@ class TechnoExt
bool CheckDeathConditions(bool isInLimbo = false);
void DepletedAmmoActions();
void EatPassengers();
void UpdateTiberiumEater();
void UpdateShield();
void UpdateOnTunnelEnter();
void UpdateOnTunnelExit();
Expand Down
1 change: 1 addition & 0 deletions src/Ext/Techno/Hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ DEFINE_HOOK(0x4DA54E, FootClass_AI, 0x6)
pExt->UpdateTypeData_Foot();

pExt->UpdateWarpInDelay();
pExt->UpdateTiberiumEater();

return 0;
}
Expand Down
16 changes: 16 additions & 0 deletions src/Ext/TechnoType/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,20 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
this->PassengerDeletionType->LoadFromINI(pINI, pSection);
}

Nullable<int> transDelay;
transDelay.Read(exINI, pSection, "TiberiumEater.TransDelay");

if (transDelay.Get(-1) >= 0 && !this->TiberiumEaterType)
this->TiberiumEaterType = std::make_unique<TiberiumEaterTypeClass>();

if (this->TiberiumEaterType)
{
if (transDelay.isset() && transDelay.Get() < 0)
this->TiberiumEaterType.reset();
else
this->TiberiumEaterType->LoadFromINI(pINI, pSection);
}

Nullable<bool> isInterceptor;
isInterceptor.Read(exINI, pSection, "Interceptor");

Expand Down Expand Up @@ -923,6 +937,8 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm)
.Process(this->LandingDir)
.Process(this->DroppodType)

.Process(this->TiberiumEaterType)

.Process(this->Convert_HumanToComputer)
.Process(this->Convert_ComputerToHuman)

Expand Down
3 changes: 3 additions & 0 deletions src/Ext/TechnoType/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <New/Type/DigitalDisplayTypeClass.h>
#include <New/Type/SelectBoxTypeClass.h>
#include <New/Type/Affiliated/DroppodTypeClass.h>
#include <New/Type/Affiliated/TiberiumEaterTypeClass.h>

class Matrix3D;
class ParticleSystemTypeClass;
Expand Down Expand Up @@ -62,6 +63,7 @@ class TechnoTypeExt
Valueable<ShieldTypeClass*> ShieldType;
std::unique_ptr<PassengerDeletionTypeClass> PassengerDeletionType;
std::unique_ptr<DroppodTypeClass> DroppodType;
std::unique_ptr<TiberiumEaterTypeClass> TiberiumEaterType;

Nullable<float> HarvesterDumpAmount;

Expand Down Expand Up @@ -547,6 +549,7 @@ class TechnoTypeExt
, SpawnHeight {}
, LandingDir {}
, DroppodType {}
, TiberiumEaterType {}

, Convert_HumanToComputer { }
, Convert_ComputerToHuman { }
Expand Down
80 changes: 80 additions & 0 deletions src/New/Type/Affiliated/TiberiumEaterTypeClass.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#include "TiberiumEaterTypeClass.h"

#include <Utilities/SavegameDef.h>
#include <Utilities/TemplateDef.h>

void TiberiumEaterTypeClass::LoadFromINI(CCINIClass* pINI, const char* pSection)
{
INI_EX exINI(pINI);
char tempBuffer[32];

this->TransDelay.Read(exINI, pSection, "TiberiumEater.TransDelay");
this->CashMultiplier.Read(exINI, pSection, "TiberiumEater.CashMultiplier");
this->AmountPerCell.Read(exINI, pSection, "TiberiumEater.AmountPerCell");

for (size_t idx = 0; ; ++idx)
{
Nullable<Vector2D<int>> cell;
_snprintf_s(tempBuffer, sizeof(tempBuffer), "TiberiumEater.Cell%d", idx);
cell.Read(exINI, pSection, tempBuffer);

if (idx >= this->Cells.size())
{
if (!cell.isset())
break;

this->Cells.emplace_back(cell.Get().X * Unsorted::LeptonsPerCell, cell.Get().Y * Unsorted::LeptonsPerCell);
}
else
{
if (!cell.isset())
continue;

this->Cells[idx] = Vector2D<int> { cell.Get().X * Unsorted::LeptonsPerCell, cell.Get().Y * Unsorted::LeptonsPerCell };
}
}

this->Display.Read(exINI, pSection, "TiberiumEater.Display");
this->DisplayToHouse.Read(exINI, pSection, "TiberiumEater.DisplayToHouse");
this->DisplayOffset.Read(exINI, pSection, "TiberiumEater.DisplayOffset");
this->Anims.Read(exINI, pSection, "TiberiumEater.Anims");

for (size_t idx = 0; idx < 4; ++idx)
{
_snprintf_s(tempBuffer, sizeof(tempBuffer), "TiberiumEater.Anims.Tiberium%d", idx);
this->Anims_Tiberiums[idx].Read(exINI, pSection, tempBuffer);
}

this->AnimMove.Read(exINI, pSection, "TiberiumEater.AnimMove");
}

template <class T>
bool TiberiumEaterTypeClass::Serialize(T& stm)
{
return stm
.Process(this->TransDelay)
.Process(this->CashMultiplier)
.Process(this->AmountPerCell)
.Process(this->Cells)
.Process(this->Display)
.Process(this->DisplayToHouse)
.Process(this->DisplayOffset)
.Process(this->Anims)
.Process(this->Anims_Tiberiums)
.Process(this->AnimMove)
.Success();
}

#pragma region(save/load)

bool TiberiumEaterTypeClass::Load(PhobosStreamReader& stm, bool registerForChange)
{
return this->Serialize(stm);
}

bool TiberiumEaterTypeClass::Save(PhobosStreamWriter& stm) const
{
return const_cast<TiberiumEaterTypeClass*>(this)->Serialize(stm);
}

#pragma endregion
31 changes: 31 additions & 0 deletions src/New/Type/Affiliated/TiberiumEaterTypeClass.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#pragma once

#include <Utilities/Constructs.h>
#include <Utilities/Enum.h>
#include <Utilities/Template.h>

class TiberiumEaterTypeClass
{
public:
Valueable<int> TransDelay { -1 };
Valueable<float> CashMultiplier { 1.0 };
Valueable<int> AmountPerCell { 0 };
std::vector<Vector2D<int>> Cells { std::vector<Vector2D<int>>(1) };
Valueable<bool> Display { true };
Valueable<AffectedHouse> DisplayToHouse { AffectedHouse::All };
Valueable<Point2D> DisplayOffset { Point2D::Empty };
ValueableVector<AnimTypeClass*> Anims {};
NullableVector<AnimTypeClass*> Anims_Tiberiums[4] {};
Valueable<bool> AnimMove { true };

TiberiumEaterTypeClass() = default;

void LoadFromINI(CCINIClass* pINI, const char* pSection);
bool Load(PhobosStreamReader& stm, bool registerForChange);
bool Save(PhobosStreamWriter& stm) const;

private:

template <typename T>
bool Serialize(T& stm);
};