-
-
Save Quit/c25203e4ffc95230c33547d86d98f073 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local terrain_config = radiant.resources.load_json('stonehearth:terrain_blocks') | |
local biome_metadata = radiant.resources.load_json('stonehearth:biome:temperate') | |
local biome_config = radiant.resources.load_json(biome_metadata.generation_file) | |
function log(msg, ...) | |
_host:log('dump', 0, string.format(msg, ...)) | |
end | |
log('start dumping...') | |
local types_by_tag = {} | |
for t, d in pairs(terrain_config.block_types) do | |
types_by_tag[d.tag] = t | |
end | |
local colors_by_type = biome_config.palettes.spring | |
local f = io.open('terrain.txt', 'w') | |
-- Terrain | |
local cubes , water, entities, buildings, farms = 0, 0, 0, 0, 0 | |
log('dump terrain') | |
local terrain_component = radiant.terrain.get_terrain_component() | |
local world_bounds = terrain_component:get_bounds() | |
local tiled_region = terrain_component:get_tiles() | |
local tile_size = tiled_region:get_tile_size() | |
local index_bounds = Cube3( | |
Point3( | |
math.floor(world_bounds.min.x / tile_size.x), | |
math.floor(world_bounds.min.y / tile_size.y), | |
math.floor(world_bounds.min.z / tile_size.z) | |
), | |
Point3( | |
math.floor(world_bounds.max.x / tile_size.x), | |
math.floor(world_bounds.max.y / tile_size.y), | |
math.floor(world_bounds.max.z / tile_size.z) | |
) | |
) | |
local cubes = 0 | |
local CLUSTER_SIZE = 4 -- bigger values equals longer loading times because of optimization, but probably faster loading speed. | |
for point in index_bounds:each_point() do | |
local tile_index = math.floor(point.x / CLUSTER_SIZE) * (world_bounds.max.z - world_bounds.min.z) + math.floor(point.z / CLUSTER_SIZE) | |
local boxed_region = tiled_region:find_tile(point) | |
if boxed_region then | |
local region = boxed_region:modify(function(cursor) | |
cursor:force_optimize_by_merge('SHVR Terrain Export') | |
for cube in cursor:each_cube() do | |
cubes = cubes + 1 | |
-- cube.tag is the tag | |
local bt = types_by_tag[cube.tag] | |
local bc = colors_by_type[bt] | |
f:write(table.concat({ 'CUBE', cube.min.x, cube.min.y, cube.min.z, cube.max.x, cube.max.y, cube.max.z, bc, tile_index }, '|')) | |
f:write('\n') | |
end | |
end) | |
end | |
end | |
-- Waterasd | |
log('dump water') | |
for _, water_entity in pairs(stonehearth.hydrology:get_water_bodies()) do | |
for cube in water_entity:get_component('stonehearth:water'):get_region():get():each_cube() do | |
water = water + 1 | |
local world_pos = radiant.entities.get_world_location(water_entity) | |
local min, max = world_pos + cube.min, world_pos + cube.max | |
f:write(table.concat({ 'WATER', min.x, min.y, min.z, max.x, max.y, max.z, water }, '|')) | |
f:write('\n') | |
end | |
end | |
-- Helper function to dump buildings - we'll iterate through all entities anyway. | |
local building_component = radiant.mods.require('stonehearth.components.building.building_component') | |
-- Get STRUCTURE_TYPES. This is a local variable only available to the building component | |
-- (and certified lua users. Such as me.) | |
-- Everything related to building dumping is extremely fragile and, as such, subject to local witchcraft | |
-- laws. Please consult with your magician lawyer of your choice about the legality of this script | |
-- in your state. | |
local STRUCTURE_TYPES | |
for i = 1, 10 do | |
local name, value = debug.getupvalue(building_component._fill_in_structures_map, i) | |
if name == 'STRUCTURE_TYPES' then | |
STRUCTURE_TYPES = value | |
break | |
end | |
end | |
if not STRUCTURE_TYPES then | |
error('Could not "find" structure types') | |
end | |
local function dump_building(entity, building) | |
local components = {} | |
for _, kind in ipairs(STRUCTURE_TYPES) do | |
for id, entry in pairs(building._sv._structures[kind]) do | |
-- if not next(entry.inverse_dependencies) then | |
table.insert(components, entry.entity) | |
-- end | |
end | |
end | |
-- Ein Satz mit x: Das war wohl nix. | |
if #components == 0 then | |
return | |
end | |
buildings = buildings + 1 | |
-- This code doesn't just look like BuildingComponent:blow_up() | |
-- it _is_ BuildingComponent:blow_up(). | |
-- Minus the blowing up bit. We might split the stuff apart later too | |
-- for "aesthetical" reasons; but for now, let's not. | |
for _, entity in pairs(components) do | |
if entity:is_valid() then | |
local cp = entity:get_component('stonehearth:construction_progress') | |
if cp and entity:get_uri() ~= 'stonehearth:build:prototypes:scaffolding' and cp:get_fabricator_component() and cp:get_color_region() then | |
local fab = cp:get_fabricator_component() | |
local world_origin = radiant.entities.get_world_grid_location(entity) | |
local proj = fab:get_project() | |
-- Project region doesn't contain color info! So, just use it as a 'stencil', and intersect the construction_data's | |
-- color region with that stencil to get our color region. | |
local color_region = proj:get_component('stonehearth:construction_data'):get_color_region():get():intersect_region( | |
proj:get_component('destination'):get_region():get()) | |
for cube in color_region:each_cube() do | |
cube:translate(radiant.entities.get_world_location(entity)) | |
f:write(table.concat( | |
{ | |
'BUILDING', | |
cube.min.x, -- adjust some values because otherwise, the entities don't line up properly | |
cube.min.y, | |
cube.min.z, | |
cube.max.x, | |
cube.max.y, | |
cube.max.z, | |
string.format('#%06x', cube.tag), | |
buildings | |
}, '|')) | |
f:write('\n') | |
end | |
end | |
end | |
end | |
end | |
local errors = {} | |
local function dump_farm(entity, farmer_field) | |
local data = farmer_field._sv | |
local farmer_field_data = radiant.entities.get_component_data(entity, 'stonehearth:farmer_field') | |
local _tilled_dirt_model = farmer_field_data.tilled_dirt | |
local _furrow_dirt_model = farmer_field_data.furrow_dirt | |
local size_x = data.size.x | |
local size_y = data.size.y | |
local contents = data.contents | |
local dirt_node_array = {} | |
local world_pos = radiant.entities.get_world_location(entity) | |
for x = 1, size_x do | |
for y = 1, size_y do | |
local dirt_plot = contents[x][y] | |
if dirt_plot and dirt_plot.x ~= nil then -- need to check for nil for backward compatibility reasons | |
local model = dirt_plot.is_furrow and _furrow_dirt_model or _tilled_dirt_model | |
f:write(table.concat({ | |
'ENTITY', | |
entity:get_uri(), | |
world_pos.x + dirt_plot.x - 0.45, | |
world_pos.y + 0.05, | |
world_pos.z + dirt_plot.y - 0.55, | |
radiant.entities.get_facing(entity), | |
model, | |
0.1, -- scale is undefined, but we HEAVILY assume it's the default 0.1 | |
'', -- no collision box | |
}, '|')) | |
f:write('\n') | |
end | |
end | |
end | |
farms = farms + 1 | |
end | |
-- Entities | |
log('dump entities') | |
-- local done = {} | |
radiant.entities.for_all_children(radiant.entities.get_root_entity(), function(entity) | |
local world_pos = radiant.entities.get_world_location(entity) | |
if not world_pos then | |
return | |
end | |
local ret, err = pcall(function() | |
local uri = entity:get_uri() | |
if not uri or uri == '' then | |
return | |
end | |
-- -- It's possible that an entity is reached twice... | |
-- -- for whatever reason. | |
-- => Nope, it's not, but buildings still have ghost entities and I have no clue how to distinguish those | |
-- (short of filtering everything with "_ghost.json" in its uri) | |
-- if done[entity:get_id()] then | |
-- log('%s (%d; %s) already reached once!', tostring(entity), entity:get_id(), entity:get_uri()) | |
-- return | |
-- end | |
-- done[entity:get_id()] = true | |
local building_component = entity:get_component('stonehearth:building') | |
if building_component then | |
dump_building(entity, building_component) | |
return | |
end | |
local farmer_field = entity:get_component('stonehearth:farmer_field') | |
if farmer_field then | |
dump_farm(entity, farmer_field) | |
return | |
end | |
local render_info = entity:get_component('render_info') | |
if not render_info then | |
return | |
end | |
local variant_name = entity:get_component('render_info'):get_model_variant() | |
if variant_name == '' then | |
variant_name = 'default' | |
end | |
local layer = entity:get_component('model_variants'):get_variant(variant_name) | |
local models = {} | |
for model in layer:each_model() do | |
table.insert(models, model) | |
end | |
local mob = entity:get_component('mob') | |
local scale = render_info:get_scale() | |
local region_offset = mob:get_region_origin() | |
local model_offset = mob:get_model_origin() / scale | |
local collision_regions = {} | |
local rcs = entity:get_component('region_collision_shape') | |
if rcs then | |
local total_offset = -region_offset + model_offset | |
for cube in rcs:get_region():get():each_cube() do | |
local min, max = cube.min + total_offset, cube.max + total_offset | |
table.insert(collision_regions, table.concat({ | |
min.x, | |
min.y, | |
min.z, | |
max.x, | |
max.y, | |
max.z | |
}, '/')) | |
end | |
end | |
f:write(table.concat({ | |
'ENTITY', | |
uri .. '(' .. entity:get_id() .. ')', | |
world_pos.x - model_offset.x, -- Yes, minus, because Unity and Stonehearth had a little fallout about which hand is the better one. | |
world_pos.y + model_offset.y, | |
world_pos.z + model_offset.z, | |
radiant.entities.get_facing(entity), | |
table.concat(models, ','), | |
scale, | |
table.concat(collision_regions, ';') | |
}, '|')) | |
f:write('\n') | |
entities = entities + 1 | |
end) | |
if not ret then | |
log('CANNOT LOAD STUFF: ' .. entity:get_uri() .. ': ' .. err) | |
end | |
end) | |
log('dump complete') | |
f:close() | |
return { cubes = cubes, water = water, entities = entities, buildings = buildings, farms = farms, errors = errors } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment