941 lines
23 KiB
C++
941 lines
23 KiB
C++
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "../noita_random.cpp"
|
|
#include "stb_hbwang.h"
|
|
#include "coalmine_hax.cpp"
|
|
#include "jps.hh"
|
|
#include "pix.cpp"
|
|
#include "search.cpp"
|
|
#include "png.cpp"
|
|
#include "MurmurHash3.cpp"
|
|
|
|
// TODO: Look into optimizing with <https://github.com/google/highway>
|
|
|
|
const bool DEBUG = false;
|
|
|
|
// MODIFIED (noita_gpu): see MapGen::hasPath. Default is the engine's value.
|
|
static int g_path_check_max_height = 205;
|
|
|
|
NollaPrng GetRNG(int width)
|
|
{
|
|
NollaPrng rng;
|
|
rng.SetRandomFromWorldSeed(world_seed);
|
|
rng.Next();
|
|
|
|
int iters = width + world_seed + 11 * (width / -11) - 12 * (world_seed / 12);
|
|
|
|
if (iters > 0)
|
|
{
|
|
do
|
|
{
|
|
rng.Next();
|
|
iters -= 1;
|
|
} while (iters != 0);
|
|
}
|
|
return rng;
|
|
}
|
|
|
|
class ToFrom
|
|
{
|
|
public:
|
|
unsigned int from;
|
|
unsigned int *to;
|
|
int to_count;
|
|
NollaPrng rng;
|
|
};
|
|
|
|
// This needs to be sorted for binary search
|
|
static const unsigned long blockedColors[] = {
|
|
0x00ac6e, // load_pixel_scene4_alt
|
|
0x70d79e, // load_gunpowderpool_01
|
|
0x70d79f, //???
|
|
0x70d7a1, // load_gunpowderpool_04
|
|
0x7868ff, // load_gunpowderpool_02
|
|
0xc35700, // load_oiltank
|
|
0xff0080, // load_pixel_scene2
|
|
0xff00ff, //???
|
|
0xff0aff, // load_pixel_scene
|
|
};
|
|
|
|
bool isBlockedColor(unsigned long color)
|
|
{
|
|
for (uint i = 0; i < sizeof(blockedColors) / sizeof(blockedColors[0]); i++)
|
|
{
|
|
if (blockedColors[i] == color)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const int BIOME_PATH_FIND_WORLD_POS_MIN_X = 159;
|
|
const int BIOME_PATH_FIND_WORLD_POS_MAX_X = 223;
|
|
|
|
const int WORLD_OFFSET_X = 35;
|
|
const int WORLD_OFFSET_Y = 14;
|
|
|
|
int GetWidthFromPix(const int a, const int b, const int offset)
|
|
{
|
|
return (((b - offset) * 512) / 10 - ((a - offset) * 512) / 10);
|
|
}
|
|
int GetWidthFromPix(const int a, const int b)
|
|
{
|
|
return GetWidthFromPix(a, b, 0);
|
|
}
|
|
|
|
static int floorDiv512(int value)
|
|
{
|
|
if (value >= 0)
|
|
{
|
|
return value / 512;
|
|
}
|
|
return -(((-value) + 511) / 512);
|
|
}
|
|
|
|
int GetTilePosXValue(const int gx, const int gy)
|
|
{
|
|
return floorDiv512(gx) + WORLD_OFFSET_X;
|
|
}
|
|
|
|
int GetTilePosYValue(const int gx, const int gy)
|
|
{
|
|
return floorDiv512(gy) + WORLD_OFFSET_Y;
|
|
}
|
|
|
|
int GetGlobalPosXValue(const int x, const int y)
|
|
{
|
|
return 512 * (x - WORLD_OFFSET_X);
|
|
}
|
|
|
|
int GetGlobalPosYValue(const int x, const int y)
|
|
{
|
|
return 512 * (y - WORLD_OFFSET_Y);
|
|
}
|
|
|
|
double GetBiomeOffsetX()
|
|
{
|
|
NollaPrng rng = NewNollaPrng(world_seed);
|
|
double someRand = rng.Next() * 9999.0 + 1.0;
|
|
double someRand2 = rng.Next() * 200000.0 - 100000.0;
|
|
double offset = rng.Next() * 200000.0 - 100000.0;
|
|
return offset;
|
|
}
|
|
|
|
void rgbaToRgb(const unsigned char *src, unsigned char *dest, uint w, uint h)
|
|
{
|
|
unsigned long j = 0;
|
|
unsigned long src_size = w * h;
|
|
for (unsigned long i = 0; i < src_size; i++)
|
|
{
|
|
dest[j++] = src[i * 4];
|
|
dest[j++] = src[i * 4 + 1];
|
|
dest[j++] = src[i * 4 + 2];
|
|
}
|
|
}
|
|
|
|
void rgbToRgba(const unsigned char *src, unsigned char *dest, uint w, uint h)
|
|
{
|
|
unsigned long j = 0;
|
|
unsigned long src_size = w * h;
|
|
for (unsigned long i = 0; i < src_size; i++)
|
|
{
|
|
dest[j++] = src[i * 3];
|
|
dest[j++] = src[i * 3 + 1];
|
|
dest[j++] = src[i * 3 + 2];
|
|
dest[j++] = 255;
|
|
}
|
|
}
|
|
|
|
void whiteout(unsigned char *src, uint w, uint h)
|
|
{
|
|
unsigned long src_size = w * h;
|
|
for (unsigned long i = 0; i < src_size; i++)
|
|
{
|
|
src[i * 4] = 255;
|
|
src[i * 4 + 1] = 255;
|
|
src[i * 4 + 2] = 255;
|
|
src[i * 4 + 3] = 255;
|
|
}
|
|
}
|
|
|
|
enum mapToUse
|
|
{
|
|
out,
|
|
path
|
|
};
|
|
|
|
struct Block
|
|
{
|
|
int startX;
|
|
int startY;
|
|
int endX;
|
|
int endY;
|
|
};
|
|
|
|
struct BlockList
|
|
{
|
|
Block *items;
|
|
uint len;
|
|
uint cap;
|
|
};
|
|
|
|
void blockListClear(BlockList *list)
|
|
{
|
|
list->len = 0;
|
|
}
|
|
|
|
void blockListFree(BlockList *list)
|
|
{
|
|
free(list->items);
|
|
list->items = NULL;
|
|
list->len = 0;
|
|
list->cap = 0;
|
|
}
|
|
|
|
void blockListPush(BlockList *list, Block block)
|
|
{
|
|
if (list->len == list->cap)
|
|
{
|
|
list->cap = list->cap == 0 ? 32 : list->cap * 2;
|
|
list->items = (Block *)realloc(list->items, list->cap * sizeof(Block));
|
|
}
|
|
list->items[list->len++] = block;
|
|
}
|
|
|
|
class MapGen
|
|
{
|
|
public:
|
|
unsigned char *out_map;
|
|
unsigned char *path_map;
|
|
uint width;
|
|
uint height;
|
|
bool isCoalMine;
|
|
bool shouldBlockOutRooms;
|
|
unsigned int *randomMaterials;
|
|
int worldX;
|
|
int worldY;
|
|
|
|
mapToUse usemap = out;
|
|
|
|
unsigned long endNode;
|
|
unsigned long *parents;
|
|
|
|
BlockList blockedOutRooms = {};
|
|
|
|
MapGen(
|
|
unsigned char *_map,
|
|
uint _width,
|
|
uint _height,
|
|
bool _isCoalMine,
|
|
bool _shouldBlockOutRooms,
|
|
unsigned int *_randomMaterials,
|
|
int _worldX,
|
|
int _worldY) : width(_width),
|
|
height(_height),
|
|
out_map(_map),
|
|
isCoalMine(_isCoalMine),
|
|
shouldBlockOutRooms(_shouldBlockOutRooms),
|
|
randomMaterials(_randomMaterials),
|
|
worldX(_worldX),
|
|
worldY(_worldY)
|
|
{
|
|
path_map = (unsigned char *)malloc(3 * width * height);
|
|
memcpy(path_map, out_map, 3 * width * height);
|
|
parents = (unsigned long *)malloc(width * height * sizeof(unsigned long));
|
|
}
|
|
|
|
~MapGen()
|
|
{
|
|
blockListFree(&blockedOutRooms);
|
|
free(parents);
|
|
free(path_map);
|
|
}
|
|
|
|
unsigned char *GetMap()
|
|
{
|
|
if (DEBUG)
|
|
{
|
|
return out_map;
|
|
}
|
|
switch (usemap)
|
|
{
|
|
case out:
|
|
return out_map;
|
|
break;
|
|
case path:
|
|
return path_map;
|
|
break;
|
|
|
|
default:
|
|
return out_map;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void reset()
|
|
{
|
|
blockListClear(&blockedOutRooms);
|
|
}
|
|
|
|
void resetParents()
|
|
{
|
|
unsigned long count = width * height;
|
|
for (unsigned long i = 0; i < count; i++)
|
|
{
|
|
parents[i] = NO_PARENT;
|
|
}
|
|
}
|
|
|
|
bool hasPath()
|
|
{
|
|
usemap = out;
|
|
if (shouldBlockOutRooms)
|
|
{
|
|
blockOutRooms(COLOR_BLACK, COLOR_WHITE);
|
|
}
|
|
|
|
if (isCoalMine)
|
|
{
|
|
doCoalMineHax();
|
|
}
|
|
|
|
memcpy(path_map, out_map, 3 * width * height);
|
|
|
|
usemap = path;
|
|
bool valid = isValid();
|
|
// MODIFIED (noita_gpu): the engine only requires the path check to pass for maps whose height
|
|
// is <= 205 wang pixels (noita.exe FUN_00879bf0: `local_90 = height <= DAT_01152478`, value 205);
|
|
// taller maps accept the first generated image.
|
|
if ((int)height > g_path_check_max_height)
|
|
{
|
|
return true;
|
|
}
|
|
return valid;
|
|
}
|
|
|
|
void finalize()
|
|
{
|
|
usemap = out;
|
|
// Generated, now to fix up the map
|
|
bool mainPath = isMainPath();
|
|
if (!isCoalMine && mainPath)
|
|
{
|
|
fillMainPath();
|
|
}
|
|
if (isCoalMine)
|
|
{
|
|
undoCoalMineHax();
|
|
}
|
|
if (shouldBlockOutRooms)
|
|
{
|
|
fillBlockedRooms(COLOR_BLACK);
|
|
}
|
|
ClearPath();
|
|
fillC0ffee();
|
|
fillRandomMaterials();
|
|
}
|
|
|
|
void undoCoalMineHax()
|
|
{
|
|
unsigned char *map = GetMap();
|
|
for (int y = 0; y < height; y++)
|
|
{
|
|
for (int x = 0; x < width; x++)
|
|
{
|
|
long overlayPos = getPos(256, 3, x, y);
|
|
long i = getPos(width, 3, x, y);
|
|
long pix = createRGB(coalmine_overlay[overlayPos], coalmine_overlay[overlayPos + 1], coalmine_overlay[overlayPos + 2]);
|
|
if (pix == 0x4000)
|
|
{
|
|
// pudy248 note: is not actually air, this is the main rock portion of the overlay
|
|
map[i] = 0x00;
|
|
map[i + 1] = 0x00;
|
|
map[i + 2] = 0x00;
|
|
}
|
|
if (pix == 0x0040)
|
|
{ // blue. Looks like air?
|
|
map[i] = 0x00;
|
|
map[i + 1] = 0x00;
|
|
map[i + 2] = 0x00;
|
|
}
|
|
if (pix == 0xFEFEFE)
|
|
{ // white. Stairs. rock_static_intro
|
|
// In the debug it's not shown, but used in path finding.
|
|
map[i] = 0x00;
|
|
map[i + 1] = 0x00;
|
|
map[i + 2] = 0x00;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void doCoalMineHax()
|
|
{
|
|
unsigned char *map = GetMap();
|
|
for (int y = 0; y < height; y++)
|
|
{
|
|
for (int x = 0; x < width; x++)
|
|
{
|
|
long overlayPos = getPos(256, 3, x, y);
|
|
long i = getPos(width, 3, x, y);
|
|
long pix = createRGB(coalmine_overlay[overlayPos], coalmine_overlay[overlayPos + 1], coalmine_overlay[overlayPos + 2]);
|
|
if (pix == 0x4000)
|
|
{
|
|
// pudy248 note: is not actually air, this is the main rock portion of the overlay
|
|
map[i] = 0xFF;
|
|
map[i + 1] = 0xFF;
|
|
map[i + 2] = 0xFF;
|
|
}
|
|
if (pix == 0x0040)
|
|
{ // blue. Looks like air?
|
|
map[i] = 0x00;
|
|
map[i + 1] = 0x00;
|
|
map[i + 2] = 0x00;
|
|
}
|
|
if (pix == 0xFEFEFE)
|
|
{ // white. Stairs. rock_static_intro
|
|
// In the debug it's not shown, but used in path finding.
|
|
map[i] = 0x0a;
|
|
map[i + 1] = 0x33;
|
|
map[i + 2] = 0x44;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ClearPath()
|
|
{
|
|
unsigned char *map = GetMap();
|
|
unsigned long current = endNode;
|
|
unsigned long max = width * height;
|
|
while (current != NO_PARENT && current < max)
|
|
{
|
|
int x = current % width;
|
|
int y = current / width;
|
|
long c = getPixelColor(map, width, x, y);
|
|
if (c == COLOR_COFFEE)
|
|
{
|
|
floodFill(map, width, height, x, y, COLOR_COFFEE, COLOR_BLACK);
|
|
}
|
|
current = parents[current];
|
|
}
|
|
}
|
|
|
|
bool HasPathToBottom(
|
|
uint path_start_x,
|
|
bool fixed_x)
|
|
{
|
|
unsigned char *map = GetMap();
|
|
resetParents();
|
|
Search s = Search(map, width, height, 0, height - 1, parents);
|
|
|
|
if (fixed_x)
|
|
{
|
|
bool hasPath = s.findPath(path_start_x, 0);
|
|
if (hasPath)
|
|
{
|
|
endNode = s.endPos;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int x = path_start_x;
|
|
|
|
while (x < width)
|
|
{
|
|
long c = getPixelColor(map, width, x, 0);
|
|
if (c != COLOR_BLACK && c != COLOR_COFFEE)
|
|
{
|
|
x++;
|
|
continue;
|
|
}
|
|
|
|
if (c == COLOR_COFFEE)
|
|
{
|
|
floodFill(map, width, height, x, 0, COLOR_COFFEE, COLOR_BLACK);
|
|
continue;
|
|
}
|
|
|
|
bool hasPath = s.findPath(x, 0);
|
|
if (hasPath)
|
|
{
|
|
endNode = s.endPos;
|
|
return true;
|
|
}
|
|
x++;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool isMainPath()
|
|
{
|
|
int fill_x_from = (BIOME_PATH_FIND_WORLD_POS_MIN_X - worldX * 512.0) / 10;
|
|
int fill_x_to = fill_x_from + (BIOME_PATH_FIND_WORLD_POS_MAX_X - BIOME_PATH_FIND_WORLD_POS_MIN_X) / 10;
|
|
return worldY < 29 && fill_x_to > 0 && fill_x_from > 0 && width > fill_x_from && fill_x_to < width + fill_x_from;
|
|
}
|
|
|
|
int fillMainPath()
|
|
{
|
|
unsigned char *map = GetMap();
|
|
int fill_x_from = (BIOME_PATH_FIND_WORLD_POS_MIN_X - worldX * 512.0) / 10;
|
|
int fill_x_to = fill_x_from + (BIOME_PATH_FIND_WORLD_POS_MAX_X - BIOME_PATH_FIND_WORLD_POS_MIN_X) / 10;
|
|
fill(map, width, fill_x_from, fill_x_to, 0, 6, COLOR_BLACK);
|
|
return fill_x_from;
|
|
}
|
|
|
|
void clearStartingArea()
|
|
{
|
|
unsigned char *map = GetMap();
|
|
uint x = 0;
|
|
|
|
for (uint x = 0; x < width; x++)
|
|
{
|
|
long c = getPixelColor(map, width, x, 0);
|
|
if (c == COLOR_COFFEE)
|
|
{
|
|
floodFill(map, width, height, x, 0, COLOR_COFFEE, COLOR_BLACK);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool isValid()
|
|
{
|
|
uint path_start_x = 0;
|
|
bool mainPath = isMainPath();
|
|
if (isCoalMine)
|
|
{
|
|
path_start_x = 0x8e;
|
|
}
|
|
else if (mainPath)
|
|
{
|
|
path_start_x = fillMainPath();
|
|
}
|
|
|
|
bool hasPath = HasPathToBottom(path_start_x, mainPath);
|
|
|
|
return hasPath;
|
|
}
|
|
|
|
void blockOutRooms(unsigned long passableColor, unsigned long targetColor)
|
|
{
|
|
unsigned char *map = GetMap();
|
|
unsigned long posMax = width * height;
|
|
|
|
for (unsigned long pos = 0; pos < posMax; pos++)
|
|
{
|
|
long color = getPixelColor(map, pos * 3);
|
|
// ~70% of pixels are black or white, so skip them
|
|
if (color == COLOR_BLACK)
|
|
{
|
|
continue;
|
|
}
|
|
if (color == COLOR_WHITE)
|
|
{
|
|
continue;
|
|
}
|
|
if (!isBlockedColor(color))
|
|
{
|
|
continue;
|
|
}
|
|
uint x = pos % width;
|
|
uint y = pos / width;
|
|
|
|
int startX = x + 1;
|
|
int endX = x + 1;
|
|
int startY = y + 1;
|
|
int endY = y + 1;
|
|
bool foundEnd = false;
|
|
while (!foundEnd && endX < width)
|
|
{
|
|
long c = getPixelColor(map, width, endX, startY);
|
|
if (c == passableColor || ShadeOfGrey(c, 0x32))
|
|
{
|
|
endX += 1;
|
|
continue;
|
|
};
|
|
endX -= 1;
|
|
foundEnd = true;
|
|
}
|
|
if (endX >= width)
|
|
{
|
|
endX = width - 1;
|
|
}
|
|
foundEnd = false;
|
|
while (!foundEnd && endY < height)
|
|
{
|
|
long c = getPixelColor(map, width, startX, endY);
|
|
if (c == passableColor || ShadeOfGrey(c, 0x32))
|
|
{
|
|
endY += 1;
|
|
continue;
|
|
};
|
|
endY -= 1;
|
|
foundEnd = true;
|
|
}
|
|
if (endY >= height)
|
|
{
|
|
endY = height - 1;
|
|
}
|
|
blockListPush(&blockedOutRooms, Block{startX, startY, endX, endY});
|
|
fill(map, width, startX, endX, startY, endY, targetColor);
|
|
}
|
|
}
|
|
|
|
void fillBlockedRooms(unsigned long color)
|
|
{
|
|
unsigned char *map = GetMap();
|
|
for (uint i = 0; i < blockedOutRooms.len; i++)
|
|
{
|
|
Block block = blockedOutRooms.items[i];
|
|
fill(map, width, block.startX, block.endX, block.startY, block.endY, color);
|
|
}
|
|
}
|
|
|
|
void fillC0ffee()
|
|
{
|
|
unsigned char *map = GetMap();
|
|
NollaPrng rng = NewNollaPrng(0);
|
|
rng.SetRandomFromWorldSeed(world_seed);
|
|
rng.Next();
|
|
|
|
unsigned long posMax = width * height;
|
|
|
|
for (unsigned long pos = 0; pos < posMax; pos++)
|
|
{
|
|
long c = getPixelColor(map, pos * 3);
|
|
if (c != COLOR_COFFEE)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
long to = COLOR_BLACK;
|
|
if (0.5f > (float)rng.Next()) // BIOME_RANDOM_BLOCK_CHANCE
|
|
{
|
|
to = COLOR_WHITE;
|
|
}
|
|
|
|
int x = pos % width;
|
|
int y = pos / width;
|
|
floodFill(map, width, height, x, y, COLOR_COFFEE, to);
|
|
}
|
|
}
|
|
|
|
void fillRandomMaterials()
|
|
{
|
|
unsigned char *map = GetMap();
|
|
int numberOfRandoms = randomMaterials[0];
|
|
if (numberOfRandoms == 0)
|
|
{
|
|
return;
|
|
}
|
|
ToFrom *replacements = (ToFrom *)malloc(numberOfRandoms * sizeof(ToFrom));
|
|
int pos = 1;
|
|
|
|
for (int i = 0; i < numberOfRandoms; i++)
|
|
{
|
|
replacements[i].from = randomMaterials[pos];
|
|
replacements[i].to_count = randomMaterials[pos + 1];
|
|
replacements[i].to = &randomMaterials[pos + 2];
|
|
replacements[i].rng = GetRNG(width);
|
|
replacements[i].rng.Next();
|
|
pos += 2 + replacements[i].to_count;
|
|
}
|
|
|
|
for (int i = 0; i < numberOfRandoms; i++)
|
|
{
|
|
ToFrom *rr = &replacements[i];
|
|
unsigned long posMax = width * height;
|
|
for (unsigned long pos = 0; pos < posMax; pos++)
|
|
{
|
|
long c = getPixelColor(map, pos * 3);
|
|
if (c != rr->from)
|
|
{
|
|
continue;
|
|
}
|
|
int rand = rr->rng.Random(0, rr->to_count - 1);
|
|
int x = pos % width;
|
|
int y = pos / width;
|
|
floodFill(map, width, height, x, y, rr->from, rr->to[rand]);
|
|
}
|
|
}
|
|
free(replacements);
|
|
}
|
|
};
|
|
|
|
struct TilesetCacheEntry
|
|
{
|
|
unsigned long color;
|
|
stbhw_tileset *tileset;
|
|
};
|
|
|
|
static TilesetCacheEntry *tilesetCache = NULL;
|
|
static uint tilesetCacheLen = 0;
|
|
static uint tilesetCacheCap = 0;
|
|
|
|
static uint nextWangRandom(void *user)
|
|
{
|
|
return ((NollaPrng *)user)->NextU();
|
|
}
|
|
|
|
stbhw_tileset *get_tileset(unsigned long color, const unsigned char *rgba_tiles_data, uint tiles_width, uint tiles_height)
|
|
{
|
|
for (uint i = 0; i < tilesetCacheLen; i++)
|
|
{
|
|
if (tilesetCache[i].color == color)
|
|
{
|
|
return tilesetCache[i].tileset;
|
|
}
|
|
}
|
|
|
|
stbhw_tileset *ts = (stbhw_tileset *)malloc(sizeof(stbhw_tileset));
|
|
memset(ts, 0, sizeof(stbhw_tileset));
|
|
unsigned char *rgb_tiles_data = (unsigned char *)malloc(3 * tiles_width * tiles_height);
|
|
rgbaToRgb(rgba_tiles_data, rgb_tiles_data, tiles_width, tiles_height);
|
|
int success = stbhw_build_tileset_from_image(ts, rgb_tiles_data, tiles_width * 3, tiles_width, tiles_height);
|
|
free(rgb_tiles_data);
|
|
if (!success)
|
|
{
|
|
free(ts);
|
|
return NULL;
|
|
}
|
|
|
|
if (tilesetCacheLen == tilesetCacheCap)
|
|
{
|
|
tilesetCacheCap = tilesetCacheCap == 0 ? 16 : tilesetCacheCap * 2;
|
|
tilesetCache = (TilesetCacheEntry *)realloc(tilesetCache, tilesetCacheCap * sizeof(TilesetCacheEntry));
|
|
}
|
|
tilesetCache[tilesetCacheLen++] = TilesetCacheEntry{color, ts};
|
|
return ts;
|
|
}
|
|
|
|
void generate_map_from_rgba_tiles(
|
|
unsigned char *rgba_tiles_data,
|
|
uint tiles_width,
|
|
uint tiles_height,
|
|
unsigned char *result,
|
|
uint width,
|
|
uint height,
|
|
unsigned long color,
|
|
bool isCoalMine,
|
|
bool shouldBlockOutRooms,
|
|
unsigned int *randomMaterials,
|
|
int worldX,
|
|
int worldY)
|
|
{
|
|
if (rgba_tiles_data == NULL || tiles_width == 0 || tiles_height == 0)
|
|
{
|
|
whiteout(result, width, height);
|
|
return;
|
|
}
|
|
|
|
stbhw_tileset *ts = get_tileset(color, rgba_tiles_data, tiles_width, tiles_height);
|
|
if (ts == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
NollaPrng rng = GetRNG(width);
|
|
|
|
int tries = 0;
|
|
long mod_malloc_amount = 3 * width * (height + 4);
|
|
unsigned char *res = (unsigned char *)malloc(mod_malloc_amount);
|
|
long malloc_amount = 3 * width * height;
|
|
|
|
unsigned char *rgb_result = (unsigned char *)malloc(malloc_amount);
|
|
|
|
bool hasPath = false;
|
|
do
|
|
{
|
|
if (tries >= 100)
|
|
{
|
|
break;
|
|
}
|
|
|
|
NollaPrng rng2 = NewNollaPrng(rng.NextU());
|
|
|
|
int success = stbhw_generate_image(ts, NULL, res, width * 3, width, height + 4, nextWangRandom, &rng2);
|
|
if (!success)
|
|
{
|
|
free(res);
|
|
free(rgb_result);
|
|
return;
|
|
}
|
|
|
|
int num_bytes = 4 * 3 * width;
|
|
memcpy(rgb_result, &res[num_bytes], mod_malloc_amount - num_bytes);
|
|
|
|
MapGen mg = MapGen(rgb_result, width, height, isCoalMine, shouldBlockOutRooms, randomMaterials, worldX, worldY);
|
|
|
|
hasPath = mg.hasPath();
|
|
if (hasPath)
|
|
{
|
|
mg.finalize();
|
|
}
|
|
mg.reset();
|
|
tries++;
|
|
} while (hasPath == false);
|
|
|
|
free(res);
|
|
rgbToRgba(rgb_result, result, width, height);
|
|
free(rgb_result);
|
|
}
|
|
|
|
class MapHandler
|
|
{
|
|
public:
|
|
uint width;
|
|
uint height;
|
|
unsigned long color;
|
|
bool isCoalMine;
|
|
bool shouldBlockOutRooms;
|
|
unsigned int *randomMaterials;
|
|
int worldX;
|
|
int worldY;
|
|
|
|
unsigned char *map;
|
|
unsigned char *bigMap;
|
|
|
|
void init(
|
|
uint _width,
|
|
uint _height,
|
|
unsigned long _color,
|
|
bool _isCoalMine,
|
|
bool _shouldBlockOutRooms,
|
|
uintptr_t _randomMaterials,
|
|
int _worldX,
|
|
int _worldY)
|
|
{
|
|
width = _width;
|
|
height = _height;
|
|
color = _color;
|
|
isCoalMine = _isCoalMine;
|
|
shouldBlockOutRooms = _shouldBlockOutRooms;
|
|
randomMaterials = reinterpret_cast<unsigned int *>(_randomMaterials);
|
|
worldX = _worldX - WORLD_OFFSET_X;
|
|
worldY = _worldY - WORLD_OFFSET_Y;
|
|
map = (unsigned char *)malloc(_width * _height * 4);
|
|
bigMap = (unsigned char *)malloc(_width * 10 * _height * 10 * 4);
|
|
}
|
|
|
|
void destroy()
|
|
{
|
|
free(map);
|
|
free(bigMap);
|
|
}
|
|
|
|
void generate_map(const unsigned char *png_data, uint32_t png_len)
|
|
{
|
|
if (png_data == NULL || png_len == 0)
|
|
{
|
|
whiteout(map, width, height);
|
|
return;
|
|
}
|
|
image tiles = load_png_bytes(png_data, png_len);
|
|
generate_map_from_rgba_tiles(
|
|
tiles.image,
|
|
tiles.width,
|
|
tiles.height,
|
|
map,
|
|
width,
|
|
height,
|
|
color,
|
|
isCoalMine,
|
|
shouldBlockOutRooms,
|
|
randomMaterials,
|
|
worldX,
|
|
worldY);
|
|
free_png_image(&tiles);
|
|
}
|
|
|
|
void toBig()
|
|
{
|
|
scaleImage(map, width, height, bigMap, 10);
|
|
}
|
|
|
|
void drawImageData(image *img, int gx, int gy, uintptr_t _colorToMaterialTable)
|
|
{
|
|
if (img == NULL || img->image == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// int tx = (gx) / 512;
|
|
int tx = (gx + 5) / 512;
|
|
// int rx = (gx) % 512;
|
|
int rx = (gx + 5) % 512;
|
|
|
|
// int ty = (gy) / 512;
|
|
int ty = (gy + 13) / 512;
|
|
// int ry = (gy) % 512;
|
|
int ry = (gy + 13) % 512;
|
|
|
|
int old_px = (tx - worldX) * 512 + rx;
|
|
int old_py = (ty - worldY) * 512 + ry;
|
|
|
|
int px = gx - worldX * 512;
|
|
int py = gy - worldY * 512;
|
|
|
|
// printf("drawImageData path: %s, gx: %i, gy: %i, px: %i, py: %i, old_px: %i, old_py: %i\n", path.c_str(), gx, gy, px, py, old_px, old_py);
|
|
|
|
const unsigned int *colorToMaterialTable = reinterpret_cast<unsigned int *>(_colorToMaterialTable);
|
|
|
|
// Check that the image won't go out of bounds
|
|
// if (px + img.width > width * 10 || py + img.height > height * 10)
|
|
// {
|
|
// printf("drawImageData out of bounds %i %i %i %i\n", px, py, img.width, img.height);
|
|
// return;
|
|
// }
|
|
// if (px < 0 || py < 0)
|
|
// {
|
|
// printf("drawImageData out of bounds %i %i %i %i\n", px, py, img.width, img.height);
|
|
// return;
|
|
// }
|
|
|
|
for (int y = 0; y < img->height; y++)
|
|
{
|
|
for (int x = 0; x < img->width; x++)
|
|
{
|
|
unsigned long offset = getPos(img->width, img->channels, x, y);
|
|
unsigned long pixel = getRGBAPixelColor(img->image, offset);
|
|
bool isTransparent = isRGBATransparent(pixel);
|
|
if (pixel == 0xFF || isTransparent) // 0x000000FF (black)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
unsigned long toColor = pixel;
|
|
for (int i = 0; i < colorToMaterialTable[0]; i++)
|
|
{
|
|
int offset = 1 + i * 2;
|
|
if (colorToMaterialTable[offset] == pixel)
|
|
{
|
|
toColor = colorToMaterialTable[offset + 1];
|
|
break;
|
|
}
|
|
}
|
|
setRGBAPixelColor(bigMap, width * 10, px + x, py + y, toColor);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// map and result are allocated and freed in js.
|
|
// Only used in coalmines?
|
|
STBHW_EXTERN void generate_path_map(
|
|
unsigned char map[],
|
|
uint map_w,
|
|
uint map_h,
|
|
unsigned char result[],
|
|
int worldX,
|
|
int worldY)
|
|
{
|
|
long malloc_amount = 3 * map_w * map_h;
|
|
memcpy(result, map, malloc_amount);
|
|
uint path_start_x = 0x8e;
|
|
floodFill(result, map_w, map_h, path_start_x, 1, COLOR_BLACK, COLOR_PURPLE);
|
|
}
|