Skip to content

Commit 9af8d8d

Browse files
NetsuNegiDeathFishAtEaseCoronia
authored
[New feature]Tiberium eater logic (#1619)
- Re-impl from PR #1111 - 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` 大于等于0时,单位将可以实时将矿石转换为资金,就像《命令与征服3:凯恩之怒》中GDI史诗载具MARV那样。 - `TiberiumEater.TransDelay` specifies the interval in game frames between two mining "processes". - `TiberiumEater.TransDelay` 指定两次转换之间的间隔帧数。 - `TiberiumEater.CellN` set a list of cells that'll process tiberium eating, where `N` is 0-based and the values are offset related to the TechnoType's current cell. If not set, only the ores on the TechnoType's current cell will be eaten. - `TiberiumEater.CellN` 设置一系列将要被转换矿石的格子偏移量,N为从0开始的整数,实际坐标基于单位当前所处的格子。若不设定,则只有单位当前所处的格子中的矿石会被转换。 - `TiberiumEater.AmountPerCell` controls how many "bails" of ore can be mined at each cell at once. - `TiberiumEater.AmountPerCell` 控制每次在每格内转换多少数量的矿石。 - 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.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. - `TiberiumEater.Display=true` 将会显示转换后获得的金额。 `TiberiumEater.Display.Houses` 控制谁可以看见显示的金额。 - 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. - 转换时可以播放动画。如果 `TiberiumEater.Anims` 包含8个动画,那么将会根据单位的朝向进行选择;其他情况下,将会随机选择动画。 - `TiberiumEater.Anims.TiberiumN`, if set, will override `TiberiumEater.Anims` when mining corresponding tiberium type. - `TiberiumEater.Anims.TiberiumN` 若设置,则在处理对应矿石类型的时候将覆盖 `TiberiumEater.Anims` 的值. - If `TiberiumEater.AnimMove=true`, the animations will move with the unit. - 如果 `TiberiumEater.AnimMove=true` ,动画将会跟随单位移动。 In `rulesmd.ini`: ```ini [SOMETECHNO] ; InfantryType, UnitType or AircraftType TiberiumEater.TransDelay=-1 ; integer TiberiumEater.CashMultiplier=1.0 ; float 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 TiberiumEater.Display=true ; boolean TiberiumEater.Display.Houses=all ; AffectedHouse enumeration TiberiumEater.Anims= ; list of animations 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 ``` --------- Co-authored-by: 九千天华 <1065703286@qq.com> Co-authored-by: Coronia <2217891145@qq.com>
1 parent 5e6e4d4 commit 9af8d8d

12 files changed

+260
-0
lines changed

CREDITS.md

+1
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,7 @@ This page lists all the individual contributions to the project by their author.
387387
- Fix the bug that buildings will always be tinted as airstrike owner
388388
- Fix the bug that `AllowAirstrike=no` cannot completely prevent air strikes from being launched against it
389389
- Fix the bug that infantry ignored `Passengers` and `SizeLimit` when entering buildings
390+
- Tiberium eater logic
390391
- **Apollo** - Translucent SHP drawing patches
391392
- **ststl**:
392393
- Customizable `ShowTimer` priority of superweapons

Phobos.vcxproj

+2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
<ClCompile Include="src\Ext\Unit\Hooks.Sinking.cpp" />
2929
<ClCompile Include="src\Misc\Hooks.AlphaImage.cpp" />
3030
<ClCompile Include="src\New\Entity\AttachEffectClass.cpp" />
31+
<ClCompile Include="src\New\Type\Affiliated\TiberiumEaterTypeClass.cpp" />
3132
<ClCompile Include="src\New\Type\AttachEffectTypeClass.cpp" />
3233
<ClCompile Include="src\Commands\Commands.cpp" />
3334
<ClCompile Include="src\Commands\DamageDisplay.cpp" />
@@ -197,6 +198,7 @@
197198
<ClInclude Include="src\Blowfish\blowfish.h" />
198199
<ClInclude Include="src\Ext\Cell\Body.h" />
199200
<ClInclude Include="src\New\Entity\AttachEffectClass.h" />
201+
<ClInclude Include="src\New\Type\Affiliated\TiberiumEaterTypeClass.h" />
200202
<ClInclude Include="src\New\Type\AttachEffectTypeClass.h" />
201203
<ClInclude Include="src\Commands\FrameByFrame.h" />
202204
<ClInclude Include="src\Commands\FrameStep.h" />

docs/New-or-Enhanced-Logics.md

+29
Original file line numberDiff line numberDiff line change
@@ -1724,6 +1724,35 @@ IsVoiceCreatedGlobal=false ; boolean
17241724
VoiceCreated= ; Sound entry
17251725
```
17261726

1727+
### Tiberium eater
1728+
1729+
- TechnoTypes can convert the ores underneath them into credits in real time, like GDI's MARV in Command & Conquer 3 Kane's Wrath.
1730+
- `TiberiumEater.TransDelay` specifies the interval frames between two eating processes, 0 means eat in every frame. When it's below 0, the logic will be turned off for this TechnoType.
1731+
- `TiberiumEater.CellN` set a list of cells that'll process tiberium eating, where `N` is 0-based and the values are offset related to the TechnoType's current cell. If not set, only the ores on the TechnoType's current cell will be eaten.
1732+
- `TiberiumEater.AmountPerCell` controls the amount of ores that can be eaten at each cell at once. No limit when it's below 0.
1733+
- By default, ores mined in this way worth the same as regular harvesting. This can be adjusted by `TiberiumEater.CashMultiplier`.
1734+
- `TiberiumEater.Display`, if set to true, will create a flying text to display the total credits received in each eating process. `TiberiumEater.Display.Houses` determines which houses can see the credits display.
1735+
- An animation will be played at each mined cell in an eating process. If `TiberiumEater.Anims` contains 8 entries, entry from position matching the TechnoType's current facing will be chosen. Otherwise, an entry will be chosen randomly.
1736+
- `TiberiumEater.Anims.TiberiumN`, if set, will override `TiberiumEater.Anims` when eating corresponding tiberium type.
1737+
- If `TiberiumEater.AnimMove` set to true, the animations will move with the TechnoType.
1738+
1739+
In `rulesmd.ini`:
1740+
```ini
1741+
[SOMETECHNO] ; InfantryType, VehicleType or AircraftType
1742+
TiberiumEater.TransDelay=-1 ; integer
1743+
TiberiumEater.CellN= ; X,Y - cell offset
1744+
TiberiumEater.CashMultiplier=1.0 ; floating point value
1745+
TiberiumEater.AmountPerCell=0 ; integer
1746+
TiberiumEater.Display=true ; boolean
1747+
TiberiumEater.Display.Houses=all ; AffectedHouse enumeration
1748+
TiberiumEater.Anims= ; List of AnimationTypes
1749+
TiberiumEater.Anims.Tiberium0= ; List of AnimationTypes
1750+
TiberiumEater.Anims.Tiberium1= ; List of AnimationTypes
1751+
TiberiumEater.Anims.Tiberium2= ; List of AnimationTypes
1752+
TiberiumEater.Anims.Tiberium3= ; List of AnimationTypes
1753+
TiberiumEater.AnimMove=true ; boolean
1754+
```
1755+
17271756
### Weapons fired on warping in / out
17281757

17291758
- It is now possible to add weapons that are fired on a teleporting TechnoType when it warps in or out. They are at the same time as the appropriate animations (`WarpIn` / `WarpOut`) are displayed.

docs/Whats-New.md

+1
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ New:
374374
- [Make harvesters do addtional scan after unload](Fixed-or-Improved-Logics.md#make-harvesters-do-addtional-scan-after-unload) (by TaranDahl)
375375
- [Passenger-based insignias](Fixed-or-Improved-Logics.md#customizable-veterancy-insignias) (by Ollerus)
376376
- [Use `InsigniaType` to set the properties of insignia in a batch](Miscellanous.md#insignia-type) (by Ollerus)
377+
- [Tiberium eater logic](New-or-Enhanced-Logics.md#tiberium-eater) (by NetsuNegi)
377378
378379
Vanilla fixes:
379380
- Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya)

src/Ext/Techno/Body.Update.cpp

+92
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include <SpawnManagerClass.h>
55
#include <ParticleSystemClass.h>
6+
#include <Conversions.h>
67

78
#include <Ext/Anim/Body.h>
89
#include <Ext/Bullet/Body.h>
@@ -338,6 +339,97 @@ void TechnoExt::ExtData::EatPassengers()
338339
}
339340
}
340341

342+
void TechnoExt::ExtData::UpdateTiberiumEater()
343+
{
344+
const auto pEaterType = this->TypeExtData->TiberiumEaterType.get();
345+
346+
if (!pEaterType)
347+
return;
348+
349+
const int transDelay = pEaterType->TransDelay;
350+
351+
if (transDelay && this->TiberiumEater_Timer.InProgress())
352+
return;
353+
354+
const auto pThis = this->OwnerObject();
355+
const auto pOwner = pThis->Owner;
356+
bool active = false;
357+
const bool displayCash = pEaterType->Display && pThis->IsClearlyVisibleTo(HouseClass::CurrentPlayer);
358+
int facing = pThis->PrimaryFacing.Current().GetFacing<8>();
359+
360+
if (facing >= 7)
361+
facing = 0;
362+
else
363+
facing++;
364+
365+
const int cellCount = static_cast<int>(pEaterType->Cells.size());
366+
367+
for (int idx = 0; idx < cellCount; idx++)
368+
{
369+
const auto& cellOffset = pEaterType->Cells[idx];
370+
const auto pos = TechnoExt::GetFLHAbsoluteCoords(pThis, CoordStruct { cellOffset.X, cellOffset.Y, 0 }, false);
371+
const auto pCell = MapClass::Instance.TryGetCellAt(pos);
372+
373+
if (!pCell)
374+
continue;
375+
376+
if (const int contained = pCell->GetContainedTiberiumValue())
377+
{
378+
const int tiberiumIdx = pCell->GetContainedTiberiumIndex();
379+
const int tiberiumValue = TiberiumClass::Array[tiberiumIdx]->Value;
380+
const int tiberiumAmount = static_cast<int>(static_cast<double>(contained) / tiberiumValue);
381+
const int amount = pEaterType->AmountPerCell > 0 ? std::min(pEaterType->AmountPerCell.Get(), tiberiumAmount) : tiberiumAmount;
382+
pCell->ReduceTiberium(amount);
383+
const float multiplier = pEaterType->CashMultiplier * (1.0f + pOwner->NumOrePurifiers * RulesClass::Instance->PurifierBonus);
384+
const int value = static_cast<int>(std::round(amount * tiberiumValue * multiplier));
385+
pOwner->TransactMoney(value);
386+
active = true;
387+
388+
if (displayCash)
389+
{
390+
auto cellCoords = pCell->GetCoords();
391+
cellCoords.Z = std::max(pThis->Location.Z, cellCoords.Z);
392+
FlyingStrings::AddMoneyString(value, pOwner, pEaterType->DisplayToHouse, cellCoords, pEaterType->DisplayOffset);
393+
}
394+
395+
const auto& anims = pEaterType->Anims_Tiberiums[tiberiumIdx].GetElements(pEaterType->Anims);
396+
const int animCount = static_cast<int>(anims.size());
397+
398+
if (animCount == 0)
399+
continue;
400+
401+
AnimTypeClass* pAnimType = nullptr;
402+
403+
switch (animCount)
404+
{
405+
case 1:
406+
pAnimType = anims[0];
407+
break;
408+
409+
case 8:
410+
pAnimType = anims[facing];
411+
break;
412+
413+
default:
414+
pAnimType = anims[ScenarioClass::Instance->Random.RandomRanged(0, animCount - 1)];
415+
break;
416+
}
417+
418+
if (pAnimType)
419+
{
420+
const auto pAnim = GameCreate<AnimClass>(pAnimType, pos);
421+
AnimExt::SetAnimOwnerHouseKind(pAnim, pThis->Owner, nullptr, false, true);
422+
423+
if (pEaterType->AnimMove)
424+
pAnim->SetOwnerObject(pThis);
425+
}
426+
}
427+
}
428+
429+
if (active && transDelay)
430+
this->TiberiumEater_Timer.Start(pEaterType->TransDelay);
431+
}
432+
341433
void TechnoExt::ExtData::UpdateShield()
342434
{
343435
auto const pTypeExt = this->TypeExtData;

src/Ext/Techno/Body.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,7 @@ void TechnoExt::ExtData::Serialize(T& Stm)
589589
.Process(this->IsBeingChronoSphered)
590590
.Process(this->KeepTargetOnMove)
591591
.Process(this->LastSensorsMapCoords)
592+
.Process(this->TiberiumEater_Timer)
592593
.Process(this->AirstrikeTargetingMe)
593594
;
594595
}

src/Ext/Techno/Body.h

+3
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ class TechnoExt
6767
bool IsBeingChronoSphered; // Set to true on units currently being ChronoSphered, does not apply to Ares-ChronoSphere'd buildings or Chrono reinforcements.
6868
bool KeepTargetOnMove;
6969
CellStruct LastSensorsMapCoords;
70+
CDTimerClass TiberiumEater_Timer;
7071

7172
AirstrikeClass* AirstrikeTargetingMe;
7273

@@ -112,6 +113,7 @@ class TechnoExt
112113
, IsBeingChronoSphered { false }
113114
, KeepTargetOnMove { false }
114115
, LastSensorsMapCoords { CellStruct::Empty }
116+
, TiberiumEater_Timer {}
115117
, AirstrikeTargetingMe { nullptr }
116118
{ }
117119

@@ -121,6 +123,7 @@ class TechnoExt
121123
bool CheckDeathConditions(bool isInLimbo = false);
122124
void DepletedAmmoActions();
123125
void EatPassengers();
126+
void UpdateTiberiumEater();
124127
void UpdateShield();
125128
void UpdateOnTunnelEnter();
126129
void UpdateOnTunnelExit();

src/Ext/Techno/Hooks.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ DEFINE_HOOK(0x4DA54E, FootClass_AI, 0x6)
3737
pExt->UpdateTypeData_Foot();
3838

3939
pExt->UpdateWarpInDelay();
40+
pExt->UpdateTiberiumEater();
4041

4142
return 0;
4243
}

src/Ext/TechnoType/Body.cpp

+16
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,20 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
753753
this->PassengerDeletionType->LoadFromINI(pINI, pSection);
754754
}
755755

756+
Nullable<int> transDelay;
757+
transDelay.Read(exINI, pSection, "TiberiumEater.TransDelay");
758+
759+
if (transDelay.Get(-1) >= 0 && !this->TiberiumEaterType)
760+
this->TiberiumEaterType = std::make_unique<TiberiumEaterTypeClass>();
761+
762+
if (this->TiberiumEaterType)
763+
{
764+
if (transDelay.isset() && transDelay.Get() < 0)
765+
this->TiberiumEaterType.reset();
766+
else
767+
this->TiberiumEaterType->LoadFromINI(pINI, pSection);
768+
}
769+
756770
Nullable<bool> isInterceptor;
757771
isInterceptor.Read(exINI, pSection, "Interceptor");
758772

@@ -996,6 +1010,8 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm)
9961010
.Process(this->LandingDir)
9971011
.Process(this->DroppodType)
9981012

1013+
.Process(this->TiberiumEaterType)
1014+
9991015
.Process(this->Convert_HumanToComputer)
10001016
.Process(this->Convert_ComputerToHuman)
10011017

src/Ext/TechnoType/Body.h

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <New/Type/DigitalDisplayTypeClass.h>
1414
#include <New/Type/SelectBoxTypeClass.h>
1515
#include <New/Type/Affiliated/DroppodTypeClass.h>
16+
#include <New/Type/Affiliated/TiberiumEaterTypeClass.h>
1617

1718
class Matrix3D;
1819
class ParticleSystemTypeClass;
@@ -62,6 +63,7 @@ class TechnoTypeExt
6263
Valueable<ShieldTypeClass*> ShieldType;
6364
std::unique_ptr<PassengerDeletionTypeClass> PassengerDeletionType;
6465
std::unique_ptr<DroppodTypeClass> DroppodType;
66+
std::unique_ptr<TiberiumEaterTypeClass> TiberiumEaterType;
6567

6668
Nullable<float> HarvesterDumpAmount;
6769

@@ -556,6 +558,7 @@ class TechnoTypeExt
556558
, SpawnHeight {}
557559
, LandingDir {}
558560
, DroppodType {}
561+
, TiberiumEaterType {}
559562

560563
, Convert_HumanToComputer { }
561564
, Convert_ComputerToHuman { }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#include "TiberiumEaterTypeClass.h"
2+
3+
#include <Utilities/SavegameDef.h>
4+
#include <Utilities/TemplateDef.h>
5+
6+
void TiberiumEaterTypeClass::LoadFromINI(CCINIClass* pINI, const char* pSection)
7+
{
8+
INI_EX exINI(pINI);
9+
char tempBuffer[32];
10+
11+
this->TransDelay.Read(exINI, pSection, "TiberiumEater.TransDelay");
12+
this->CashMultiplier.Read(exINI, pSection, "TiberiumEater.CashMultiplier");
13+
this->AmountPerCell.Read(exINI, pSection, "TiberiumEater.AmountPerCell");
14+
15+
for (size_t idx = 0; ; ++idx)
16+
{
17+
Nullable<Vector2D<int>> cell;
18+
_snprintf_s(tempBuffer, sizeof(tempBuffer), "TiberiumEater.Cell%d", idx);
19+
cell.Read(exINI, pSection, tempBuffer);
20+
21+
if (idx >= this->Cells.size())
22+
{
23+
if (!cell.isset())
24+
break;
25+
26+
this->Cells.emplace_back(cell.Get().X * Unsorted::LeptonsPerCell, cell.Get().Y * Unsorted::LeptonsPerCell);
27+
}
28+
else
29+
{
30+
if (!cell.isset())
31+
continue;
32+
33+
this->Cells[idx] = Vector2D<int> { cell.Get().X * Unsorted::LeptonsPerCell, cell.Get().Y * Unsorted::LeptonsPerCell };
34+
}
35+
}
36+
37+
this->Display.Read(exINI, pSection, "TiberiumEater.Display");
38+
this->DisplayToHouse.Read(exINI, pSection, "TiberiumEater.DisplayToHouse");
39+
this->DisplayOffset.Read(exINI, pSection, "TiberiumEater.DisplayOffset");
40+
this->Anims.Read(exINI, pSection, "TiberiumEater.Anims");
41+
42+
for (size_t idx = 0; idx < 4; ++idx)
43+
{
44+
_snprintf_s(tempBuffer, sizeof(tempBuffer), "TiberiumEater.Anims.Tiberium%d", idx);
45+
this->Anims_Tiberiums[idx].Read(exINI, pSection, tempBuffer);
46+
}
47+
48+
this->AnimMove.Read(exINI, pSection, "TiberiumEater.AnimMove");
49+
}
50+
51+
template <class T>
52+
bool TiberiumEaterTypeClass::Serialize(T& stm)
53+
{
54+
return stm
55+
.Process(this->TransDelay)
56+
.Process(this->CashMultiplier)
57+
.Process(this->AmountPerCell)
58+
.Process(this->Cells)
59+
.Process(this->Display)
60+
.Process(this->DisplayToHouse)
61+
.Process(this->DisplayOffset)
62+
.Process(this->Anims)
63+
.Process(this->Anims_Tiberiums)
64+
.Process(this->AnimMove)
65+
.Success();
66+
}
67+
68+
#pragma region(save/load)
69+
70+
bool TiberiumEaterTypeClass::Load(PhobosStreamReader& stm, bool registerForChange)
71+
{
72+
return this->Serialize(stm);
73+
}
74+
75+
bool TiberiumEaterTypeClass::Save(PhobosStreamWriter& stm) const
76+
{
77+
return const_cast<TiberiumEaterTypeClass*>(this)->Serialize(stm);
78+
}
79+
80+
#pragma endregion
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#pragma once
2+
3+
#include <Utilities/Constructs.h>
4+
#include <Utilities/Enum.h>
5+
#include <Utilities/Template.h>
6+
7+
class TiberiumEaterTypeClass
8+
{
9+
public:
10+
Valueable<int> TransDelay { -1 };
11+
Valueable<float> CashMultiplier { 1.0 };
12+
Valueable<int> AmountPerCell { 0 };
13+
std::vector<Vector2D<int>> Cells { std::vector<Vector2D<int>>(1) };
14+
Valueable<bool> Display { true };
15+
Valueable<AffectedHouse> DisplayToHouse { AffectedHouse::All };
16+
Valueable<Point2D> DisplayOffset { Point2D::Empty };
17+
ValueableVector<AnimTypeClass*> Anims {};
18+
NullableVector<AnimTypeClass*> Anims_Tiberiums[4] {};
19+
Valueable<bool> AnimMove { true };
20+
21+
TiberiumEaterTypeClass() = default;
22+
23+
void LoadFromINI(CCINIClass* pINI, const char* pSection);
24+
bool Load(PhobosStreamReader& stm, bool registerForChange);
25+
bool Save(PhobosStreamWriter& stm) const;
26+
27+
private:
28+
29+
template <typename T>
30+
bool Serialize(T& stm);
31+
};

0 commit comments

Comments
 (0)