259 lines
7.4 KiB
Lua
259 lines
7.4 KiB
Lua
--- Procedural generation methods, and other aspects of generating the
|
|
--- environment
|
|
|
|
function init_world()
|
|
-- constants for map tiles' sprite locations, as long as we have the
|
|
-- tokens
|
|
tile_tree = 1
|
|
tile_tree_stump = 3
|
|
tile_bush = 5
|
|
tile_empty_bush = 7
|
|
tile_cactus = 9
|
|
tile_flowering_cactus = 11
|
|
tile_cactus_stump = 13
|
|
tile_big_mushroom = 32
|
|
tile_mushroom_stump = 34
|
|
tile_pebbles = 36
|
|
tile_long_grass = 38
|
|
tile_red_flowers = 40
|
|
tile_pink_flowers = 42
|
|
tile_fairy_ring = 44
|
|
tile_rock = 46
|
|
|
|
-- Metadata for different biomes
|
|
-- tile_frequencies tuples are {frequency, sprite_index}, see index_map.md
|
|
-- frequencies by convention add up to 1000, but this is arbitrary, the
|
|
-- only concern is the space consumed by the resulting tables.
|
|
biome_data = {
|
|
meadow = {
|
|
biome_frequency = 20,
|
|
base_color = 3,
|
|
tile_frequencies = {
|
|
{525, 0},
|
|
{200, tile_red_flowers},
|
|
{200, tile_pink_flowers},
|
|
{55, tile_long_grass},
|
|
{19, tile_bush},
|
|
{1, tile_tree}
|
|
}
|
|
},
|
|
grassland = {
|
|
biome_frequency = 55,
|
|
base_color = 3,
|
|
tile_frequencies = {
|
|
{500, 0},
|
|
{345, tile_long_grass},
|
|
{4, tile_bush},
|
|
{1, tile_tree},
|
|
{100, tile_red_flowers},
|
|
{50, tile_pink_flowers}
|
|
}
|
|
},
|
|
forest = {
|
|
biome_frequency = 20,
|
|
base_color = 3,
|
|
tile_frequencies = {
|
|
{600, 0},
|
|
{200, tile_tree},
|
|
{50, tile_bush},
|
|
{50, tile_red_flowers},
|
|
{40, tile_mushrooms},
|
|
{59, tile_pink_flowers},
|
|
{1, tile_big_mushroom}
|
|
}
|
|
},
|
|
desert = {
|
|
biome_frequency = 5,
|
|
base_color = 9,
|
|
tile_frequencies = {
|
|
{800, 0},
|
|
{109, tile_pebbles},
|
|
{60, tile_cactus},
|
|
{30, tile_rock},
|
|
{1, tile_flowering_cactus}
|
|
}
|
|
}
|
|
}
|
|
|
|
-- Why is this hard-coded separately from the biome_data? glad you asked.
|
|
-- Lua's pairs() function appears not to guarantee a consistent return order,
|
|
-- and we want our world to be deterministically generated,
|
|
-- so the biome_metadata array needs to have its entries appear consistently.
|
|
biome_list = {"grassland", "meadow", "forest", "desert"}
|
|
|
|
-- this is the frequency list for the biomes themselves
|
|
biome_metadata = {}
|
|
|
|
for i=1,#biome_list do
|
|
local biome = biome_list[i]
|
|
|
|
-- add the biome's name N times to the biome metadata 'hat'
|
|
for i=1,biome_data[biome].biome_frequency do
|
|
add(biome_metadata, biome)
|
|
end
|
|
|
|
build_biome(biome, biome_data[biome])
|
|
end
|
|
|
|
object_interaction_map = {
|
|
[tile_bush] = {
|
|
replacement = tile_empty_bush,
|
|
sfx = 13,
|
|
drop = 81
|
|
},
|
|
|
|
[tile_tree] = {
|
|
replacement = tile_tree_stump,
|
|
sfx = 11,
|
|
drop = 66
|
|
},
|
|
|
|
[tile_big_mushroom] = {
|
|
replacement = tile_mushroom_stump,
|
|
sfx = 12,
|
|
drop = 64
|
|
},
|
|
|
|
[tile_flowering_cactus] = {
|
|
replacement = tile_cactus_stump,
|
|
sfx = 12,
|
|
drop = 80
|
|
},
|
|
|
|
[tile_cactus] = {
|
|
replacement = tile_cactus_stump,
|
|
sfx = 12,
|
|
drop = 65
|
|
}
|
|
}
|
|
|
|
-- initialize a ring buffer of changed positions. In use, this will be keyed
|
|
-- using strings of the form mod_buffer["x:y"], using absolute world
|
|
-- coordinates. this is to flatten the buffer so that #mod_cache is useful
|
|
-- for checking against the number of allowed entries
|
|
mod_buffer = {}
|
|
mod_queue = {}
|
|
end
|
|
|
|
-- draw the sprites for this part of the world to the screen
|
|
-- this calculates everything about the world fresh every frame,
|
|
-- but pico-8 handles this just fine!
|
|
function draw_world(start_x, start_y)
|
|
for x=0,6 do
|
|
for y=0,6 do
|
|
-- color the background for this segment
|
|
rectfill(x*16+8, y*16+8, x*16+24, y*16+24,
|
|
get_base_color(start_x-3+x, start_y-3+y))
|
|
|
|
-- now get the sprite, and render as long as it isn't 0
|
|
local sprite = get_tile(start_x-3+x, start_y-3+y)
|
|
if sprite ~= 0 then
|
|
spr(sprite, x*16+8, y*16+8, 2, 2)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- build the lookup table for a given biome, based on the biome_meta data for
|
|
-- that string.
|
|
function build_biome(biome_name, data)
|
|
local meta_frequencies = data.tile_frequencies
|
|
local tile_lookup = {}
|
|
|
|
for i=1,#meta_frequencies do
|
|
local tuple = meta_frequencies[i]
|
|
for j=1,tuple[1] do
|
|
add(tile_lookup, tuple[2])
|
|
end
|
|
end
|
|
|
|
data.tile_lookup = tile_lookup
|
|
end
|
|
|
|
-- generates a unique identifier for a position
|
|
-- uses srand() and rand() to get an unpredictable value (is this too slow?)
|
|
-- the two seed values are randomly chosen. Change them and change the world.
|
|
function generate_uid(pos_x, pos_y)
|
|
srand((pos_x + 2229) * (pos_y + 12295))
|
|
return flr(rnd(0xffff))
|
|
end
|
|
|
|
-- given an {x,y} position, calculates the aligned starting position for the
|
|
-- biome that position is in.
|
|
-- biomes are currently defined to be 128x128
|
|
function calculate_biome_pos(pos_x, pos_y)
|
|
return flr(pos_x / 128), flr(pos_y / 128)
|
|
end
|
|
|
|
-- determines which biome a given world map position should be,
|
|
-- returns the object out of the biome_data table
|
|
function get_biome_name(pos_x, pos_y)
|
|
local biome_pos_x, biome_pos_y = calculate_biome_pos(pos_x, pos_y)
|
|
local uid = generate_uid(biome_pos_x, biome_pos_y)
|
|
return biome_metadata[(uid % #biome_metadata) + 1]
|
|
end
|
|
|
|
-- get the background color for the current biome
|
|
function get_base_color(x, y)
|
|
local biome = get_biome_name(x, y)
|
|
return biome_data[biome].base_color
|
|
end
|
|
|
|
-- determine what sprite to render for a given position.
|
|
function get_tile(pos_x, pos_y)
|
|
-- lookup changes in the change buffer
|
|
local modded_sprite = mod_buffer[get_mod_key(pos_x, pos_y)]
|
|
if (modded_sprite) return modded_sprite
|
|
|
|
local biome_name = get_biome_name(pos_x, pos_y)
|
|
local biome = biome_data[biome_name]
|
|
local uid = generate_uid(pos_x, pos_y)
|
|
|
|
return biome.tile_lookup[(uid % #biome.tile_lookup) + 1]
|
|
end
|
|
|
|
|
|
---
|
|
--- mod buffer functions - these handle locations on the world map that have
|
|
--- changed from their 'default' state
|
|
---
|
|
|
|
-- x and y are global coords
|
|
function get_mod_key(x, y)
|
|
return tostr(x) .. ":" .. tostr(y)
|
|
end
|
|
|
|
-- x and y are map-local coords
|
|
function write_map_change(new_sprite, x, y, perm)
|
|
local key = get_mod_key(x, y)
|
|
mod_buffer[key] = new_sprite
|
|
|
|
-- the queue gives us a time-ordered list of items to delete.
|
|
-- anything that should persist is simply not added to the queue,
|
|
-- making it un-deletable. It also doesn't count against the maximum
|
|
-- size of the mod queue before deletion, meaning it permanently inflates
|
|
-- the size of ram.
|
|
-- obviously if we end up with a *very large number* of persistent
|
|
-- objects we can run into trouble, but this is functionally a design
|
|
-- limitation. We also only save 32 of these on exit, further limiting
|
|
-- the possible size of the 'memory leak' pool, as it were.
|
|
if not perm then
|
|
add(mod_queue, key)
|
|
end
|
|
|
|
if #mod_queue >= 8192 then
|
|
cull_mod_buffer()
|
|
end
|
|
end
|
|
|
|
function cull_mod_buffer()
|
|
-- we cull 512 entries at a time.
|
|
local count = 0
|
|
for i=1,511 do
|
|
local key = mod_queue[1]
|
|
if (not key) return -- check that we're not out of items for some reason
|
|
mod_buffer[key] = nil
|
|
del(mod_queue, key)
|
|
end
|
|
end
|