This commit is contained in:
Gaël committed 2026-10-19 20:59:15 +02:00
commit 87bff164f1
416 files changed
+279512

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Seva Maltsev <me@seva.dev> (https://seva.dev/)"
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+72
View File
@@ -0,0 +1,72 @@
#pragma once
#include <stdint.h>
typedef unsigned int uint;
typedef uint8_t byte;
typedef int8_t int8;
typedef int16_t int16;
typedef int32_t int32;
typedef int64_t int64;
typedef uint8_t uint8;
typedef uint16_t uint16;
typedef uint32_t uint32;
typedef uint64_t uint64;
typedef uint8 bool8;
uint world_seed = 0;
class NollaPrng
{
public:
double Seed;
void SetRandomFromWorldSeed(uint s)
{
Seed = s;
if (2147483647.0 <= Seed)
{
Seed = s * 0.5;
}
}
uint NextU()
{
Next();
return Seed * 4.656612875e-10 * 2147483645.0;
}
double Next()
{
int v4 = 0x41a7 * (int)Seed - 0x7FFFFFFF * ((int)Seed / 0x1f31d);
if (v4 <= 0)
{
v4 += 0x7fffffff;
}
Seed = (double)v4;
return Seed / 0x7fffffff;
}
int Random(int a, int b)
{
return a + (int)((double)(b + 1 - a) * Next());
}
};
NollaPrng NewNollaPrng(double seed)
{
NollaPrng rng;
rng.Seed = seed;
rng.Next();
return rng;
}
void SetWorldSeed(uint worldseed)
{
world_seed = worldseed;
}
uint GetWorldSeed()
{
return world_seed;
}
+334
View File
@@ -0,0 +1,334 @@
//-----------------------------------------------------------------------------
// MurmurHash3 was written by Austin Appleby, and is placed in the public
// domain. The author hereby disclaims copyright to this source code.
// Note - The x86 and x64 versions do _not_ produce the same results, as the
// algorithms are optimized for their respective platforms. You can still
// compile and run any of them on any platform, but your performance with the
// non-native version will be less than optimal.
#include "MurmurHash3.h"
//-----------------------------------------------------------------------------
// Platform-specific functions and macros
// Microsoft Visual Studio
#if defined(_MSC_VER)
#define FORCE_INLINE __forceinline
#include <stdlib.h>
#define ROTL32(x,y) _rotl(x,y)
#define ROTL64(x,y) _rotl64(x,y)
#define BIG_CONSTANT(x) (x)
// Other compilers
#else // defined(_MSC_VER)
#define FORCE_INLINE inline __attribute__((always_inline))
inline uint32_t rotl32 ( uint32_t x, int8_t r )
{
return (x << r) | (x >> (32 - r));
}
inline uint64_t rotl64 ( uint64_t x, int8_t r )
{
return (x << r) | (x >> (64 - r));
}
#define ROTL32(x,y) rotl32(x,y)
#define ROTL64(x,y) rotl64(x,y)
#define BIG_CONSTANT(x) (x##LLU)
#endif // !defined(_MSC_VER)
//-----------------------------------------------------------------------------
// Block read - if your platform needs to do endian-swapping or can only
// handle aligned reads, do the conversion here
FORCE_INLINE uint32_t getblock32 ( const uint32_t * p, int i )
{
return p[i];
}
FORCE_INLINE uint64_t getblock64 ( const uint64_t * p, int i )
{
return p[i];
}
//-----------------------------------------------------------------------------
// Finalization mix - force all bits of a hash block to avalanche
FORCE_INLINE uint32_t fmix32 ( uint32_t h )
{
h ^= h >> 16;
h *= 0x85ebca6b;
h ^= h >> 13;
h *= 0xc2b2ae35;
h ^= h >> 16;
return h;
}
//----------
FORCE_INLINE uint64_t fmix64 ( uint64_t k )
{
k ^= k >> 33;
k *= BIG_CONSTANT(0xff51afd7ed558ccd);
k ^= k >> 33;
k *= BIG_CONSTANT(0xc4ceb9fe1a85ec53);
k ^= k >> 33;
return k;
}
//-----------------------------------------------------------------------------
void MurmurHash3_x86_32 ( const void * key, int len,
uint32_t seed, void * out )
{
const uint8_t * data = (const uint8_t*)key;
const int nblocks = len / 4;
uint32_t h1 = seed;
const uint32_t c1 = 0xcc9e2d51;
const uint32_t c2 = 0x1b873593;
//----------
// body
const uint32_t * blocks = (const uint32_t *)(data + nblocks*4);
for(int i = -nblocks; i; i++)
{
uint32_t k1 = getblock32(blocks,i);
k1 *= c1;
k1 = ROTL32(k1,15);
k1 *= c2;
h1 ^= k1;
h1 = ROTL32(h1,13);
h1 = h1*5+0xe6546b64;
}
//----------
// tail
const uint8_t * tail = (const uint8_t*)(data + nblocks*4);
uint32_t k1 = 0;
switch(len & 3)
{
case 3: k1 ^= tail[2] << 16;
case 2: k1 ^= tail[1] << 8;
case 1: k1 ^= tail[0];
k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1;
};
//----------
// finalization
h1 ^= len;
h1 = fmix32(h1);
*(uint32_t*)out = h1;
}
//-----------------------------------------------------------------------------
void MurmurHash3_x86_128 ( const void * key, const int len,
uint32_t seed, void * out )
{
const uint8_t * data = (const uint8_t*)key;
const int nblocks = len / 16;
uint32_t h1 = seed;
uint32_t h2 = seed;
uint32_t h3 = seed;
uint32_t h4 = seed;
const uint32_t c1 = 0x239b961b;
const uint32_t c2 = 0xab0e9789;
const uint32_t c3 = 0x38b34ae5;
const uint32_t c4 = 0xa1e38b93;
//----------
// body
const uint32_t * blocks = (const uint32_t *)(data + nblocks*16);
for(int i = -nblocks; i; i++)
{
uint32_t k1 = getblock32(blocks,i*4+0);
uint32_t k2 = getblock32(blocks,i*4+1);
uint32_t k3 = getblock32(blocks,i*4+2);
uint32_t k4 = getblock32(blocks,i*4+3);
k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1;
h1 = ROTL32(h1,19); h1 += h2; h1 = h1*5+0x561ccd1b;
k2 *= c2; k2 = ROTL32(k2,16); k2 *= c3; h2 ^= k2;
h2 = ROTL32(h2,17); h2 += h3; h2 = h2*5+0x0bcaa747;
k3 *= c3; k3 = ROTL32(k3,17); k3 *= c4; h3 ^= k3;
h3 = ROTL32(h3,15); h3 += h4; h3 = h3*5+0x96cd1c35;
k4 *= c4; k4 = ROTL32(k4,18); k4 *= c1; h4 ^= k4;
h4 = ROTL32(h4,13); h4 += h1; h4 = h4*5+0x32ac3b17;
}
//----------
// tail
const uint8_t * tail = (const uint8_t*)(data + nblocks*16);
uint32_t k1 = 0;
uint32_t k2 = 0;
uint32_t k3 = 0;
uint32_t k4 = 0;
switch(len & 15)
{
case 15: k4 ^= tail[14] << 16;
case 14: k4 ^= tail[13] << 8;
case 13: k4 ^= tail[12] << 0;
k4 *= c4; k4 = ROTL32(k4,18); k4 *= c1; h4 ^= k4;
case 12: k3 ^= tail[11] << 24;
case 11: k3 ^= tail[10] << 16;
case 10: k3 ^= tail[ 9] << 8;
case 9: k3 ^= tail[ 8] << 0;
k3 *= c3; k3 = ROTL32(k3,17); k3 *= c4; h3 ^= k3;
case 8: k2 ^= tail[ 7] << 24;
case 7: k2 ^= tail[ 6] << 16;
case 6: k2 ^= tail[ 5] << 8;
case 5: k2 ^= tail[ 4] << 0;
k2 *= c2; k2 = ROTL32(k2,16); k2 *= c3; h2 ^= k2;
case 4: k1 ^= tail[ 3] << 24;
case 3: k1 ^= tail[ 2] << 16;
case 2: k1 ^= tail[ 1] << 8;
case 1: k1 ^= tail[ 0] << 0;
k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1;
};
//----------
// finalization
h1 ^= len; h2 ^= len; h3 ^= len; h4 ^= len;
h1 += h2; h1 += h3; h1 += h4;
h2 += h1; h3 += h1; h4 += h1;
h1 = fmix32(h1);
h2 = fmix32(h2);
h3 = fmix32(h3);
h4 = fmix32(h4);
h1 += h2; h1 += h3; h1 += h4;
h2 += h1; h3 += h1; h4 += h1;
((uint32_t*)out)[0] = h1;
((uint32_t*)out)[1] = h2;
((uint32_t*)out)[2] = h3;
((uint32_t*)out)[3] = h4;
}
//-----------------------------------------------------------------------------
void MurmurHash3_x64_128 ( const void * key, const int len,
const uint32_t seed, void * out )
{
const uint8_t * data = (const uint8_t*)key;
const int nblocks = len / 16;
uint64_t h1 = seed;
uint64_t h2 = seed;
const uint64_t c1 = BIG_CONSTANT(0x87c37b91114253d5);
const uint64_t c2 = BIG_CONSTANT(0x4cf5ad432745937f);
//----------
// body
const uint64_t * blocks = (const uint64_t *)(data);
for(int i = 0; i < nblocks; i++)
{
uint64_t k1 = getblock64(blocks,i*2+0);
uint64_t k2 = getblock64(blocks,i*2+1);
k1 *= c1; k1 = ROTL64(k1,31); k1 *= c2; h1 ^= k1;
h1 = ROTL64(h1,27); h1 += h2; h1 = h1*5+0x52dce729;
k2 *= c2; k2 = ROTL64(k2,33); k2 *= c1; h2 ^= k2;
h2 = ROTL64(h2,31); h2 += h1; h2 = h2*5+0x38495ab5;
}
//----------
// tail
const uint8_t * tail = (const uint8_t*)(data + nblocks*16);
uint64_t k1 = 0;
uint64_t k2 = 0;
switch(len & 15)
{
case 15: k2 ^= ((uint64_t)tail[14]) << 48;
case 14: k2 ^= ((uint64_t)tail[13]) << 40;
case 13: k2 ^= ((uint64_t)tail[12]) << 32;
case 12: k2 ^= ((uint64_t)tail[11]) << 24;
case 11: k2 ^= ((uint64_t)tail[10]) << 16;
case 10: k2 ^= ((uint64_t)tail[ 9]) << 8;
case 9: k2 ^= ((uint64_t)tail[ 8]) << 0;
k2 *= c2; k2 = ROTL64(k2,33); k2 *= c1; h2 ^= k2;
case 8: k1 ^= ((uint64_t)tail[ 7]) << 56;
case 7: k1 ^= ((uint64_t)tail[ 6]) << 48;
case 6: k1 ^= ((uint64_t)tail[ 5]) << 40;
case 5: k1 ^= ((uint64_t)tail[ 4]) << 32;
case 4: k1 ^= ((uint64_t)tail[ 3]) << 24;
case 3: k1 ^= ((uint64_t)tail[ 2]) << 16;
case 2: k1 ^= ((uint64_t)tail[ 1]) << 8;
case 1: k1 ^= ((uint64_t)tail[ 0]) << 0;
k1 *= c1; k1 = ROTL64(k1,31); k1 *= c2; h1 ^= k1;
};
//----------
// finalization
h1 ^= len; h2 ^= len;
h1 += h2;
h2 += h1;
h1 = fmix64(h1);
h2 = fmix64(h2);
h1 += h2;
h2 += h1;
((uint64_t*)out)[0] = h1;
((uint64_t*)out)[1] = h2;
}
//-----------------------------------------------------------------------------
+37
View File
@@ -0,0 +1,37 @@
//-----------------------------------------------------------------------------
// MurmurHash3 was written by Austin Appleby, and is placed in the public
// domain. The author hereby disclaims copyright to this source code.
#ifndef _MURMURHASH3_H_
#define _MURMURHASH3_H_
//-----------------------------------------------------------------------------
// Platform-specific functions and macros
// Microsoft Visual Studio
#if defined(_MSC_VER) && (_MSC_VER < 1600)
typedef unsigned char uint8_t;
typedef unsigned int uint32_t;
typedef unsigned __int64 uint64_t;
// Other compilers
#else // defined(_MSC_VER)
#include <stdint.h>
#endif // !defined(_MSC_VER)
//-----------------------------------------------------------------------------
void MurmurHash3_x86_32 ( const void * key, int len, uint32_t seed, void * out );
void MurmurHash3_x86_128 ( const void * key, int len, uint32_t seed, void * out );
void MurmurHash3_x64_128 ( const void * key, int len, uint32_t seed, void * out );
//-----------------------------------------------------------------------------
#endif // _MURMURHASH3_H_
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+105
View File
@@ -0,0 +1,105 @@
#pragma once
#include "../noita_random.cpp"
const unsigned long COLOR_PURPLE = 0x7f007f;
const unsigned long COLOR_RED = 0xff0000;
const unsigned long COLOR_BLACK = 0x000000;
const unsigned long COLOR_WHITE = 0xffffff;
const unsigned long COLOR_YELLOW = 0xffff00;
// Extra traversable colors
const unsigned long COLOR_COFFEE = 0xc0ffee;
const unsigned long COLOR_FROZEN_VAULT_MINT = 0xcff7c8;
const unsigned long COLOR_HELL_GREEN = 0x8aff80;
unsigned long createRGB(const unsigned char r, const unsigned char g, const unsigned char b)
{
return ((r & 0xff) << 16) + ((g & 0xff) << 8) + (b & 0xff);
}
unsigned long createRGBA(const unsigned char r, const unsigned char g, const unsigned char b, const unsigned char a)
{
return ((r & 0xff) << 24) | ((g & 0xff) << 16) | ((b & 0xff) << 8) | (a & 0xff);
}
unsigned long getPos(const uint w, const uint s, const uint x, const uint y)
{
return s * (w * y + x);
}
unsigned long getPixelColor(const unsigned char *map, unsigned long pos)
{
unsigned char r = map[pos];
unsigned char g = map[pos + 1];
unsigned char b = map[pos + 2];
return createRGB(r, g, b);
}
unsigned long getPixelColor(const unsigned char *map, const uint w, const uint x, const uint y)
{
unsigned long pos = getPos(w, 3, x, y);
return getPixelColor(map, pos);
}
bool ShadeOfGrey(const unsigned long color, const unsigned char limit)
{
unsigned char r = (color >> 16) & 0xFF;
unsigned char g = (color >> 8) & 0xFF;
unsigned char b = color & 0xFF;
return r <= limit && (r == g) && (g == b) && color != COLOR_BLACK && color != COLOR_WHITE;
}
bool isRGBATransparent(const unsigned long color)
{
unsigned char a = color & 0xFF;
return a == 0;
}
unsigned long getRGBAPixelColor(const unsigned char *map, unsigned long pos)
{
unsigned char r = map[pos];
unsigned char g = map[pos + 1];
unsigned char b = map[pos + 2];
unsigned char a = map[pos + 3];
return createRGBA(r, g, b, a);
}
unsigned long getRGBAPixelColor(const unsigned char *map, const uint w, const uint x, const uint y)
{
unsigned long pos = getPos(w, 4, x, y);
return getRGBAPixelColor(map, pos);
}
void setPixelColor(unsigned char *map, unsigned long pos, unsigned long color)
{
unsigned char r = ((color >> 16) & 0xff);
unsigned char g = ((color >> 8) & 0xff);
unsigned char b = ((color) & 0xff);
map[pos] = r;
map[pos + 1] = g;
map[pos + 2] = b;
}
void setRGBAPixelColor(unsigned char *map, unsigned long pos, unsigned long color)
{
unsigned char r = ((color >> 24) & 0xff);
unsigned char g = ((color >> 16) & 0xff);
unsigned char b = ((color >> 8) & 0xff);
unsigned char a = ((color) & 0xff);
map[pos] = r;
map[pos + 1] = g;
map[pos + 2] = b;
map[pos + 3] = a;
}
void setPixelColor(unsigned char *map, uint w, uint x, uint y, unsigned long color)
{
unsigned long pos = getPos(w, 3, x, y);
setPixelColor(map, pos, color);
}
void setRGBAPixelColor(unsigned char *map, uint w, uint x, uint y, unsigned long color)
{
unsigned long pos = getPos(w, 4, x, y);
setRGBAPixelColor(map, pos, color);
}
+87
View File
@@ -0,0 +1,87 @@
#pragma once
#include <stdint.h>
#include <stdlib.h>
void zero_out(uint8_t *data, uint l)
{
if (data == NULL)
{
return;
}
for (uint i = 0; i < l; i += 4)
{
uint8_t r = data[i + 0];
uint8_t g = data[i + 1];
uint8_t b = data[i + 2];
uint8_t a = data[i + 3];
if (a == 0 && r == 255 && g == 255 && b == 255)
{
data[i + 0] = 0;
data[i + 1] = 0;
data[i + 2] = 0;
data[i + 3] = 0;
}
}
}
struct image
{
int width;
int height;
int channels;
stbi_uc *image;
};
image load_png_bytes(const unsigned char *data, int len)
{
int width = 0;
int height = 0;
int channels = 0;
stbi_uc *img = stbi_load_from_memory(data, len, &width, &height, &channels, 4);
if (img == NULL)
{
return image{0, 0, 0, NULL};
}
channels = 4;
zero_out(img, width * height * channels);
return image{width, height, channels, img};
}
void free_png_image(image *img)
{
if (img == NULL)
{
return;
}
stbi_image_free(img->image);
img->image = NULL;
img->width = 0;
img->height = 0;
img->channels = 0;
}
void scaleImage(unsigned char *src, uint src_width, uint src_height, unsigned char *dest, unsigned char scale)
{
int dest_width = src_width * scale;
int dest_height = src_height * scale;
for (int y = 0; y < dest_height; y++)
{
int src_y = y / scale;
for (int x = 0; x < dest_width; x++)
{
int src_x = x / scale;
int src_index = (src_y * src_width + src_x) * 4;
int dest_index = (y * dest_width + x) * 4;
dest[dest_index + 0] = src[src_index + 0];
dest[dest_index + 1] = src[src_index + 1];
dest[dest_index + 2] = src[src_index + 2];
dest[dest_index + 3] = src[src_index + 3];
}
}
}
+328
View File
@@ -0,0 +1,328 @@
#pragma once
#include <stdlib.h>
#include <string.h>
#include "pix.cpp"
const unsigned long NO_PARENT = (unsigned long)-1;
void fill(unsigned char *map,
int w,
int x1,
int x2,
int y1,
int y2,
long color)
{
for (int x = x1; x <= x2; x++)
{
for (int y = y1; y <= y2; y++)
{
setPixelColor(map, w, x, y, color);
}
}
}
struct FloodPoint
{
unsigned int x;
unsigned int y;
};
struct FloodStack
{
FloodPoint *items;
unsigned long len;
unsigned long cap;
};
static void flood_stack_push(FloodStack *stack, unsigned int x, unsigned int y)
{
if (stack->len == stack->cap)
{
stack->cap = stack->cap == 0 ? 256 : stack->cap * 2;
stack->items = (FloodPoint *)realloc(stack->items, stack->cap * sizeof(FloodPoint));
}
stack->items[stack->len++] = FloodPoint{x, y};
}
static FloodPoint flood_stack_pop(FloodStack *stack)
{
return stack->items[--stack->len];
}
void floodFill(unsigned char *map,
uint width,
uint height,
uint initialX,
uint initialY,
unsigned long fromColor,
unsigned long toColor)
{
if (initialX >= width || initialY >= height)
{
return;
}
unsigned char *visited = (unsigned char *)calloc(width * height, 1);
FloodStack stack = {};
flood_stack_push(&stack, initialX, initialY);
visited[getPos(width, 1, initialX, initialY)] = true;
while (stack.len > 0)
{
FloodPoint pos = flood_stack_pop(&stack);
const int x = pos.x;
const int y = pos.y;
setPixelColor(map, width, x, y, toColor);
const int next[4][2] = {
{x - 1, y},
{x + 1, y},
{x, y - 1},
{x, y + 1},
};
for (int i = 0; i < 4; i++)
{
int nx = next[i][0];
int ny = next[i][1];
if (nx < 0 || ny < 0 || (uint)nx >= width || (uint)ny >= height)
{
continue;
}
unsigned long p = getPos(width, 1, nx, ny);
if (visited[p])
{
continue;
}
unsigned long nc = getPixelColor(map, p * 3);
if (nc != fromColor || nc == toColor)
{
continue;
}
visited[p] = true;
flood_stack_push(&stack, nx, ny);
}
}
free(stack.items);
free(visited);
}
struct SearchNode
{
int x;
int y;
unsigned long parent;
unsigned long order;
};
struct NodeHeap
{
SearchNode *items;
unsigned long len;
unsigned long cap;
int targetY;
};
static int node_priority(const NodeHeap *heap, const SearchNode *node)
{
int dy = node->y - heap->targetY;
return dy < 0 ? -dy : dy;
}
static bool node_less(const NodeHeap *heap, const SearchNode *a, const SearchNode *b)
{
int ap = node_priority(heap, a);
int bp = node_priority(heap, b);
if (ap != bp)
{
return ap < bp;
}
return a->order < b->order;
}
static void heap_swap(SearchNode *a, SearchNode *b)
{
SearchNode tmp = *a;
*a = *b;
*b = tmp;
}
static void heap_push(NodeHeap *heap, SearchNode node)
{
if (heap->len == heap->cap)
{
heap->cap = heap->cap == 0 ? 256 : heap->cap * 2;
heap->items = (SearchNode *)realloc(heap->items, heap->cap * sizeof(SearchNode));
}
unsigned long i = heap->len++;
heap->items[i] = node;
while (i > 0)
{
unsigned long parent = (i - 1) / 2;
if (!node_less(heap, &heap->items[i], &heap->items[parent]))
{
break;
}
heap_swap(&heap->items[i], &heap->items[parent]);
i = parent;
}
}
static SearchNode heap_pop(NodeHeap *heap)
{
SearchNode result = heap->items[0];
heap->items[0] = heap->items[--heap->len];
unsigned long i = 0;
while (true)
{
unsigned long left = i * 2 + 1;
unsigned long right = left + 1;
unsigned long best = i;
if (left < heap->len && node_less(heap, &heap->items[left], &heap->items[best]))
{
best = left;
}
if (right < heap->len && node_less(heap, &heap->items[right], &heap->items[best]))
{
best = right;
}
if (best == i)
{
break;
}
heap_swap(&heap->items[i], &heap->items[best]);
i = best;
}
return result;
}
class Search
{
public:
unsigned char *map;
int width;
int height;
int targetX;
int targetY;
char *visited;
unsigned long *parents;
unsigned long endPos = NO_PARENT;
NodeHeap heap = {};
unsigned long order = 0;
Search(unsigned char _map[], int _width, int _height, int _targetX, int _targetY, unsigned long *_parents)
{
map = _map;
width = _width;
height = _height;
targetX = _targetX;
targetY = _targetY;
parents = _parents;
visited = (char *)calloc(width * height, 1);
heap.targetY = targetY;
}
~Search()
{
free(heap.items);
free(visited);
}
bool findPath(int x, int y)
{
unsigned long start = pos(x, y);
setVisited(x, y);
parents[start] = NO_PARENT;
heap_push(&heap, SearchNode{x, y, NO_PARENT, order++});
while (heap.len > 0)
{
SearchNode n = heap_pop(&heap);
if (atTarget(&n))
{
endPos = pos(n.x, n.y);
return true;
}
tryNext(n.x, n.y - 1, &n);
tryNext(n.x - 1, n.y, &n);
tryNext(n.x + 1, n.y, &n);
tryNext(n.x, n.y + 1, &n);
}
return false;
}
unsigned long pos(int x, int y) const
{
return getPos(width, 1, x, y);
}
private:
bool tryNext(int x, int y, SearchNode *n)
{
if (!valid(x, y))
{
return false;
}
if (isVisited(x, y))
{
return false;
}
if (!traversable(x, y))
{
return false;
}
unsigned long p = pos(x, y);
unsigned long parent = pos(n->x, n->y);
setVisited(x, y);
parents[p] = parent;
heap_push(&heap, SearchNode{x, y, parent, order++});
return true;
}
void setVisited(int x, int y)
{
unsigned long p = pos(x, y);
setPixelColor(map, p * 3, COLOR_PURPLE);
visited[p] = true;
}
bool isVisited(int x, int y)
{
return visited[pos(x, y)];
}
bool valid(int x, int y) const
{
return x >= 0 && y >= 0 && x < width && y < height;
}
bool traversable(int x, int y) const
{
unsigned long p = pos(x, y);
long c = getPixelColor(map, p * 3);
return c == COLOR_BLACK || c == COLOR_COFFEE || c == COLOR_FROZEN_VAULT_MINT || c == COLOR_HELL_GREEN;
}
bool atTarget(SearchNode *n) const
{
return targetY == n->y;
}
};
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+940
View File
@@ -0,0 +1,940 @@
#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);
}