-- allocation.lua - Maintains the grid node allocation table --[[ In contrast to classical mapgens, cellworld does not rely on perlin noise and "reproducable randomness". Because structures can have varying size, and it is not specified which direction is explored first, we cannot safely reconstruct the existing structures easily only from noise. Gridmine instead stores an "allocation map" along with the map, which stores which structures have been placed where. Generated structures are selected during generation on a "what fits" basis. Binary file format: 1byte version 2byte number of structureID entries [ 2byte structure id string structure name \n ] [ 6byte cellPosition 4byte indirection entry or structure entry ] EOF Position: Indirection entry: I This node is part of a structure whose origin is at x-xoff .. z-zoff Structure entry: S All entries have a length of 4 bytes. ]]-- local function int_to_bytes(i, clip) local x = i if clip then x=i+32768--clip to positive integers end local cH = math.floor(x / 256) % 256; local cL = math.floor(x ) % 256; return(string.char(cH, cL)); end local function bytes_to_int(bytes, clip) local t={string.byte(bytes,1,-1)} local n = t[1] * 256 + t[2] if clip then return n-32768 else return n end end -- quick unit test assert(bytes_to_int(int_to_bytes(42))==42) assert(bytes_to_int(int_to_bytes(-42))~=-42)-- this must not work! assert(bytes_to_int(int_to_bytes(42,true),true)==42) assert(bytes_to_int(int_to_bytes(-42,true),true)==-42) --local variables for performance local gma_structids={} local gma_entries={} local function gmaget(x,y,z) local ny=gma_entries[y] if ny then local nx=ny[x] if nx then return nx[z] end end return nil end cellworld.alloc_get_cid = gmaget local function gmaset(x,y,z,v) if not gma_entries[y] then gma_entries[y]={} end if not gma_entries[y][x] then gma_entries[y][x]={} end gma_entries[y][x][z]=v end local function format_cid(x,y,z,cid) if not cid then return "" end local indirection = string.sub(cid, 1, 1) if indirection == "I" then local ox,oy,oz = string.byte(cid, 2, 4) local icid = gmaget(x-ox, y-oy, z-oz) return "I "..ox.." "..oy.." "..oz.." -> "..x-ox.." "..y-oy.." "..z-oz.." "..format_cid(x-ox, y-oy, z-oz, icid) elseif indirection == "S" then local rotation = string.sub(cid, 2, 2) local stid_char = string.sub(cid, 3, 4) local stid = bytes_to_int(stid_char) local stna = gma_structids[stid] return "S str="..stid.." ("..(stna or "?")..")" else return "???" end end local path=minetest.get_worldpath()..DIR_DELIM.."cellworld_allocation_map" --load function cellworld.load_allocation() cellworld.log("loading structure allocation map") local file, err = io.open(path, "rb") if not file then cellworld.log("Couldn't load the allocation database: ", err or "Unknown Error") else -- read version local vers_byte = file:read(1) local version = string.byte(vers_byte) if version~=1 then error("Doesn't support allocation file of version "..version) end -- read structure ids local nstr_byte = file:read(2) local nstr = bytes_to_int(nstr_byte) for i = 1,nstr do local stid_byte = file:read(2) local stid = bytes_to_int(stid_byte) local stna = file:read("*l") cellworld.log("structure id:", stid, "->", stna) gma_structids[stid] = stna end cellworld.log("read", nstr, "structure ids.") -- read nodes. code suspiciously resembles advtrains ndb, not that I copied it :) ... local cnt=0 local hst_x=file:read(2) local hst_y=file:read(2) local hst_z=file:read(2) local cid=file:read(4) while hst_z and hst_y and hst_x and cid and #hst_z==2 and #hst_y==2 and #hst_x==2 and #cid==4 do gmaset(bytes_to_int(hst_x,true), bytes_to_int(hst_y,true), bytes_to_int(hst_z,true), cid) cnt=cnt+1 hst_x=file:read(2) hst_y=file:read(2) hst_z=file:read(2) cid=file:read(4) end cellworld.log("read", cnt, "structure allocations.") file:close() end end --save function cellworld.save_allocation() local tmppath = path local file, err = io.open(tmppath, "wb") if not file then cellworld.log("Couldn't save the allocation database: ", err or "Unknown Error") else cellworld.log("Saving allocation database...") -- write version file:write(string.char(1)) -- how many structid entries local cnt = 0 for _,_ in pairs(gma_structids) do cnt = cnt + 1 end -- write structids local nstr = 0 file:write(int_to_bytes(cnt)) for stid,stna in pairs(gma_structids) do file:write(int_to_bytes(stid)) file:write(stna) file:write("\n") nstr = nstr+1 end cellworld.log("wrote", nstr, "structure ids.") -- write entries local cnt = 0 for y, ny in pairs(gma_entries) do for x, nx in pairs(ny) do for z, cid in pairs(nx) do file:write(int_to_bytes(x,true)) file:write(int_to_bytes(y,true)) file:write(int_to_bytes(z,true)) file:write(cid) cnt=cnt+1 end end end cellworld.log("wrote", cnt, "structure allocations.") file:close() end end function cellworld.dump_allocation() for y, ny in pairs(gma_entries) do for x, nx in pairs(ny) do for z, cid in pairs(nx) do cellworld.log(x,y,z,format_cid(x,y,z,cid)) end end end end function cellworld.alloc_dump_area(lx,ly,lz,ux,uy,uz) cellworld.log("-- allocation dumping area",lx,ly,lz,"<>",ux,uy,uz) for dx,dy,dz,dcid in cellworld.alloc_get_area_iterator_raw(lx,ly,lz,ux,uy,uz) do cellworld.log(dx,dy,dz,format_cid(dx,dy,dz,dcid)) end cellworld.log("-- end of dump") end local function resolve_structure(cid, ox, oy, oz) local rotation = string.sub(cid, 2, 2) local stid_char = string.sub(cid, 3, 4) local stid = bytes_to_int(stid_char) local stna = gma_structids[stid] --cellworld.debug(" structure rot=",rotation,"stid=",stid,"stna=",stna) if stna then return stna, vector.new(ox,oy,oz), string.byte(rotation) end cellworld.log("[allocation] Warn: unknown structure name for id",stid,". Ignored") return nil end --cellworld.alloc_get_cid(x,y,z) cellworld.alloc_get_cid = gmaget -- returns: -- structure_name, relative_pos, rotation -- Note: structure rotation not calculated here yet function cellworld.alloc_get_structure_at(cell_pos) local cid = gmaget(cell_pos.x, cell_pos.y, cell_pos.z) if not cid then return nil end return cellworld.alloc_resolve_cid(cell_pos.x, cell_pos.y, cell_pos.z, cid) end -- returns: -- structure_name, relative_pos, rotation function cellworld.alloc_resolve_cid(x, y, z, cid) --cellworld.debug("alloc_resolve_cid(", x, y, z,")") local indirection = string.sub(cid, 1, 1) if indirection == "I" then local ox,oy,oz = string.byte(cid, 2, 4) --cellworld.debug("alloc_resolve_cid(", x, y, z,")") local icid = gmaget(x-ox, y-oy, z-oz) --cellworld.debug(" indirection +",ox,oy,oz,format_cid(x-ox, y-oy, z-oz,icid)) if not icid then cellworld.log("[allocation] Warn: Indirection at",vector.new(x,y,z),"has nil target. Ignored") cellworld.alloc_dump_area(x-3,y-3,z-3,x+3,y+3,z+3) return nil end indirection = string.sub(icid, 1, 1) if indirection ~= "S" then cellworld.log("[allocation] Warn: Indirection at",vector.new(x,y,z),"points to something other than S. Ignored") cellworld.alloc_dump_area(x-3,y-3,z-3,x+3,y+3,z+3) return nil end return resolve_structure(icid, ox, oy, oz) elseif indirection == "S" then return resolve_structure(cid, 0,0,0) else cellworld.log("[allocation] Warn: Unknown node type",indirection,"at gridpos",vector.new(x,y,z),". Ignored") return nil end end function cellworld.alloc_set_structure(cell_pos, structure_name, rotation, structure_extent, no_clobber) --cellworld.debug("alloc_set_structure(",cell_pos, structure_name, rotation, structure_extent, no_clobber,")") for x=0,structure_extent.x-1 do for y=0,structure_extent.y-1 do for z=0,structure_extent.z-1 do -- write indirection if x==0 and y==0 and z==0 then --continue elseif no_clobber and gmaget(cell_pos.x + x, cell_pos.y + y, cell_pos.z + z) then local ocid = gmaget(cell_pos.x + x, cell_pos.y + y, cell_pos.z + z) cellworld.log("[allocation] While trying to place",structure_name,"at grid position",cell_pos,"extent",structure_extent) cellworld.log("[allocation] Not clobbering ",vector.new(cell_pos.x + x, cell_pos.y + y, cell_pos.z + z),"which has entry",format_cid(cell_pos.x + x, cell_pos.y + y, cell_pos.z + z, ocid)) error("Failed to place structure; see log...") --continue else local indir = "I"..string.char(x)..string.char(y)..string.char(z) --cellworld.debug(" indirection ",cell_pos,"+",x,y,z) gmaset(cell_pos.x + x, cell_pos.y + y, cell_pos.z + z, indir) end end end end if no_clobber and gmaget(cell_pos.x, cell_pos.y, cell_pos.z) then return end -- write structure local stid for istid, istna in ipairs(gma_structids) do if istna==structure_name then stid = istid break end end if not stid then stid = #gma_structids + 1 cellworld.debug(" allocating new structure id ",stid,"for",structure_name) gma_structids[stid] = structure_name end local struct = "S"..string.char(rotation)..int_to_bytes(stid) --cellworld.debug(" structure ",cell_pos,"rot=",rotation,"stid=",stid) gmaset(cell_pos.x, cell_pos.y, cell_pos.z, struct) end function cellworld.alloc_has_space(cell_pos, structure_extent) for _ in cellworld.alloc_get_area_iterator(cell_pos, vector.add(cell_pos, structure_extent)) do --if any result, no space return false end return true end -- Returns an iterator function that iterates over all allocation entries in the given area, in raw format. -- Iterator returns: x, y, z, cid -- where cid can be decrypted by calling cellworld.alloc_resolve_cid(x,y,z,cid) function cellworld.alloc_get_area_iterator(gp_min, gp_max) return cellworld.alloc_get_area_iterator_raw(gp_min.x, gp_min.y, gp_min.z, gp_max.x, gp_max.y, gp_max.z) end function cellworld.alloc_get_area_iterator_raw(lx, ly, lz, ox, oy, oz) local x, y, z = lx - 1, ly - 1, lz - 1 local it_y, it_x return function() while y<=oy do if it_y then while x<=ox do if it_x then while z