Skip to content

Commit

Permalink
Implement: Fallin module
Browse files Browse the repository at this point in the history
* Implement and name all Fallin module functions.
* Implement Game functions related to fallins: Lego_LoadFallinMap, Lego_UpdateFallins, Lego_PTL_RockFall, Level_GenerateLandSlideNearBlock
* Implement LegoObject functions related to fallins: LegoObject_AttackPath, LegoObject_Callback_StampMiniFigureWithCrystal
* Add STANDARD_FRAMERATEI constant for integer version.
* Cleanup Lego_LoadErodeMap a bit and change stored block value to uint32 so that value isn't reduced to uint8.
  • Loading branch information
trigger-segfault committed Jun 21, 2023
1 parent 3a09450 commit d3a91ae
Show file tree
Hide file tree
Showing 9 changed files with 515 additions and 46 deletions.
1 change: 1 addition & 0 deletions src/openlrr/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@

// Standard Gods engine FPS constant. Used everywhere
#define STANDARD_FRAMERATE 25.0f
#define STANDARD_FRAMERATEI 25

#pragma endregion

Expand Down
190 changes: 180 additions & 10 deletions src/openlrr/game/Game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1876,7 +1876,7 @@ void __cdecl LegoRR::Lego_HandleWorldDebugKeys(sint32 mbx, sint32 mby, LegoObjec
/// DEBUG KEYBIND: [A] "Creates a landslide at mousepoint."
// The DIRECTION_FLAG_N here is probably why the landslide debug key is so finicky.
if (Shortcut_IsPressed(ShortcutID::Debug_CreateLandslide)) {
Fallin_Block_FUN_0040f260(&mouseBlockPos, DIRECTION_FLAG_N, true);
Fallin_GenerateLandSlide(&mouseBlockPos, DIRECTION_FLAG_N, true);
}
/// DEBUG KEYBIND: [A] "Causes unit at mousepoint to slip."
if (mouseOverObj != nullptr && Shortcut_IsPressed(ShortcutID::Debug_TripUnit)) {
Expand Down Expand Up @@ -2693,8 +2693,7 @@ bool32 __cdecl LegoRR::Lego_LoadErodeMap(Lego_Level* level, const char* filename
return false;

uint32 fileSize;
uint32 handle = Gods98::File_LoadBinaryHandle(filename, &fileSize);
/// TODO: We need a constant for this... probably.
const uint32 handle = Gods98::File_LoadBinaryHandle(filename, &fileSize);
if (handle == (uint32)MEMORY_HANDLE_INVALID)
return false;

Expand All @@ -2706,13 +2705,17 @@ bool32 __cdecl LegoRR::Lego_LoadErodeMap(Lego_Level* level, const char* filename
for (uint32 by = 0; by < height; by++) {
for (uint32 bx = 0; bx < width; bx++) {
const Point2I blockPos = { (sint32)bx, (sint32)by };
Lego_Block* block = &blockValue(level, bx, by);

const Lego_ErodeType erodeType = (Lego_ErodeType)MapShared_GetBlock(handle, bx, by);
block->erodeSpeed = 0; // No erosion for this block.

// type: Lego_ErodeType
const uint32 erodeType = MapShared_GetBlock(handle, bx, by);
if (erodeType != Lego_ErodeType_None) {
// erodeSpeed can range from [1,5] (erodeType: [1,10] + 1 -> [2,11]).
blockValue(level, bx, by).erodeSpeed = ((uint8)erodeType + 1) / 2;
block->erodeSpeed = (uint8)((erodeType + 1) / 2);

if (((uint32)erodeType % 2) == 0) {
if ((erodeType % 2) == 0) {
// Even erode types are source blocks.
const uint32 rng = (uint32)Gods98::Maths_Rand();

Expand Down Expand Up @@ -2808,10 +2811,84 @@ bool32 __cdecl LegoRR::Level_HandleEmergeTriggers(Lego_Level* level, const Point
//bool32 __cdecl LegoRR::Lego_LoadPathMap(Lego_Level* level, const char* filename, sint32 modifier);

// <LegoRR.exe @0042c900>
//bool32 __cdecl LegoRR::Lego_LoadFallinMap(Lego_Level* level, const char* filename);
bool32 __cdecl LegoRR::Lego_LoadFallinMap(Lego_Level* level, const char* filename)
{
// Don't use fallin data for blocks unless we successfully load the map file.
legoGlobs.hasFallins = false;

if (filename == nullptr)
return false;

uint32 fileSize;
const uint32 handle = Gods98::File_LoadBinaryHandle(filename, &fileSize);
if (handle == (uint32)MEMORY_HANDLE_INVALID)
return false;

uint32 width, height;
MapShared_GetDimensions(handle, &width, &height);
const bool sizeMatches = (width == level->width && height == level->height);
if (sizeMatches) {
// Use fallin data for blocks.
legoGlobs.hasFallins = true;

for (uint32 by = 0; by < height; by++) {
for (uint32 bx = 0; bx < width; bx++) {
Lego_Block* block = &blockValue(level, bx, by);

block->fallinIntensity = 0; // No fallins for this block.

// type: Lego_FallInType
const uint32 fallinType = MapShared_GetBlock(handle, bx, by);
if (fallinType != Lego_FallInType_None) {

if (fallinType >= Lego_FallInType_Danger_Low) {
block->fallinIntensity = (uint32)(fallinType - Lego_FallInType_Danger_Low) + 1; // [1,4]
block->fallinUpper = true;
}
else {
block->fallinIntensity = fallinType; // [1,4]
block->fallinUpper = false;
}

/// FIX APPLY: Properly use fallinIntensity for maxTime instead of fallinType, which might be higher.
/// As seen in Lego_UpdateFallins.
const uint32 maxTime = (block->fallinIntensity * legoGlobs.FallinMultiplier * STANDARD_FRAMERATEI);
block->fallinTimer = (real32)((uint32)block->randomness % maxTime);
}
}
}
}

Gods98::Mem_FreeHandle(handle);
return sizeMatches;
}

// <LegoRR.exe @0042caa0>
//void __cdecl LegoRR::Lego_UpdateFallins(real32 elapsedGame);
void __cdecl LegoRR::Lego_UpdateFallins(real32 elapsedWorld)
{
if (legoGlobs.hasFallins) {
Lego_Level* level = Lego_GetLevel();

for (uint32 by = 0; by < level->height; by++) {
for (uint32 bx = 0; bx < level->width; bx++) {
Lego_Block* block = &blockValue(level, bx, by);

if (block->fallinIntensity != 0) {
block->fallinTimer += elapsedWorld;
const uint32 maxTime = block->fallinIntensity * legoGlobs.FallinMultiplier * STANDARD_FRAMERATEI;
if (block->fallinTimer > (real32)maxTime) {
block->fallinTimer = 0.0f;
const Point2I blockPos = { (sint32)bx, (sint32)by };

if (Fallin_TryGenerateLandSlide(&blockPos, block->fallinUpper)) {
Info_Send(Info_Landslide, nullptr, nullptr, &blockPos);
}
}
}
}
}
}
}

// <LegoRR.exe @0042cbc0>
//bool32 __cdecl LegoRR::Lego_LoadBlockPointersMap(Lego_Level* level, const char* filename, sint32 modifier);
Expand Down Expand Up @@ -3012,7 +3089,60 @@ const char* __cdecl LegoRR::Level_Free(void)
//void __cdecl LegoRR::Level_UncoverHiddenCavern(uint32 bx, uint32 by);

// <LegoRR.exe @004316b0>
//void __cdecl LegoRR::Lego_PTL_RockFall(uint32 bx, uint32 by, Direction direction, bool32 isBlockVertexPos);
void __cdecl LegoRR::Lego_PTL_RockFall(uint32 bx, uint32 by, Direction direction, bool32 isBlockVertexPos)
{
const Point2F EFFECT_DIRECTIONS[DIRECTION__COUNT] = {
{ 0.0f, 1.0f },
{ 1.0f, 0.0f },
{ 0.0f, -1.0f },
{ -1.0f, 0.0f },
};

/// FIX APPLY: Don't use floating points here, there's no need to.
/// REFACTOR: Use standard directions here, since it doesn't make a difference.
const Point2I DIRECTIONS[DIRECTION__COUNT] = {
{ -1, 0 },
{ 0, 1 },
{ 1, 0 },
{ 0, -1 },
};

// Count number of adjacent floor blocks to determine rockfall effect type.
uint32 floorCount = 0;
for (uint32 i = 0; i < _countof(DIRECTIONS); i++) {
const Point2I blockOffPos = {
(sint32)bx + DIRECTIONS[i].x,
(sint32)by + DIRECTIONS[i].y,
};
if (blockValue(Lego_GetLevel(), blockOffPos.x, blockOffPos.y).flags1 & BLOCK1_FLOOR) {
floorCount++;
}
}
const RockFallType rockFallType = (floorCount <= 1 ? ROCKFALL_3SIDES : ROCKFALL_OUTSIDECORNER);

Vector3F wPos = { 0.0f, 0.0f, 0.0f }; // dummy init
if (!isBlockVertexPos) {
Map3D_BlockToWorldPos(Lego_GetMap(), bx, by, &wPos.x, &wPos.y);
}
else {
// wPos.z obtained here is not used.
Map3D_BlockVertexToWorldPos(Lego_GetMap(), bx, by, &wPos.x, &wPos.y, &wPos.z);
}
/// TODO: We can probably skip getting the z pos for Map3D_BlockVertexToWorldPos...
wPos.z = Map3D_GetWorldZ(Lego_GetMap(), wPos.x, wPos.y);

/// REFACTOR: We don't need to get the world z twice.
const Vector3F sfxPos = wPos;
//Vector3F sfxPos = { wPos.x, wPos.y, 0.0f };
//sfxPos.z = Map3D_GetWorldZ(Lego_GetMap(), sfxPos.x, sfxPos.y);
SFX_Random_PlaySound3DOnContainer(nullptr, SFX_RockBreak, false, false, &sfxPos);

if (Effect_Spawn_RockFall(rockFallType, bx, by, wPos.x, wPos.y, wPos.z,
EFFECT_DIRECTIONS[direction].x, EFFECT_DIRECTIONS[direction].y))
{
blockValue(Lego_GetLevel(), bx, by).flags1 |= BLOCK1_ROCKFALLFX;
}
}

// <LegoRR.exe @004318e0>
//LegoRR::Lego_SurfaceType __cdecl LegoRR::Lego_GetBlockTerrain(sint32 bx, sint32 by);
Expand Down Expand Up @@ -3419,7 +3549,47 @@ void __cdecl LegoRR::Level_PowerGrid_ClearDrainPowerBlocks(void)
//void __cdecl LegoRR::Level_SetPointer_FromSurfaceType(Lego_SurfaceType surfaceType);

// <LegoRR.exe @00435160>
//void __cdecl LegoRR::Level_GenerateFallin_InRadius(const Point2I* blockPos, sint32 radius, bool32 param_3);
void __cdecl LegoRR::Level_GenerateLandSlideNearBlock(const Point2I* blockPos, sint32 radius, bool32 once)
{
bool32 blocksTried[10][10] = { { false } };

const uint32 diameter = radius * 2;

uint32 maxTries = diameter * diameter;
if (!once) {
// Reduce max attempts if we're asking for more than one fallin.
maxTries /= 4;
}
// We don't need to worry about going over list bounds,
// since we only check within the diameter x diameter area.
//if (maxTries > (10 * 10)) {
// maxTries = (10 * 10);
//}

for (uint32 i = 0; i < maxTries; i++) {
const uint32 rngY = (uint32)Gods98::Maths_Rand() % diameter;
const uint32 rngX = (uint32)Gods98::Maths_Rand() % diameter;

const Point2I blockOffPos = {
blockPos->x + (sint32)rngX - radius,
blockPos->y + (sint32)rngY - radius,
};

// Unlike other grids, X is the higher order coordinate here.
//const uint32 idx = 10 * rngX + rngY;
if (!blocksTried[rngX][rngY]) {
// We haven't already tried this block.
if (Fallin_TryGenerateLandSlide(&blockOffPos, true)) {
if (once) {
return; // Fallin generated, finish here since only one was asked for.
}
}

// Mark this block as tried, so we don't try it again.
blocksTried[rngX][rngY] = true;
}
}
}

// <LegoRR.exe @00435230>
//void __cdecl LegoRR::Level_UpdateTutorialBlockFlashing(Lego_Level* level, Gods98::Viewport* viewMain, real32 elapsedGame, real32 elapsedAbs);
Expand Down
18 changes: 9 additions & 9 deletions src/openlrr/game/Game.h
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ struct Lego_Block // [LegoRR/Lego.c|struct:0x48|pack:1]
/*34,1*/ uint8 aiNode;
/*35,3*/ undefined field_0x35_0x37[3];
/*38,4*/ bool32 fallinUpper; // (fallin upper: 1 if fallin > 4)
/*3c,4*/ sint32 fallinIntensity; // (fallin scale: 1-4)
/*3c,4*/ uint32 fallinIntensity; // (fallin scale: 1-4)
/*40,4*/ real32 fallinTimer; // (randomized with full fallin value)
/*44,4*/ bool32 tutoHighlightState; // Tutorial block highlight color (false = tutorial color, true = normal).
/*48*/
Expand Down Expand Up @@ -1426,12 +1426,12 @@ bool32 __cdecl Level_HandleEmergeTriggers(Lego_Level* level, const Point2I* bloc
//bool32 __cdecl Lego_LoadPathMap(Lego_Level* level, const char* filename, sint32 modifier);

// <LegoRR.exe @0042c900>
#define Lego_LoadFallinMap ((bool32 (__cdecl* )(Lego_Level* level, const char* filename))0x0042c900)
//bool32 __cdecl Lego_LoadFallinMap(Lego_Level* level, const char* filename);
//#define Lego_LoadFallinMap ((bool32 (__cdecl* )(Lego_Level* level, const char* filename))0x0042c900)
bool32 __cdecl Lego_LoadFallinMap(Lego_Level* level, const char* filename);

// <LegoRR.exe @0042caa0>
#define Lego_UpdateFallins ((void (__cdecl* )(real32 elapsedGame))0x0042caa0)
//void __cdecl Lego_UpdateFallins(real32 elapsedGame);
//#define Lego_UpdateFallins ((void (__cdecl* )(real32 elapsedWorld))0x0042caa0)
void __cdecl Lego_UpdateFallins(real32 elapsedWorld);

// <LegoRR.exe @0042cbc0>
#define Lego_LoadBlockPointersMap ((bool32 (__cdecl* )(Lego_Level* level, const char* filename, sint32 modifier))0x0042cbc0)
Expand Down Expand Up @@ -1592,8 +1592,8 @@ __inline Map3D* Lego_GetMap(void) { return Lego_GetLevel()->map; }
//void __cdecl Level_UncoverHiddenCavern(uint32 bx, uint32 by);

// <LegoRR.exe @004316b0>
#define Lego_PTL_RockFall ((void (__cdecl* )(uint32 bx, uint32 by, Direction direction, bool32 isBlockVertexPos))0x004316b0)
//void __cdecl Lego_PTL_RockFall(uint32 bx, uint32 by, Direction direction, bool32 isBlockVertexPos);
//#define Lego_PTL_RockFall ((void (__cdecl* )(uint32 bx, uint32 by, Direction direction, bool32 isBlockVertexPos))0x004316b0)
void __cdecl Lego_PTL_RockFall(uint32 bx, uint32 by, Direction direction, bool32 isBlockVertexPos);

// <LegoRR.exe @004318e0>
#define Lego_GetBlockTerrain ((Lego_SurfaceType (__cdecl* )(sint32 bx, sint32 by))0x004318e0)
Expand Down Expand Up @@ -1929,8 +1929,8 @@ __inline SFX_ID __cdecl Lego_GetSurfaceTypeSFX(Lego_SurfaceType surfaceType) { r
//void __cdecl Level_SetPointer_FromSurfaceType(Lego_SurfaceType surfaceType);

// <LegoRR.exe @00435160>
#define Level_GenerateFallin_InRadius ((void (__cdecl* )(const Point2I* blockPos, sint32 radius, bool32 param_3))0x00435160)
//void __cdecl Level_GenerateFallin_InRadius(const Point2I* blockPos, sint32 radius, bool32 param_3);
//#define Level_GenerateLandSlideNearBlock ((void (__cdecl* )(const Point2I* blockPos, sint32 radius, bool32 once))0x00435160)
void __cdecl Level_GenerateLandSlideNearBlock(const Point2I* blockPos, sint32 radius, bool32 once);

// <LegoRR.exe @00435230>
#define Level_UpdateTutorialBlockFlashing ((void (__cdecl* )(Lego_Level* level, Gods98::Viewport* viewMain, real32 elapsedGame, real32 elapsedAbs))0x00435230)
Expand Down
48 changes: 46 additions & 2 deletions src/openlrr/game/object/Object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "../audio/SFX.h"
#include "../effects/DamageText.h"
#include "../effects/Effects.h"
#include "../front/Reward.h"
#include "../interface/hud/Bubbles.h"
#include "../interface/Encyclopedia.h"
Expand Down Expand Up @@ -1506,10 +1507,53 @@ void __cdecl LegoRR::LegoObject_RequestPowerGridUpdate(void)
//void __cdecl LegoRR::LegoObject_RockMonster_FUN_0043ad70(LegoObject* liveObj);

// <LegoRR.exe @0043aeb0>
//void __cdecl LegoRR::LegoObject_FUN_0043aeb0(LegoObject* liveObj);
void __cdecl LegoRR::LegoObject_AttackPath(LegoObject* liveObj)
{
Point2I blockPos = { 0, 0 }; // dummy init
LegoObject_GetBlockPos(liveObj, &blockPos.x, &blockPos.y);

// 1-in-5 chance to generate landslides near the stamped block.
if (((uint32)Gods98::Maths_Rand() % 5) == 0) {
Level_GenerateLandSlideNearBlock(&blockPos, 3, true);
}

// Stamp and destroy the path.
if (Level_Block_IsPath(&blockPos)) {
Effect_Spawn_SmashPath(liveObj, nullptr);
AITask_DoClearTypeAction(&blockPos, Message_ClearRemovePathComplete);
Level_BlockUpdateSurface(Lego_GetLevel(), blockPos.x, blockPos.y, 0);
}

// Cause any Mini-Figures carrying crystals within the stamp radius to drop everything.
for (auto obj : objectListSet.EnumerateSkipUpgradeParts()) {
LegoObject_Callback_StampMiniFigureWithCrystal(obj, liveObj);
}
//LegoObject_RunThroughListsSkipUpgradeParts(LegoObject_Callback_StampMiniFigureWithCrystal, liveObj);
}

// <LegoRR.exe @0043af50>
//bool32 __cdecl LegoRR::LegoObject_Callback_TryStampMiniFigureWithCrystal(LegoObject* targetObj, LegoObject* stamperObj);
bool32 __cdecl LegoRR::LegoObject_Callback_StampMiniFigureWithCrystal(LegoObject* targetObj, void* pStamperObj)
{
LegoObject* stamperObj = (LegoObject*)pStamperObj;

if (targetObj->type == LegoObject_MiniFigure && (targetObj->flags1 & LIVEOBJ1_CARRYING) &&
targetObj->carriedObjects[0]->type == LegoObject_PowerCrystal)
{
Point2F stamperPos = { 0.0f, 0.0f }; // dummy init
LegoObject_GetPosition(stamperObj, &stamperPos.x, &stamperPos.y);
Point2F targetPos = { 0.0f, 0.0f }; // dummy init
LegoObject_GetPosition(targetObj, &targetPos.x, &targetPos.y);

// Is target within stamping distance?
const real32 stampRadius = StatsObject_GetStampRadius(stamperObj);
if (Gods98::Maths_Vector2DDistance(&targetPos, &stamperPos) < stampRadius) {
// Drop carried crystal and end routing.
LegoObject_DropCarriedObject(targetObj, false);
LegoObject_Route_End(targetObj, false);
}
}
return false;
}

// <LegoRR.exe @0043b010>
LegoRR::LegoObject* __cdecl LegoRR::LegoObject_TryGenerateSlug(LegoObject* originObj, LegoObject_ID objID)
Expand Down
8 changes: 4 additions & 4 deletions src/openlrr/game/object/Object.h
Original file line number Diff line number Diff line change
Expand Up @@ -949,12 +949,12 @@ void __cdecl LegoObject_RequestPowerGridUpdate(void);
//void __cdecl LegoObject_RockMonster_FUN_0043ad70(LegoObject* liveObj);

// <LegoRR.exe @0043aeb0>
#define LegoObject_FUN_0043aeb0 ((void (__cdecl* )(LegoObject* liveObj))0x0043aeb0)
//void __cdecl LegoObject_FUN_0043aeb0(LegoObject* liveObj);
//#define LegoObject_AttackPath ((void (__cdecl* )(LegoObject* liveObj))0x0043aeb0)
void __cdecl LegoObject_AttackPath(LegoObject* liveObj);

// <LegoRR.exe @0043af50>
#define LegoObject_Callback_TryStampMiniFigureWithCrystal ((bool32 (__cdecl* )(LegoObject* targetObj, LegoObject* stamperObj))0x0043af50)
//bool32 __cdecl LegoObject_Callback_TryStampMiniFigureWithCrystal(LegoObject* targetObj, LegoObject* stamperObj);
//#define LegoObject_Callback_StampMiniFigureWithCrystal ((bool32 (__cdecl* )(LegoObject* targetObj, LegoObject* stamperObj))0x0043af50)
bool32 __cdecl LegoObject_Callback_StampMiniFigureWithCrystal(LegoObject* targetObj, void* pStamperObj);

// <LegoRR.exe @0043b010>
//#define LegoObject_TryGenerateSlug ((LegoObject* (__cdecl* )(LegoObject* originObj, LegoObject_ID objID))0x0043b010)
Expand Down
Loading

0 comments on commit d3a91ae

Please sign in to comment.