--- 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 = 68 }, [tile_tree] = { replacement = tile_tree_stump, sfx = 11, drop = 64 }, [tile_big_mushroom] = { replacement = tile_mushroom_stump, sfx = 12, drop = 65 }, -- cactus w/ flower [tile_flowering_cactus] = { replacement = tile_cactus_stump, sfx = 12, drop = 67 }, -- cactus [tile_cactus] = { replacement = tile_cactus_stump, sfx = 12, drop = 66 } } -- 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