Skip to content

Commit 2f1fc0e

Browse files
CrimRecyaCoronia
andauthored
[Highly Customized] Jumpjet Climbing Logic Enhancement (Phobos-developers#1318)
In vanilla game, the jumpjet unit would stop horizontal movement as it rose to a relatively high altitude. Here are two options to change this. - You can now let the jumpjets increase their height earlier by set `JumpjetClimbPredictHeight` to true or simply let them skip the stop check by set `JumpjetClimbWithoutCutOut` to true. In `rulesmd.ini`: ```ini [General] JumpjetClimbPredictHeight=false ; boolean JumpjetClimbWithoutCutOut=false ; boolean ``` --------- Co-authored-by: Coronia <[email protected]>
1 parent 5a4750f commit 2f1fc0e

File tree

6 files changed

+183
-2
lines changed

6 files changed

+183
-2
lines changed

CREDITS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,7 @@ This page lists all the individual contributions to the project by their author.
537537
- Restore turret recoil effect
538538
- Fix an issue that `FireAngle` was not taken into account when drawing barrel in `TurretShadow`
539539
- Fix an issue that barrel anim data will be incorrectly overwritten by turret anim data if the techno's section exists in the map file
540+
- Jumpjet Climbing Logic Enhancement
540541
- **Ollerus**:
541542
- Build limit group enhancement
542543
- Customizable rocker amplitude

docs/Fixed-or-Improved-Logics.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,6 +1287,19 @@ IronCurtain.ExtraTintIntensity=0.0 ; floating point value
12871287
ForceShield.ExtraTintIntensity=0.0 ; floating point value
12881288
```
12891289

1290+
### Jumpjet climbing logic enhancement
1291+
1292+
- You can now let the jumpjets increase their height earlier by set `JumpjetClimbPredictHeight` to true. The jumpjet will raise its height 5 cells in advance, instead of only raising its height when encountering cliffs or buildings in front of it.
1293+
- You can also let them simply skip the stop check by set `JumpjetClimbWithoutCutOut` to true. The jumpjet will not stop moving horizontally when encountering cliffs or buildings in front of it, but will continue to move forward while raising its altitude.
1294+
- When `JumpjetClimbPredictHeight` is enabled, if the height raised five grids in advance is still not enough to cross cliffs or buildings, it will stop and move horizontally as before, unless `JumpjetClimbWithoutCutOut` is also enabled.
1295+
1296+
In `rulesmd.ini`:
1297+
```ini
1298+
[General]
1299+
JumpjetClimbPredictHeight=false ; boolean
1300+
JumpjetClimbWithoutCutOut=false ; boolean
1301+
```
1302+
12901303
### Jumpjet rotating on crashing toggle
12911304

12921305
- Jumpjet that is going to crash starts to change its facing uncontrollably, this can now be turned off.

docs/Whats-New.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,7 @@ New:
432432
- Reverse engineer warhead (by CrimRecya)
433433
- AI base construction modification (by CrimRecya)
434434
- Restore turret recoil effect (by CrimRecya)
435+
- Jumpjet Climbing Logic Enhancement (by CrimRecya)
435436
436437
Vanilla fixes:
437438
- Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya)

src/Ext/Rules/Body.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,9 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI)
224224
this->DefaultInfantrySelectBox.Read(exINI, GameStrings::AudioVisual, "DefaultInfantrySelectBox");
225225
this->DefaultUnitSelectBox.Read(exINI, GameStrings::AudioVisual, "DefaultUnitSelectBox");
226226

227+
this->JumpjetClimbPredictHeight.Read(exINI, GameStrings::General, "JumpjetClimbPredictHeight");
228+
this->JumpjetClimbWithoutCutOut.Read(exINI, GameStrings::General, "JumpjetClimbWithoutCutOut");
229+
227230
this->DamageOwnerMultiplier.Read(exINI, GameStrings::CombatDamage, "DamageOwnerMultiplier");
228231
this->DamageAlliesMultiplier.Read(exINI, GameStrings::CombatDamage, "DamageAlliesMultiplier");
229232
this->DamageEnemiesMultiplier.Read(exINI, GameStrings::CombatDamage, "DamageEnemiesMultiplier");
@@ -501,6 +504,8 @@ void RulesExt::ExtData::Serialize(T& Stm)
501504
.Process(this->DropPodTrailer)
502505
.Process(this->DropPodDefaultTrailer)
503506
.Process(this->PodImage)
507+
.Process(this->JumpjetClimbPredictHeight)
508+
.Process(this->JumpjetClimbWithoutCutOut)
504509
.Process(this->DamageOwnerMultiplier)
505510
.Process(this->DamageAlliesMultiplier)
506511
.Process(this->DamageEnemiesMultiplier)

src/Ext/Rules/Body.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,9 @@ class RulesExt
173173
Valueable<AnimTypeClass*> Promote_VeteranAnimation;
174174
Valueable<AnimTypeClass*> Promote_EliteAnimation;
175175

176+
Valueable<bool> JumpjetClimbPredictHeight;
177+
Valueable<bool> JumpjetClimbWithoutCutOut;
178+
176179
Valueable<double> DamageOwnerMultiplier;
177180
Valueable<double> DamageAlliesMultiplier;
178181
Valueable<double> DamageEnemiesMultiplier;
@@ -389,6 +392,8 @@ class RulesExt
389392
, DropPodTrailer { }
390393
, DropPodDefaultTrailer { }
391394
, PodImage { }
395+
, JumpjetClimbPredictHeight { false }
396+
, JumpjetClimbWithoutCutOut { false }
392397
, DamageOwnerMultiplier { 1.0 }
393398
, DamageAlliesMultiplier { 1.0 }
394399
, DamageEnemiesMultiplier { 1.0 }

src/Ext/Unit/Hooks.Jumpjet.cpp

Lines changed: 158 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
#include <JumpjetLocomotionClass.h>
22
#include <UnitClass.h>
3+
#include <BuildingClass.h>
4+
35
#include <Utilities/Macro.h>
46
#include <Ext/Techno/Body.h>
5-
#include <Ext/TechnoType/Body.h>
67
#include <Ext/WeaponType/Body.h>
7-
#include <Ext/Techno/Body.h>
88

99
// Misc jumpjet facing, turning, drawing fix -- Author: Trsdy
1010
// Jumpjets stuck at FireError::FACING because Jumpjet has its own facing just for JumpjetTurnRate
@@ -222,3 +222,159 @@ void __stdcall JumpjetLocomotionClass_Unlimbo(ILocomotion* pThis)
222222
}
223223

224224
DEFINE_FUNCTION_JUMP(VTABLE, 0x7ECDB8, JumpjetLocomotionClass_Unlimbo)
225+
226+
// Let the jumpjet increase their height earlier or simply skip the stop check
227+
namespace JumpjetRushHelpers
228+
{
229+
bool Skip = false;
230+
int GetJumpjetHeightWithOccupyTechno(const CellClass* pCell); // Replace sub_485080
231+
int JumpjetLocomotionPredictHeight(JumpjetLocomotionClass* pThis); // Replace sub_54D820
232+
}
233+
234+
int JumpjetRushHelpers::GetJumpjetHeightWithOccupyTechno(const CellClass* pCell)
235+
{
236+
if (const auto pBuilding = pCell->GetBuilding())
237+
{
238+
auto dim2 = CoordStruct::Empty;
239+
pBuilding->Type->Dimension2(&dim2);
240+
return dim2.Z;
241+
}
242+
243+
int height = 0;
244+
245+
if (pCell->FindTechnoNearestTo(Point2D::Empty, false))
246+
height += 85; // Vanilla
247+
248+
if (pCell->ContainsBridge())
249+
height += CellClass::BridgeHeight;
250+
251+
return height;
252+
}
253+
254+
int JumpjetRushHelpers::JumpjetLocomotionPredictHeight(JumpjetLocomotionClass* pThis)
255+
{
256+
const auto pFoot = pThis->LinkedTo;
257+
const auto pLocation = &pFoot->Location;
258+
259+
constexpr int shift = 8; // >> shift -> / Unsorted::LeptonsPerCell
260+
constexpr auto point2Cell = [](const Point2D& point) -> CellStruct
261+
{
262+
return CellStruct { static_cast<short>(point.X >> shift), static_cast<short>(point.Y >> shift) };
263+
};
264+
auto getJumpjetHeight = [](const CellClass* const pCell, const Point2D& point) -> int
265+
{
266+
return pCell->GetFloorHeight(Point2D { point.X, point.Y }) + JumpjetRushHelpers::GetJumpjetHeightWithOccupyTechno(pCell);
267+
};
268+
269+
// Initialize
270+
auto curCoord = Point2D { pLocation->X, pLocation->Y };
271+
const CellClass* pCurCell = MapClass::Instance.GetCellAt(point2Cell(curCoord));
272+
int maxHeight = getJumpjetHeight(pCurCell, curCoord);
273+
274+
// If is moving
275+
if (pThis->CurrentSpeed > 0.0)
276+
{
277+
// Prepare for prediction
278+
auto lastCoord = Point2D::Empty;
279+
const int checkLength = (pThis->LocomotionFacing.IsRotating() || !pFoot->Destination)
280+
? Unsorted::LeptonsPerCell
281+
: Math::min((Unsorted::LeptonsPerCell * 5), pFoot->DistanceFrom(pFoot->Destination)); // Predict the distance of 5 cells ahead
282+
const double angle = -pThis->LocomotionFacing.Current().GetRadian<65536>();
283+
const auto checkCoord = Point2D { static_cast<int>(checkLength * Math::cos(angle) + 0.5), static_cast<int>(checkLength * Math::sin(angle) + 0.5) };
284+
const int largeStep = Math::max(std::abs(checkCoord.X), std::abs(checkCoord.Y));
285+
const int checkSteps = (largeStep > Unsorted::LeptonsPerCell) ? (largeStep / Unsorted::LeptonsPerCell + 1) : 1;
286+
const auto stepCoord = Point2D { (checkCoord.X / checkSteps), (checkCoord.Y / checkSteps) };
287+
288+
auto getSideHeight = [](const CellClass* const pCell) -> int
289+
{
290+
return (pCell->Level * Unsorted::LevelHeight) + JumpjetRushHelpers::GetJumpjetHeightWithOccupyTechno(pCell);
291+
};
292+
auto getAntiAliasingCell = [&stepCoord, &checkCoord](const Point2D& curCoord, const Point2D& lastCoord) -> CellClass*
293+
{
294+
// Check if it is a diagonal relationship
295+
if ((curCoord.X >> shift) == (lastCoord.X >> shift) || (curCoord.Y >> shift) == (lastCoord.Y >> shift))
296+
return nullptr;
297+
298+
constexpr int mask = 0xFF; // & mask -> % Unsorted::LeptonsPerCell
299+
bool lastX = false;
300+
301+
// Calculate the bias of the previous cell
302+
if (std::abs(stepCoord.X) > std::abs(stepCoord.Y))
303+
{
304+
const int offsetX = curCoord.X & mask;
305+
const int deltaX = (stepCoord.X > 0) ? offsetX : (offsetX - Unsorted::LeptonsPerCell);
306+
const int projectedY = curCoord.Y - deltaX * checkCoord.Y / checkCoord.X;
307+
lastX = (projectedY ^ curCoord.Y) >> shift == 0;
308+
}
309+
else
310+
{
311+
const int offsetY = curCoord.Y & mask;
312+
const int deltaY = (stepCoord.Y > 0) ? offsetY : (offsetY - Unsorted::LeptonsPerCell);
313+
const int projectedX = curCoord.X - deltaY * checkCoord.X / checkCoord.Y;
314+
lastX = (projectedX ^ curCoord.X) >> shift != 0;
315+
}
316+
317+
// Get cell
318+
return MapClass::Instance.TryGetCellAt(lastX
319+
? CellStruct { static_cast<short>(lastCoord.X >> shift), static_cast<short>(curCoord.Y >> shift) }
320+
: CellStruct { static_cast<short>(curCoord.X >> shift), static_cast<short>(lastCoord.Y >> shift) });
321+
};
322+
auto checkStepHeight = [&maxHeight, &curCoord, &lastCoord, &pCurCell, &stepCoord,
323+
&getJumpjetHeight, &getAntiAliasingCell, &getSideHeight]() -> bool
324+
{
325+
// Check forward
326+
lastCoord = curCoord;
327+
curCoord += stepCoord;
328+
pCurCell = MapClass::Instance.TryGetCellAt(point2Cell(curCoord));
329+
330+
if (!pCurCell)
331+
return false;
332+
333+
maxHeight = Math::max(maxHeight, getJumpjetHeight(pCurCell, curCoord));
334+
335+
// "Anti-Aliasing"
336+
if (const auto pCheckCell = getAntiAliasingCell(curCoord, lastCoord))
337+
maxHeight = Math::max(maxHeight, getSideHeight(pCheckCell));
338+
339+
return true;
340+
};
341+
342+
// Predict height
343+
if (checkStepHeight())
344+
{
345+
// The forward cell is not so high, keep moving
346+
if ((pLocation->Z - maxHeight) >= pFoot->GetTechnoType()->JumpjetHeight)
347+
JumpjetRushHelpers::Skip = true;
348+
349+
// Check further
350+
for (int i = 1; i < checkSteps && checkStepHeight(); ++i);
351+
}
352+
}
353+
354+
return maxHeight;
355+
}
356+
357+
DEFINE_HOOK(0x54D827, JumpjetLocomotionClass_sub_54D820_PredictHeight, 0x8)
358+
{
359+
enum { SkipVanillaCalculate = 0x54D928 };
360+
361+
GET(JumpjetLocomotionClass*, pThis, ESI);
362+
363+
if (!RulesExt::Global()->JumpjetClimbPredictHeight)
364+
return 0;
365+
366+
R->EAX(JumpjetRushHelpers::JumpjetLocomotionPredictHeight(pThis));
367+
return SkipVanillaCalculate;
368+
}
369+
370+
DEFINE_HOOK(0x54D4C0, JumpjetLocomotionClass_sub_54D0F0_NoStuck, 0x6)
371+
{
372+
enum { SkipCheckStop = 0x54D52F };
373+
374+
if (JumpjetRushHelpers::Skip)
375+
JumpjetRushHelpers::Skip = false;
376+
else if (!RulesExt::Global()->JumpjetClimbWithoutCutOut)
377+
return 0;
378+
379+
return SkipCheckStop;
380+
}

0 commit comments

Comments
 (0)