Hey Everyone,
I think we should add some code to OP2Helper to ease programming lava/eruptions.
Mcshay wrote an interesting smart lava library but it appears there are only header files and a lava.lib file available so the source code isn't fully readable. There is some overlap in use though. http://forum.outpost2.net/index.php/topic,3208.msg53008.html#msg53008
Currently OP2Helper contains 2 structures in a header called lava.h that are designed to emulate how Dynamix programmers set Lava (but I don't know if they are used by anyone in practice?). I'm proposing expanding lava.h and adding lava.cpp. The new content would be functions easing the setting of large map sections as lava possible and easing setting or stopping lava flow animations. I would like to encapsulate the new code in the namespace Lava. The original 2 structures should probably remain outside the namespace for backward compatibility.
One function allows setting a MAP_RECT as lava possible. Another function allows setting any tile that has a cell type of S1 or S2 in a MAP_RECT as lava possible.
I pretty much dump identical code into whatever scenario I'm interested in adding a volcano to, so it would be helpful to me if it was contained in OP2Helper. While I made some minor edits, this code has been floating around the forums for a very long time and was not written by me.
If there is interest, I could write a function that searches out all tileIndices on a map that match the darker lava rock (that resides inside the gray rock) and set them as lava possible. Since I always set these tiles to S1 or S2 anyways, I'm not sure it is a needed function though.
Current lava.h contents:
// Note: This file contains code to emulate how official Sierra maps seem to be built.
// These structs are used to define data about where lava can expand on the map.
// Size: 0x8
struct Range
{
int start;
int end;
};
// Size: 0x1C [0x1C = 28, or 7 dwords]
struct LavaPossibleInfo
{
int x;
Range y1;
Range y2;
Range y3;
};
Proposed addition to lava.h
#include "Outpost2DLL\Outpost2DLL.h"
#include "OP2Helper\OP2Helper.h"
namespace Lava
{
// Sets all tiles in a MAP_RECT to lava-possible
void SetLavaPossibleRegion(const MAP_RECT& mapRect);
// Sets all S1 and S2 Celltypes in a MAP_RECT to lava-possible
void SetLavaPossibleAllSlowCells(const MAP_RECT& mapRect);
//Functions to start the lava flow animation on the side of a volcano
void AnimateFlowSW(const LOCATION& TopLeftMostTile);
void AnimateFlowS(const LOCATION& TopLeftMostTile);
void AnimateFlowSE(const LOCATION& TopLeftMostTile);
//Functions to stop the lava flow animation on the side of a volcano
void FreezeFlowSW(const LOCATION& TopLeftMostTile);
void FreezeFlowS(const LOCATION& TopLeftMostTile);
void FreezeFlowSE(const LOCATION& TopLeftMostTile);
}
Proposed lava.cpp contents
namespace Lava
{
void SetLavaPossibleRegion(const MAP_RECT& mapRect)
{
int rectWidth = mapRect.x2 - mapRect.x1 + 1;
int rectHeight = mapRect.y2 - mapRect.y1 + 1;
int numberOfTiles = rectWidth * rectHeight;
LOCATION currentLoc;
for (int i = 0; i < numberOfTiles; ++i)
{
currentLoc = LOCATION(mapRect.x1 + i % rectWidth, mapRect.y1 + i / rectWidth);
GameMap::SetLavaPossible(currentLoc, true);
}
}
void SetLavaPossibleAllSlowCells(const MAP_RECT& mapRect)
{
int cellType;
LOCATION location;
for (int y = mapRect.y1; y <= mapRect.y2; ++y)
{
for (int x = mapRect.x1; x <= mapRect.x2; ++x)
{
location = LOCATION(x, y);
cellType = GameMap::GetCellType(location);
if (cellType == CellTypes::cellSlowPassible1 ||
cellType == CellTypes::cellSlowPassible2)
{
GameMap::SetLavaPossible(location, true);
}
}
}
}
void AnimateFlowSW(const LOCATION& loc)
{
GameMap::SetTile(loc + LOCATION(1, 0), 0x453);
GameMap::SetTile(loc, 0x447);
GameMap::SetTile(loc + LOCATION(0, 1), 0x45E);
GameMap::SetTile(loc + LOCATION(1, 1), 0x469);
}
void AnimateFlowS(const LOCATION& loc)
{
GameMap::SetTile(loc, 0x474);
GameMap::SetTile(loc + LOCATION(0, 1), 0x47E);
}
void AnimateFlowSE(const LOCATION& loc)
{
GameMap::SetTile(loc, 0x489);
GameMap::SetTile(loc + LOCATION(0, 1), 0x4A0);
GameMap::SetTile(loc + LOCATION(1, 1), 0x4AB);
GameMap::SetTile(loc + LOCATION(1, 0), 0x494);
}
void FreezeFlowSW(const LOCATION& loc)
{
GameMap::SetTile(loc + LOCATION(1, 0), 0x45A);
GameMap::SetTile(loc, 0x44F);
GameMap::SetTile(loc + LOCATION(0, 1), 0x465);
GameMap::SetTile(loc + LOCATION(1, 1), 0x470);
}
void FreezeFlowS(const LOCATION& loc)
{
GameMap::SetTile(loc, 0x47B);
GameMap::SetTile(loc + LOCATION(0, 1), 0x486);
}
void FreezeFlowSE(const LOCATION& loc)
{
GameMap::SetTile(loc, 0x490);
GameMap::SetTile(loc + LOCATION(0, 1), 0x4A8);
GameMap::SetTile(loc + LOCATION(1, 1), 0x4B2);
GameMap::SetTile(loc + LOCATION(1, 0), 0x49C);
}
}
I wrote the function using the remainder operator to purposefully remove a nested loop. Someone else wrote the nested loop function on the forum that I shamelessly stole. It is a little odd that both approaches are used right next to each other.
When I have worked with 2D maps in the past, I've always preferred storing the map data in a 1D array as opposed to a 2D array. I think this made working with the remainder operator more natural than a nested x & y for loop. I also find working with a nested loop harder to read. But the remainder operator isn't any easier to read I suppose. So I guess what I'm saying is it doesn't matter too much to me which way it goes. It would be an easy function to re-write. Performance wise it doesn't matter much since you will only call the function once or twice at initialization to set lava possible areas.
If we remove the namespace from the code, will it be a problem if other custom scenarios have already defined their own function with the same name and reference OP2Helper? I know at least one other scenario uses very similar names, but I would have to check if they are identical or not. Unless there are other opinions to keep the namespace around, I'll remove it to keep the code base consistent.
EDIT
Below is SetLavaPossibleRegion re-written as a nested loop. Hooman is right, it looks cleaner than my approach, especially since the region is already packaged as a MAP_RECT.
for (int x = mapRect.x1; x <= mapRect.x2; ++x)
{
for (int y = mapRect.y1; y <= mapRect.y2; ++y)
{
GameMap::SetLavaPossible(LOCATION(x, y), true);
}
}
EDIT 2: This function needs to ensure that x1 and y1 are less then x2 and y2 unless MAP_RECT automatically does that.
Hey everyone,
Thanks for the comments. I understand the name mangling issue with the namespace and that it will not ruin anything to wrap OP2 Helper functions and classes in namespaces. I'm not sure what EAX and EDX is though (I'm guessing related to assembly code?).
I'll plan to post a ver2 of the code in a little bit.
I did some test cases with MAP_RECT to see how it would handle wrapping issues with an East/West map and handling inverted values.
MAP_RECT accepts inverted values without crashing.
IE MAP_RECT(25, 25, 5, 5)
However, doing a check to see if a valid point is inside the MAP_RECT, it will return false.
IE MAP_RECT(25, 25, 5, 5);
// Will return false.
bool inside = mapRect.Check(LOCATION(15, 15);
When passing a MAP_RECT to wraps from East to West on a 512 tile width map or larger, check will pass if your MAP_RECT uses a smaller x2 value:
MAP_RECT mapRect(500 - 1, 0 - 1, 50 - 1, 20 - 1);
//Will return true on a 512 or greater size map
bool inside = mapRect.Check(LOCATION(5 - 1, 5 - 1));
Passing a x2 value greater than the width of the map that wraps east west will not pass check.
MAP_RECT mapRect2(500 - 1, 0 - 1, 600 - 1, 20 - 1);
// Fails
inside = mapRect2.Check(LOCATION(5 - 1, 5 - 1));
Below is version 2 of the Lava code. Full header and cpp file are displayed. I left the namespace in and fixed the SetLavaPossibleRegion to a nested x, y loop.
Looking for other changes or more dissent if we really don't want the namespace to exist.
SetLavaPossibleRegion will not be able to handle horizontal wrapping on maps, but I think we have concluded that is okay.
Lava.h
#pragma once
#include "Outpost2DLL\Outpost2DLL.h"
#include "OP2Helper\OP2Helper.h"
namespace Lava
{
// Sets all tiles in a MAP_RECT to lava-possible.
void SetLavaPossibleRegion(const MAP_RECT& mapRect);
// Sets all S1 and S2 Celltypes in a MAP_RECT to lava-possible
void SetLavaPossibleAllSlowCells(const MAP_RECT& mapRect);
//Functions to start the lava flow animation on the side of a volcano
void AnimateFlowSW(const LOCATION& TopLeftMostTile);
void AnimateFlowS(const LOCATION& TopLeftMostTile);
void AnimateFlowSE(const LOCATION& TopLeftMostTile);
//Functions to stop the lava flow animation on the side of a volcano
void FreezeFlowSW(const LOCATION& TopLeftMostTile);
void FreezeFlowS(const LOCATION& TopLeftMostTile);
void FreezeFlowSE(const LOCATION& TopLeftMostTile);
}
// Note: The structs Range and LavaPossibleInfo emulate how official Sierra maps
// define data about where lava can expand on the map.
// Size: 0x8
struct Range
{
int start;
int end;
};
// Size: 0x1C [0x1C = 28, or 7 dwords]
struct LavaPossibleInfo
{
int x;
Range y1;
Range y2;
Range y3;
};
Lava.cpp
#include "Lava.h"
namespace Lava
{
void SetLavaPossibleRegion(const MAP_RECT& mapRect)
{
for (int x = mapRect.x1; x <= mapRect.x2; ++x)
{
for (int y = mapRect.y1; y <= mapRect.y2; ++y)
{
GameMap::SetLavaPossible(LOCATION(x, y), true);
}
}
}
void SetLavaPossibleAllSlowCells(const MAP_RECT& mapRect)
{
int cellType;
LOCATION location;
for (int y = mapRect.y1; y <= mapRect.y2; ++y)
{
for (int x = mapRect.x1; x <= mapRect.x2; ++x)
{
location = LOCATION(x, y);
cellType = GameMap::GetCellType(location);
if (cellType == CellTypes::cellSlowPassible1 ||
cellType == CellTypes::cellSlowPassible2)
{
GameMap::SetLavaPossible(location, true);
}
}
}
}
void AnimateFlowSW(const LOCATION& loc)
{
GameMap::SetTile(loc + LOCATION(1, 0), 0x453);
GameMap::SetTile(loc, 0x447);
GameMap::SetTile(loc + LOCATION(0, 1), 0x45E);
GameMap::SetTile(loc + LOCATION(1, 1), 0x469);
}
void AnimateFlowS(const LOCATION& loc)
{
GameMap::SetTile(loc, 0x474);
GameMap::SetTile(loc + LOCATION(0, 1), 0x47E);
}
void AnimateFlowSE(const LOCATION& loc)
{
GameMap::SetTile(loc, 0x489);
GameMap::SetTile(loc + LOCATION(0, 1), 0x4A0);
GameMap::SetTile(loc + LOCATION(1, 1), 0x4AB);
GameMap::SetTile(loc + LOCATION(1, 0), 0x494);
}
void FreezeFlowSW(const LOCATION& loc)
{
GameMap::SetTile(loc + LOCATION(1, 0), 0x45A);
GameMap::SetTile(loc, 0x44F);
GameMap::SetTile(loc + LOCATION(0, 1), 0x465);
GameMap::SetTile(loc + LOCATION(1, 1), 0x470);
}
void FreezeFlowS(const LOCATION& loc)
{
GameMap::SetTile(loc, 0x47B);
GameMap::SetTile(loc + LOCATION(0, 1), 0x486);
}
void FreezeFlowSE(const LOCATION& loc)
{
GameMap::SetTile(loc, 0x490);
GameMap::SetTile(loc + LOCATION(0, 1), 0x4A8);
GameMap::SetTile(loc + LOCATION(1, 1), 0x4B2);
GameMap::SetTile(loc + LOCATION(1, 0), 0x49C);
}
}