diff --git a/postinit/map/forest_map.lua b/postinit/map/forest_map.lua index ae0689907..890f1c89e 100644 --- a/postinit/map/forest_map.lua +++ b/postinit/map/forest_map.lua @@ -1,9 +1,13 @@ +local modimport = modimport GLOBAL.setfenv(1, GLOBAL) require("constants") require("mathutil") +require("map/storygen") -local separate_region = require("map/separate_region") +local Levels = require("map/levels") + +local build_map = require("map/build_map") local build_porkland = require("map/build_porkland") local startlocations = require("map/startlocations") local forest_map = require("map/forest_map") @@ -50,33 +54,14 @@ end local SKIP_GEN_CHECKS = false local _Generate = forest_map.Generate local GetTileForNoiseTile = ToolUtil.GetUpvalue(_Generate, "GetTileForNoiseTile") -local pickspawnprefab = ToolUtil.GetUpvalue(_Generate, "pickspawnprefab") -local pickspawngroup = ToolUtil.GetUpvalue(_Generate, "pickspawngroup") -local pickspawncountprefabforground = ToolUtil.GetUpvalue(_Generate, "pickspawncountprefabforground") local TranslateWorldGenChoices = ToolUtil.GetUpvalue(_Generate, "TranslateWorldGenChoices") +local SpawnFunctions = { + pickspawnprefab = ToolUtil.GetUpvalue(_Generate, "pickspawnprefab"), + pickspawngroup = ToolUtil.GetUpvalue(_Generate, "pickspawngroup"), + pickspawncountprefabforground = ToolUtil.GetUpvalue(_Generate, "pickspawncountprefabforground"), +} -forest_map.Generate = function(prefab, map_width, map_height, tasks, level, level_type, ...) - assert(level.overrides ~= nil, "Level must have overrides specified.") - - local is_porkland = level.location == "porkland" - Node.is_porkland = is_porkland - if not is_porkland then - return _Generate(prefab, map_width, map_height, tasks, level, level_type, ...) - end - - TRANSLATE_TO_PREFABS["grass"] = {"grass", "grass_tall", "grass_tall_bunche_patch"} - - WorldSim:SetPointsBarrenOrReservedTile(WORLD_TILES.ROAD) - WorldSim:SetResolveNoiseFunction(GetTileForNoiseTile) - - WorldSim:SetValidateGroundTileFunction(validate_ground_tile) - - local SpawnFunctions = { - pickspawnprefab = pickspawnprefab, - pickspawngroup = pickspawngroup, - pickspawncountprefabforground = pickspawncountprefabforground, - } - +local function GetWorldGenParams(level, level_type) local current_gen_params = deepcopy(level.overrides) local default_impassible_tile = WORLD_TILES.IMPASSABLE @@ -98,16 +83,16 @@ forest_map.Generate = function(prefab, map_width, map_height, tasks, level, leve end end - if current_gen_params.islands ~= nil then + if current_gen_params.islands ~= nil then local percent = {always = 1, never = 0, default = 0.2, sometimes = 0.1, often = 0.8} story_gen_params.island_percent = percent[current_gen_params.islands] end - if current_gen_params.branching ~= nil then + if current_gen_params.branching ~= nil then story_gen_params.branching = current_gen_params.branching end - if current_gen_params.loop ~= nil then + if current_gen_params.loop ~= nil then local loop_percent = { never = 0, default = nil, always = 1.0 } local loop_target = { never = "any", default = nil, always = "end"} story_gen_params.loop_percent = loop_percent[current_gen_params.loop] @@ -141,8 +126,16 @@ forest_map.Generate = function(prefab, map_width, map_height, tasks, level, leve end end + return current_gen_params, story_gen_params +end + +local function InitWorld(world_size, join_islands, topology_save) + WorldSim:SetPointsBarrenOrReservedTile(WORLD_TILES.ROAD) + WorldSim:SetResolveNoiseFunction(GetTileForNoiseTile) + WorldSim:SetValidateGroundTileFunction(validate_ground_tile) + local min_size = 350 - if current_gen_params.world_size ~= nil then + if world_size ~= nil then local sizes if PLATFORM == "PS4" then sizes = { @@ -152,8 +145,8 @@ forest_map.Generate = function(prefab, map_width, map_height, tasks, level, leve } else sizes = { - ["tiny"] = 250, - ["small"] = 350, + ["tiny"] = 1, + ["small"] = 50, ["medium"] = 400, ["default"] = 425, -- default == large, at the moment... ["large"] = 425, @@ -161,27 +154,21 @@ forest_map.Generate = function(prefab, map_width, map_height, tasks, level, leve } end - if sizes[current_gen_params.world_size] then - min_size = sizes[current_gen_params.world_size] - print("New size:", min_size, current_gen_params.world_size) + if sizes[world_size] then + min_size = sizes[world_size] + print("New size:", min_size, world_size) else - print("ERROR: Worldgen preset had an invalid size: "..current_gen_params.world_size) + print("ERROR: Worldgen preset had an invalid size: " .. world_size) end end - map_width = min_size - map_height = min_size + local map_width = min_size + local map_height = min_size WorldSim:SetWorldSize(map_width, map_height) - print("Creating story...") - require("map/storygen") - local topology_save, storygen = BuildPorkLandStory(tasks, story_gen_params, level) - WorldSim:WorldGen_InitializeNodePoints(); WorldSim:WorldGen_VoronoiPass(100) - print("... story created") - print("Baking map...", min_size) if not WorldSim:WorldGen_Commit() then @@ -193,63 +180,101 @@ forest_map.Generate = function(prefab, map_width, map_height, tasks, level, leve end topology_save.root:ApplyPoisonTag() - WorldSim:ConvertToTileMap(min_size) - -- WorldSim:SeparateIslands() + WorldSim:ConvertToTileMap(min_size) print("Map Baked!") map_width, map_height = WorldSim:GetWorldSize() - local join_islands = not current_gen_params.no_joining_islands - -- Note: This also generates land tiles local ground_fill = WORLD_TILES.DIRT WorldSim:ForceConnectivity(join_islands, false, ground_fill) + -- WorldSim:SeparateIslands() + + return map_width, map_height +end + +local function MakeFakeStory(story_gen_params) + local level_data = Levels.GetDataForLevelID("PORKLAND_TEST") + level_data.overrides.world_size = "medium" + local level = Level(level_data) + level:ChooseTasks() + level:ChooseSetPieces() + local tasks = level:GetTasksForLevel() + + local topology_save, storygen = BuildPorkLandStory(tasks, story_gen_params, level) + + return topology_save, storygen +end + +local function GeneratePorkland(prefab, map_width, map_height, tasks, level, level_type, ...) + TRANSLATE_TO_PREFABS["grass"] = {"grass", "grass_tall", "grass_tall_bunche_patch"} + + local current_gen_params, story_gen_params = GetWorldGenParams(level, level_type) + + local join_islands = not current_gen_params.no_joining_islands + + print("Creating story...") + local topology_save, storygen = BuildPorkLandStory(tasks, story_gen_params, level) + + print("Init world...") + map_width, map_height = InitWorld(current_gen_params.world_size, join_islands, topology_save) + + if map_width == nil or map_height == nil then + return nil + end + local entities = {} + local save = { + ents = entities, + map = { + tiles = "", + roads = {}, + topology = {}, + generated = { + densities = {}, + }, + prefab = prefab, + has_ocean = current_gen_params.has_ocean, + }, + } + + if level.id == "PORKLAND_DEFAULT" then + modimport("postinit/map/worldsim") + + build_map.RecordMap(topology_save) + + collectgarbage("collect") + WorldSim:ResetAll() + + local fake_topology_save, fake_storygen = MakeFakeStory(story_gen_params) + map_width, map_height = InitWorld("medium", join_islands, fake_topology_save) + + local result = build_map.ReBuildMap(map_width, map_height) + if not result then + print("PANIC: Failed to generate map!") + return nil + end + + save.map.topology.node_datas = WorldSim:GetNodeDatas() + + WorldSim:CreateNodeIdTileMap() + topology_save.root:SaveEncode({width = map_width, height = map_height}, save.map.topology) + else + topology_save.root:SaveEncode({width = map_width, height = map_height}, save.map.topology) + WorldSim:CreateNodeIdTileMap(save.map.topology.ids) + end -- Run Node specific functions here local nodes = topology_save.root:GetNodes(true) for _, node in pairs(nodes) do node:SetTilesViaFunction(entities, map_width, map_height) end - separate_region(nodes, 2) -- ensure at least two tiles at intervals between islands - - print("Encoding...") - - local save = {} - save.ents = {} - save.map = { - tiles = "", - topology = {}, - prefab = prefab, - has_ocean = current_gen_params.has_ocean, - } - topology_save.root:SaveEncode({width = map_width, height = map_height}, save.map.topology) - WorldSim:CreateNodeIdTileMap(save.map.topology.ids) - print("Encoding... DONE") - - -- TODO: Double check that each of the rooms has enough space (minimimum # tiles generated) - maybe countprefabs + % - -- For each item in the topology list - -- Get number of tiles for that node - -- if any are less than minumum - restart the generation - - for idx, val in ipairs(save.map.topology.nodes) do - if string.find(save.map.topology.ids[idx], "LOOP_BLANK_SUB") == nil then - local area = WorldSim:GetSiteArea(save.map.topology.ids[idx]) - if area < 8 then - print ("ERROR: Site "..save.map.topology.ids[idx].." area < 8: "..area) - if SKIP_GEN_CHECKS == false then - return nil - end - end - end - end local translated_prefabs, runtime_overrides = TranslateWorldGenChoices(current_gen_params) print("Populating voronoi...") - topology_save.root:GlobalPrePopulate(entities, map_width, map_height) topology_save.root:ConvertGround(SpawnFunctions, entities, map_width, map_height) WorldSim:ReplaceSingleNonLandTiles() @@ -258,22 +283,18 @@ forest_map.Generate = function(prefab, map_width, map_height, tasks, level, leve local replace_count = WorldSim:DetectDisconnect() --allow at most 5% of tiles to be disconnected if replace_count > math.floor(map_width * map_height * 0.05) then - print("PANIC: Too many disconnected tiles...", replace_count) + print("PANIC: Too many disconnected tiles...",replace_count) if SKIP_GEN_CHECKS == false then return nil end else - print("disconnected tiles...", replace_count) + print("disconnected tiles...",replace_count) end else print("Not checking for disconnected tiles.") end - save.map.generated = {} - save.map.generated.densities = {} - topology_save.root:PopulateVoronoi(SpawnFunctions, entities, map_width, map_height, translated_prefabs, save.map.generated.densities) - topology_save.root:GlobalPostPopulate(entities, map_width, map_height) for k, ents in pairs(entities) do @@ -291,7 +312,7 @@ forest_map.Generate = function(prefab, map_width, map_height, tasks, level, leve if translated_prefabs ~= nil then -- Filter out any etities over our overrides - for prefab, mult in pairs(translated_prefabs) do + for prefab,mult in pairs(translated_prefabs) do if type(mult) == "number" and mult < 1 and entities[prefab] ~= nil and #entities[prefab] > 0 then local new_amt = math.floor(#entities[prefab]*mult) if new_amt == 0 then @@ -308,7 +329,6 @@ forest_map.Generate = function(prefab, map_width, map_height, tasks, level, leve BunchSpawnerInit(entities, map_width, map_height) BunchSpawnerRun(WorldSim) - AncientArchivePass(entities, map_width, map_height, WorldSim) build_porkland(entities, topology_save, map_width, map_height, current_gen_params) @@ -348,13 +368,11 @@ forest_map.Generate = function(prefab, map_width, map_height, tasks, level, leve end end - save.ents = entities - save.map.tiles, save.map.tiledata, save.map.nav, save.map.adj, save.map.nodeidtilemap = WorldSim:GetEncodedMap(join_islands) save.map.world_tile_map = GetWorldTileMap() save.map.topology.overrides = deepcopy(current_gen_params) - save.map.topology.pl_worldgen_version = 1 -- Feel free to increase this version when making big changes + save.map.topology.pl_worldgen_version = 2 -- Feel free to increase this version when making big changes if save.map.topology.overrides == nil then save.map.topology.overrides = {} @@ -401,9 +419,17 @@ forest_map.Generate = function(prefab, map_width, map_height, tasks, level, leve end end - save.map.roads = {} - print("Done "..prefab.." map gen!") - return save + return save, topology_save +end + +forest_map.Generate = function(prefab, map_width, map_height, tasks, level, level_type, ...) + local is_porkland = level.location == "porkland" + Node.is_porkland = is_porkland + if not is_porkland then + return _Generate(prefab, map_width, map_height, tasks, level, level_type, ...) + end + + return GeneratePorkland(prefab, map_width, map_height, tasks, level, level_type, ...) end diff --git a/postinit/map/worldsim.lua b/postinit/map/worldsim.lua new file mode 100644 index 000000000..77f8b46ef --- /dev/null +++ b/postinit/map/worldsim.lua @@ -0,0 +1,251 @@ +GLOBAL.setfenv(1, GLOBAL) + +local bit = bit +local pairs = pairs +local math = math +local table = table + +local TILE_SCALE = TILE_SCALE +local WORLD_TILES = WORLD_TILES or { + IMPASSABLE = 1, +} + +local PLACE_MASK = PLACE_MASK or { + NORMAL = 0, + IGNORE_IMPASSABLE = 1, + IGNORE_BARREN = 2, + IGNORE_IMPASSABLE_BARREN = 3, + IGNORE_RESERVED = 4, + IGNORE_IMPASSABLE_RESERVED = 5, + IGNORE_BARREN_RESERVED = 6, + IGNORE_IMPASSABLE_BARREN_RESERVED = 7, +} + +local LAYOUT_POSITION = LAYOUT_POSITION or { + RANDOM = 0, + CENTER = 1, +} + +--- task->graph +--- room->node(cell) +---@class NodeData +---@field area number +---@field children table +---@field site table{ x: number, y: number } +---@field site_centroid table{ x: number, y: number } +---@field site_points table{ x: number[], y: number[], map: number[][] } +---@field polygon_vertexs table{ x: number[], y: number[] } +---@type table +local NodeDatas = {} + +local WorldSim__index = getmetatable(WorldSim).__index +if WorldSim__index.hooked then + return +end +WorldSim__index.hooked = true + +---@param node_id string, +---@param data NodeData +function WorldSim__index:SetNodeData(node_id, data) + assert(data.area) + assert(data.site) + assert(data.site_centroid) + assert(data.site_points) + assert(data.polygon_vertexs) + + data.site_points.map = {} + for i = 1, #data.site_points.x do + local x = data.site_points.x[i] + local y = data.site_points.y[i] + data.site_points.map[x] = data.site_points.map[x] or {} + data.site_points.map[x][y] = true + end + + NodeDatas[node_id] = data +end + +function WorldSim__index:GetNodeDatas() + return NodeDatas +end + +local _ResetAll = WorldSim__index.ResetAll +function WorldSim__index:ResetAll(...) + NodeDatas = {} + return _ResetAll(self, ...) +end + +local _GetSiteArea = WorldSim__index.GetSiteArea +---@param node_id string +---@return number +function WorldSim__index:GetSiteArea(node_id) + local node_data = NodeDatas[node_id] + if node_data then + return node_data.area + end + return _GetSiteArea(self, node_id) +end + +local _GetSite = WorldSim__index.GetSite +--- retrun room(site) center x, y +---@param node_id string +---@return number, number +function WorldSim__index:GetSite(node_id) + local node_data = NodeDatas[node_id] + if node_data then + return node_data.site.x, node_data.site.y + end + return _GetSite(self, node_id) +end + +local _GetSiteCentroid = WorldSim__index.GetSiteCentroid +---@param node_id string +---@return number, number +function WorldSim__index:GetSiteCentroid(node_id) + local node_data = NodeDatas[node_id] + if node_data then + return node_data.site_centroid.x, node_data.site_centroid.y + end + return _GetSiteCentroid(self, node_id) +end + +local _GetPointsForSite = WorldSim__index.GetPointsForSite +---@param node_id string +---@param ignore_reserved boolean +---@return number[], number[], number[] +function WorldSim__index:GetPointsForSite(node_id, ignore_reserved) + local node_data = NodeDatas[node_id] + if node_data then + local posints_x = {} + local posints_y = {} + local tiles = {} + for i = 1, #node_data.site_points.x do + local x = node_data.site_points.x[i] + local y = node_data.site_points.y[i] + if not self:IsTileReserved(x, y) or ignore_reserved then + table.insert(posints_x, x) + table.insert(posints_y, y) + table.insert(tiles, self:GetTile(x, y)) + end + end + return posints_x, posints_y, tiles + end + return _GetPointsForSite(self, node_id, ignore_reserved) +end + +local _GetSitePolygon = WorldSim__index.GetSitePolygon +--- Returns the polygon vertexs x, y of the site. +---@param node_id string +---@return number[], number[] +function WorldSim__index:GetSitePolygon(node_id) + local node_data = NodeDatas[node_id] + if node_data then + return node_data.polygon_vertexs.x, node_data.polygon_vertexs.y + end + return _GetSitePolygon(self, node_id) +end + +local _GetChildrenForSite = WorldSim__index.GetChildrenForSite +---@param node_id string +function WorldSim__index:GetChildrenForSite(node_id) + local node_data = NodeDatas[node_id] + if node_data then + return node_data.children + end + return _GetChildrenForSite(self, node_id) +end + +local _PointInSite = WorldSim__index.PointInSite +---@param node_id string +---@param x number +---@param y number +---@return boolean +function WorldSim__index:PointInSite(node_id, x, y) + local node_data = NodeDatas[node_id] + if node_data then + x = math.floor(x / TILE_SCALE) * TILE_SCALE + y = math.floor(y / TILE_SCALE) * TILE_SCALE + return node_data.site_points.map[x] and node_data.site_points.map[x][y] + end + + return _PointInSite(self, node_id, x, y) +end + +function WorldSim__index:GetSpcaePoint(start_x, start_y, size, fill_points, start_mask, fill_mask) + local start_ignore_impassable = bit.band(start_mask, PLACE_MASK.IGNORE_IMPASSABLE) == PLACE_MASK.IGNORE_IMPASSABLE + local start_ignore_barren = bit.band(start_mask, PLACE_MASK.IGNORE_BARREN) == PLACE_MASK.IGNORE_BARREN + local start_ignore_reserved = bit.band(start_mask, PLACE_MASK.IGNORE_RESERVED) == PLACE_MASK.IGNORE_RESERVED + + local ignore_impassable = bit.band(fill_mask, PLACE_MASK.IGNORE_IMPASSABLE) == PLACE_MASK.IGNORE_IMPASSABLE + local ignore_barren = bit.band(fill_mask, PLACE_MASK.IGNORE_BARREN) == PLACE_MASK.IGNORE_BARREN + local ignore_reserved = bit.band(fill_mask, PLACE_MASK.IGNORE_RESERVED) == PLACE_MASK.IGNORE_RESERVED + + local space = { x = {}, y = {} } + local start_tile = self:GetTile(start_x, start_y) + local start_reserved = self:IsTileReserved(start_x, start_y) + + if (start_tile == WORLD_TILES.IMPASSABLE and not start_ignore_impassable) + or (start_reserved and not start_ignore_reserved) then + return + end + + for i = 0, size - 1 do + local x = start_x + i + for j = 0, size - 1 do + local y = start_y + j + if (self:GetTile(x, y) == WORLD_TILES.IMPASSABLE and not ignore_impassable) + or (self:IsTileReserved(x, y) and not ignore_reserved) + then + return + end + table.insert(space.x, x) + table.insert(space.y, y) + end + end + + return space +end + +local _ReserveSpace = WorldSim__index.ReserveSpace +function WorldSim__index:ReserveSpace(node_id, size, start_mask, fill_mask, layout_position, tiles) + local node_data = NodeDatas[node_id] + if node_data then + local fill_points = node_data.site_points.map + + local spaces = {} + if layout_position == LAYOUT_POSITION.CENTER then + local start_x = math.floor(node_data.site.x / TILE_SCALE) * TILE_SCALE + local start_y = math.floor(node_data.site.y / TILE_SCALE) * TILE_SCALE + local space = self:GetSpcaePoint(start_x, start_y, size * 2, fill_points, start_mask, fill_mask) + if space then + table.insert(spaces, space) + end + else + for start_x, cols in pairs(fill_points) do + for start_y in pairs(cols) do + local space = self:GetSpcaePoint(start_x, start_y, size * 2, fill_points, start_mask, fill_mask) + if space then + table.insert(spaces, space) + end + end + end + end + + if #spaces == 0 then + return + end + + local space = spaces[math.random(1, #spaces)] + for i = 1, #space.x do + local x = space.x[i] + local y = space.y[i] + if tiles[i] ~= 0 then + self:SetTile(x, y, tiles[i]) + end + self:ReserveTile(x, y) + end + + return space.x[1], space.y[1] + end + + return _ReserveSpace(self, node_id, size, start_mask, fill_mask, layout_position, tiles) +end diff --git a/postinit/modules/map.lua b/postinit/modules/map.lua index 31f8866a3..12f040141 100644 --- a/postinit/modules/map.lua +++ b/postinit/modules/map.lua @@ -450,6 +450,30 @@ function Map:GetTile(x, y, ...) end end +local node_id_index_map +local _GetNodeIdAtPoint = Map.GetNodeIdAtPoint +function Map:GetNodeIdAtPoint(x, y, z, ...) + if not TheWorld.topology.node_datas then + return _GetNodeIdAtPoint(self, x, y, z, ...) + end + + if not node_id_index_map then + node_id_index_map = {} + for i, node in pairs(TheWorld.topology.nodes) do + node_id_index_map[TheWorld.topology.ids[i]] = i + end + end + + local coords_x, coords_y = self:GetTileCoordsAtPoint(x, y, z) + for node_id, node_data in pairs(TheWorld.topology.node_datas) do + if node_data.site_points.map[coords_x] and node_data.site_points.map[coords_x][coords_y] then + return node_id_index_map[node_id] or 0 + end + end + + return 0 +end + function Map:GetIslandTagAtPoint(x, y, z) local on_land = self:IsLandTileAtPoint(x, 0, z) if not on_land then diff --git a/scripts/components/giantgrubspawner.lua b/scripts/components/giantgrubspawner.lua index 7e3b92722..3ed1ada8f 100644 --- a/scripts/components/giantgrubspawner.lua +++ b/scripts/components/giantgrubspawner.lua @@ -35,7 +35,7 @@ local function SpawnGiantGrub() if not _anthill then _anthill = TheSim:FindFirstEntityWithTag("ant_hill_entrance") - if not _anthill then + if not _anthill or not _anthill.rooms then return end end diff --git a/scripts/main/packer.lua b/scripts/main/packer.lua new file mode 100644 index 000000000..e9062bb61 --- /dev/null +++ b/scripts/main/packer.lua @@ -0,0 +1,91 @@ +local function filter(t, callback) + local result = {} + for i, v in ipairs(t) do + if callback(v) then + table.insert(result, v) + end + end + return result +end + +local function NextMultipleOf(n, target) + local remainder = n % target + if remainder == 0 then + return n + end + return n + (target - remainder) +end + +local function BboxIntersects(bbox1, bbox2) + return not ( + bbox2.x >= bbox1.x + bbox1.width or + bbox2.x + bbox2.width <= bbox1.x or + bbox2.y + bbox2.height <= bbox1.y or + bbox2.y >= bbox1.y + bbox1.height + ) +end + +local function TryInsert(block, width, height, fit_blocks) + local align = 4 + local x = 0 + local y = 0 + + local bboxH = block.height + local bboxW = block.width + while (y + bboxH < height) do + local minY = nil + local yTestBBox = { x = 0, y = y, width = width, height = bboxH } + local tempBBoxs = filter(fit_blocks, function(fittedBlock) + return BboxIntersects(fittedBlock, yTestBBox) + end) + + while (x + bboxW <= width) do + local testBBox = { x = x, y = y, width = bboxW, height = bboxH } + local intersects = false + for _, bbox in ipairs(tempBBoxs) do + if BboxIntersects(bbox, testBBox) then + x = NextMultipleOf(bbox.x + bbox.width, align) + if minY == nil then + minY = bbox.height + bbox.y + else + minY = math.min(minY, bbox.height + bbox.y) + end + + intersects = true + break + end + end + if not intersects then + return { x = x, y = y, width = bboxW, height = bboxH } + end + end + if minY then + y = math.max(NextMultipleOf(minY, align), y + align) + else + y = y + align + end + x = 0 + end +end + +local function Pack(width, height, blocks) + local sorted_blocks = shallowcopy(blocks) + + table.sort(sorted_blocks, function(a,b) + return b.width * b.height > a.width * a.height + end) + + local fit_blocks = {} + for _, block in ipairs(sorted_blocks) do + block.insert_bbox = TryInsert(block, width, height, fit_blocks) + if not block.insert_bbox then + return + end + + table.insert(fit_blocks, block.insert_bbox) + end + + return sorted_blocks +end + +return Pack diff --git a/scripts/map/build_map.lua b/scripts/map/build_map.lua new file mode 100644 index 000000000..204fa29a2 --- /dev/null +++ b/scripts/map/build_map.lua @@ -0,0 +1,180 @@ + +local Pack = require("main/packer") + +local RegionGap = 10 + +local RegionDatas = {} + +local function RemoveNode(parent, node) + local removed = false + if parent.nodes then + for id, v in pairs(parent.nodes) do + if node == v then + parent.nodes[id] = nil + removed = true + end + if RemoveNode(v, node) then + removed = true + end + end + end + + if parent.children then + for id, child in pairs(parent.children) do + if node == child then + parent:RemoveChild(id) + removed = true + end + if RemoveNode(child, node) then + removed = true + end + end + end + + return removed +end + +local function RecordMap(topology_save) + RegionDatas = {} + + local region_datas = { + island_accademy = { node_datas = {} }, + island_royal = { node_datas = {} }, + island_pugalisk = { node_datas = {} }, + island_BFB = { node_datas = {} }, + island_ancient = { node_datas = {} }, + } + + local nodes = topology_save.root:GetNodes(true) + for _, node in pairs(nodes) do + for _, tag in pairs(node.data.tags) do + local region_data = region_datas[tag] + if region_data then + node.region = tag + local points_x, points_y, points_type = WorldSim:GetPointsForSite(node.id) + for i = 1, #points_x do + local x, y = points_x[i], points_y[i] + region_data.x_max = math.max(region_data.x_max or 0, x) + region_data.x_min = math.min(region_data.x_min or math.huge, x) + region_data.y_max = math.max(region_data.y_max or 0, y) + region_data.y_min = math.min(region_data.y_min or math.huge, y) + end + table.insert(region_data.node_datas, { + node = node, + points = { x = points_x, y = points_y, type = points_type }, + }) + break + end + end + + if not node.region then + local removed = RemoveNode(topology_save.root, node) + assert(removed, "Node not found in topology") + end + end + + for name, region_data in pairs(region_datas) do + local width = region_data.x_max - region_data.x_min + RegionGap * 2 + local height = region_data.y_max - region_data.y_min + RegionGap * 2 + local region_x = region_data.x_min - math.floor(RegionGap / 2) + local region_y = region_data.y_min - math.floor(RegionGap / 2) + + for _, node_data in ipairs(region_data.node_datas) do + local node = node_data.node + local area = WorldSim:GetSiteArea(node.id) + local site_x, site_y = WorldSim:GetSite(node.id) + local centroid_x, centroid_y = WorldSim:GetSiteCentroid(node.id) + local polygon_vertexs_x, polygon_vertexs_y = WorldSim:GetSitePolygon(node.id) + + node_data.area = area + node_data.relative_site = { x = site_x - region_x, y = site_y - region_y } + node_data.relative_centroid = { x = centroid_x - region_x, y = centroid_y - region_y } + node_data.relative_polygon_vertexs = {} + node_data.relative_points = {} + + for i = 1, #polygon_vertexs_x do + table.insert(node_data.relative_polygon_vertexs, { + x = polygon_vertexs_x[i] - region_x, + y = polygon_vertexs_y[i] - region_y, + }) + end + + for i = 1, #node_data.points.x do + table.insert(node_data.relative_points, { + x = node_data.points.x[i] - region_x, + y = node_data.points.y[i] - region_y, + tile = node_data.points.type[i], + }) + end + end + + table.insert(RegionDatas, { + name = name, + width = width, + height = height, + node_datas = region_data.node_datas, + }) + end +end + +local function ReBuildMap(map_width, map_height) + print("Rebuilding map...") + + for x = 0, map_width do + for y = 0, map_height do + WorldSim:SetTile(x, y, WORLD_TILES.IMPASSABLE) + end + end + + local packed_region_datas = Pack(map_width, map_height, RegionDatas) + if not packed_region_datas then + print("PANIC: Failed to pack regions") + return false + end + + for _, region_data in ipairs(packed_region_datas) do + local region_name = region_data.name + local insert_bbox = { + x = math.floor(region_data.insert_bbox.x), + y = math.floor(region_data.insert_bbox.y), + } + + for _, node_data in ipairs(region_data.node_datas) do + local node = node_data.node + local site_x = node_data.relative_site.x + insert_bbox.x + local site_y = node_data.relative_site.y + insert_bbox.y + local centroid_x = node_data.relative_centroid.x + insert_bbox.x + local centroid_y = node_data.relative_centroid.y + insert_bbox.y + + local data = { + area = node_data.area, + site = { x = site_x, y = site_y } , + site_centroid = { x = centroid_x, y = centroid_y }, + site_points = { x = {}, y = {} }, + polygon_vertexs = { x = {}, y = {} }, + children = WorldSim:GetChildrenForSite(node.id) + } + + for _, relative_vertex in ipairs(node_data.relative_polygon_vertexs) do + table.insert(data.polygon_vertexs.x, relative_vertex.x + insert_bbox.x) + table.insert(data.polygon_vertexs.y, relative_vertex.y + insert_bbox.y) + end + + for _, relative_point in ipairs(node_data.relative_points) do + local x = insert_bbox.x + relative_point.x + local y = insert_bbox.y + relative_point.y + WorldSim:SetTile(x, y, relative_point.tile) + table.insert(data.site_points.x, x) + table.insert(data.site_points.y, y) + end + WorldSim:SetNodeData(node.id, data) + end + end + + return true +end + +return { + RecordMap = RecordMap, + ReBuildMap = ReBuildMap, +} diff --git a/scripts/map/build_porkland.lua b/scripts/map/build_porkland.lua index 2b38b060f..d10a6ff8e 100644 --- a/scripts/map/build_porkland.lua +++ b/scripts/map/build_porkland.lua @@ -31,7 +31,7 @@ local function build_porkland(entities, topology_save, map_width, map_height, cu end -- make the city here. - entities = make_cities(entities, topology_save, WorldSim, map_width, map_height, current_gen_params) + make_cities(entities, topology_save, WorldSim, map_width, map_height, current_gen_params) -- Process tallgrass, jungle fernnoise and other prefabs that spawn groups. if entities["grass_tall_bunche_patch"] then @@ -75,7 +75,6 @@ local function build_porkland(entities, topology_save, map_width, map_height, cu -- entities["asparagus_patch"] = nil -- end - -- filter small ruins doors if entities["pig_ruins_entrance_small"] then print("FOUND", #entities["pig_ruins_entrance_small"], "RUIN SITES") @@ -164,7 +163,6 @@ local function build_porkland(entities, topology_save, map_width, map_height, cu entities["pig_ruins_plaque"] = {} end - if entities["randomrelic"] then for i, ent in ipairs(entities["randomrelic"]) do local relic = "relic_" .. tostring(math.random(1, 3)) diff --git a/scripts/map/levels/porkland.lua b/scripts/map/levels/porkland.lua index a8507deeb..fc1bb80d4 100644 --- a/scripts/map/levels/porkland.lua +++ b/scripts/map/levels/porkland.lua @@ -5,6 +5,7 @@ AddLevel(LEVELTYPE.SURVIVAL, { id = "PORKLAND_DEFAULT", name = STRINGS.UI.CUSTOMIZATIONSCREEN.PRESETLEVELS.PORKLAND, desc = STRINGS.UI.CUSTOMIZATIONSCREEN.PRESETLEVELDESC.PORKLAND, + version = 2, location = "porkland", overrides = { task_set = "porkland", @@ -37,3 +38,20 @@ AddLevel(LEVELTYPE.SURVIVAL, { background_node_range = {0, 1}, }) + +AddLevel(LEVELTYPE.SURVIVAL, { + id = "PORKLAND_TEST", + name = "PORKLAND_TEST", + desc = "PORKLAND_TEST", + version = 2, + location = "porkland", + overrides = { + world_size = "small", + start_location = "PorkLandStart", + task_set = "porkland_test", + pl_clocktype = "plateau", + keep_disconnected_tiles = true, + }, + + background_node_range = {0, 1}, +}) diff --git a/scripts/map/pl_storygen.lua b/scripts/map/pl_storygen.lua index a5d6cac3b..dda14bbbf 100644 --- a/scripts/map/pl_storygen.lua +++ b/scripts/map/pl_storygen.lua @@ -7,10 +7,19 @@ function Story:GenerateIslandFromTask(task, randomize) return nil end - local task_node = Graph(task.id, - { parent = self.rootNode, default_bg = task.room_bg, colour = task.colour, background = task.background_room, random_set_pieces = - task.random_set_pieces, set_pieces = task.set_pieces, maze_tiles = task.maze_tiles, treasures = task.treasures, random_treasures = - task.random_treasures }) + local task_node = Graph(task.id, { + parent = self.rootNode, + default_bg = task.room_bg, + colour = task.colour, + background = task.background_room, + random_set_pieces = task.random_set_pieces, + set_pieces = task.set_pieces, + maze_tiles = task.maze_tiles, + maze_tile_size = task.maze_tile_size, + room_tags = task.room_tags, + required_prefabs = task.required_prefabs + }) + task_node.substitutes = task.substitutes WorldSim:AddChild(self.rootNode.id, task.id, task.room_bg, task.colour.r, task.colour.g, task.colour.b, task.colour @@ -61,7 +70,7 @@ function Story:GenerateIslandFromTask(task, randomize) local newNode = task_node:AddNode({ id = new_room.id, data = { - type = new_room.entrance and "blocker" or new_room.type, + type = new_room.entrance and NODE_TYPE.Blocker or new_room.type, colour = new_room.colour, value = new_room.value, internal_type = new_room.internal_type, @@ -194,7 +203,7 @@ function Story:Pl_GenerateNodesFromTasks(linkFn) } } end - + start_node_data.data.name = "START" start_node_data.data.colour = { r = 0, g = 1, b = 1, a = .80 } if self.gen_params.start_setpeice ~= nil then @@ -241,7 +250,7 @@ function Story:Pl_AddBGNodes(min_count, max_count) local blocker_blank_template = self:GetRoom(self.level.blocker_blank_room_name) if blocker_blank_template == nil then blocker_blank_template = { - type = "blank", + type = NODE_TYPE.BLANK, tags = { "RoadPoison", "ForceDisconnected" }, colour = { r = 0.3, g = .8, b = .5, a = .50 }, value = self.impassible_value @@ -267,7 +276,7 @@ function Story:Pl_AddBGNodes(min_count, max_count) local newNode = task:AddNode({ id = new_room.id, data = { - type = "background", + type = NODE_TYPE.BackgroundRoom, colour = new_room.colour, value = new_room.value, internal_type = new_room.internal_type, @@ -537,8 +546,11 @@ local function RestrictNodesByKey(story, startParentNode, unusedTasks) and currentNodeEntrance.data.entrance == false then story:SeperateStoryByBlanks(lastNodeExit, currentNodeEntrance) else - story.rootNode:LockGraph(effectiveLastNode.id .. '->' .. currentNode.id, lastNodeExit, currentNodeEntrance, - { type = "none", key = story.tasks[currentNode.id].locks, node = nil }) + story.rootNode:LockGraph(effectiveLastNode.id .. '->' .. currentNode.id, lastNodeExit, currentNodeEntrance, { + type = "none", + key = story.tasks[currentNode.id].locks, + node = nil + }) end -- print_lockandkey_ex("\t\tAdding keys to keyring:") diff --git a/scripts/map/tasks/porkland.lua b/scripts/map/tasks/porkland.lua index 9dd6c1589..62eb6e90d 100644 --- a/scripts/map/tasks/porkland.lua +++ b/scripts/map/tasks/porkland.lua @@ -541,3 +541,14 @@ AddTask("Land_Divide_5", { background_room = "ForceDisconnectedRoom", colour = {r = 1, g = 1, b = 1, a = 0.3} }) + +AddTask("porkland_test", { + locks = {}, + keys_given = {}, + room_choices = { + ["Blank"] = 1, + }, + room_bg = GROUND.IMPASSABLE, + background_room = "Blank", + colour = { r = 0, g = 1, b = 0, a = 1 }, +}) diff --git a/scripts/map/tasksets/porklandset.lua b/scripts/map/tasksets/porklandset.lua index 77351bc8f..b41d14a30 100644 --- a/scripts/map/tasksets/porklandset.lua +++ b/scripts/map/tasksets/porklandset.lua @@ -78,8 +78,7 @@ AddTaskSet("porkland", { numoptionaltasks = 0, numrandom_set_pieces = 0, - set_pieces = {}, - -- set_pieces = { + set_pieces = { -- ["TeleportatoHamletBaseLayout"] = {count = 1, tasks = { -- "Deep_rainforest", -- "Deep_rainforest_2", @@ -130,6 +129,18 @@ AddTaskSet("porkland", { -- "Other_edge_of_civilization", -- }}, -- --]] - -- }, + }, required_prefabs = GLOBAL.PORKLAND_REQUIRED_PREFABS }) + +AddTaskSet("porkland_test", { + name = "porkland_test", + location = "porkland", + tasks = { + "porkland_test" + }, + numoptionaltasks = 0, + numrandom_set_pieces = 0, + set_pieces = {}, + required_prefabs = {} +}) diff --git a/scripts/prefabs/anthill.lua b/scripts/prefabs/anthill.lua index 3c9a02d7f..efe8e9a74 100644 --- a/scripts/prefabs/anthill.lua +++ b/scripts/prefabs/anthill.lua @@ -418,7 +418,9 @@ local function CreateInterior(inst) CreateRegularRooms(inst) BuildWalls(inst) RefreshDoors(inst) - TheWorld.components.interiorspawner:AddExterior(inst) + if inst.interiorID then + TheWorld.components.interiorspawner:AddExterior(inst) + end end local function GenerateMaze(inst) diff --git a/scripts/prefabs/pig_ruins_entrance.lua b/scripts/prefabs/pig_ruins_entrance.lua index b0f7ef2fc..11a5f4c76 100644 --- a/scripts/prefabs/pig_ruins_entrance.lua +++ b/scripts/prefabs/pig_ruins_entrance.lua @@ -668,8 +668,10 @@ local function OnLoadPostPass(inst, data) -- 出口的连接写在 OnLoadPostPas target_interior = exit_room_id, } inst.interiorID = exit_room_id - TheWorld.components.interiorspawner:AddDoor(inst, exterior_door_def2) - TheWorld.components.interiorspawner:AddExterior(inst) + if inst.interiorID then + TheWorld.components.interiorspawner:AddDoor(inst, exterior_door_def2) + TheWorld.components.interiorspawner:AddExterior(inst) + end end end