diff --git a/data/json/mapgen/bugs/ants.json b/data/json/mapgen/bugs/ants.json new file mode 100644 index 000000000000..8fe8c502de37 --- /dev/null +++ b/data/json/mapgen/bugs/ants.json @@ -0,0 +1,1207 @@ +[ + { + "//": "winding wall of narrow passage", + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "ant_tunnel_bounds_vn", + "object": { "mapgensize": [ 2, 2 ], "rows": [ + ". ", + ". " + ], "terrain": { ".": "t_dirt_underground" } } + }, + { + "//": "horizontal narrow passage", + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "ant_narrow_passage_h", + "object": { + "mapgensize": [ 4, 4 ], + "rows": [ + "####", + "####", + "####", + "####" + ], + "terrain": { "#": "t_rock" }, + "place_nested": [ + { "chunks": [ "ant_tunnel_bounds_vn" ], "x": 0, "y": [ 0, 1 ] }, + { "chunks": [ "ant_tunnel_bounds_vn" ], "x": 0, "y": [ 1, 2 ] }, + { "chunks": [ "ant_tunnel_bounds_vn" ], "x": 1, "y": [ 0, 1 ] }, + { "chunks": [ "ant_tunnel_bounds_vn" ], "x": 1, "y": [ 1, 2 ] }, + { "chunks": [ "ant_tunnel_bounds_vn" ], "x": 2, "y": [ 0, 1 ] }, + { "chunks": [ "ant_tunnel_bounds_vn" ], "x": 2, "y": [ 1, 2 ] }, + { "chunks": [ "ant_tunnel_bounds_vn" ], "x": 3, "y": [ 0, 1 ] }, + { "chunks": [ "ant_tunnel_bounds_vn" ], "x": 3, "y": [ 1, 2 ] } + ] + } + }, + { + "//": "winding wall of narrow passage", + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "ant_tunnel_bounds_hn", + "object": { "mapgensize": [ 2, 2 ], "rows": [ + "..", + " " + ], "terrain": { ".": "t_dirt_underground" } } + }, + { + "//": "vertical narrow passage", + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "ant_narrow_passage_v", + "object": { + "mapgensize": [ 4, 4 ], + "rows": [ + "####", + "####", + "####", + "####" + ], + "terrain": { "#": "t_rock" }, + "place_nested": [ + { "chunks": [ "ant_tunnel_bounds_hn" ], "x": [ 0, 1 ], "y": 0 }, + { "chunks": [ "ant_tunnel_bounds_hn" ], "x": [ 1, 2 ], "y": 0 }, + { "chunks": [ "ant_tunnel_bounds_hn" ], "x": [ 0, 1 ], "y": 1 }, + { "chunks": [ "ant_tunnel_bounds_hn" ], "x": [ 1, 2 ], "y": 1 }, + { "chunks": [ "ant_tunnel_bounds_hn" ], "x": [ 0, 1 ], "y": 2 }, + { "chunks": [ "ant_tunnel_bounds_hn" ], "x": [ 1, 2 ], "y": 2 }, + { "chunks": [ "ant_tunnel_bounds_hn" ], "x": [ 0, 1 ], "y": 3 }, + { "chunks": [ "ant_tunnel_bounds_hn" ], "x": [ 1, 2 ], "y": 3 } + ] + } + }, + { + "type": "mapgen", + "method": "json", + "om_terrain": "anthill", + "object": { + "rows": [ + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ------ ", + " -++++- ", + " -+<<+- ", + " -+<<+- ", + " -++++- ", + " ------ ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " " + ], + "terrain": { + " ": [ [ "t_region_groundcover_barren", 5 ], [ "t_region_groundcover", 3 ], [ "t_region_shrub", 1 ] ], + "<": "t_slope_down", + "+": "t_dirtmound", + "-": [ "t_dirtmound", "t_region_groundcover_barren" ] + } + } + }, + { + "//": "slopes up to anthill entrance to be used where appropriate", + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "ant_up_to_surface", + "object": { "mapgensize": [ 2, 2 ], "rows": [ + ">>", + ">>" + ], "terrain": { ">": "t_slope_up" } } + }, + { + "//": "walls of ant tunnel that can shift from side to side to make the tunnel wind", + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "ant_tunnel_bounds_h", + "object": { + "mapgensize": [ 14, 14 ], + "rows": [ + "#### ####", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " " + ], + "terrain": { "#": "t_rock" } + } + }, + { + "//": "walls of ant tunnel that can shift from side to side to make the tunnel wind", + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "ant_tunnel_bounds_h1", + "object": { + "mapgensize": [ 14, 14 ], + "rows": [ + "#### ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " " + ], + "terrain": { "#": "t_rock" } + } + }, + { + "//": "walls of ant tunnel that can shift from side to side to make the tunnel wind", + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "ant_tunnel_bounds_v", + "object": { + "mapgensize": [ 14, 14 ], + "rows": [ + "# ", + "# ", + "# ", + "# ", + " ", + " ", + " ", + " ", + " ", + " ", + "# ", + "# ", + "# ", + "# " + ], + "terrain": { "#": "t_rock" } + } + }, + { + "//": "walls of ant tunnel that can shift from side to side to make the tunnel wind", + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "ant_tunnel_bounds_v1", + "object": { + "mapgensize": [ 14, 14 ], + "rows": [ + "# ", + "# ", + "# ", + "# ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " " + ], + "terrain": { "#": "t_rock" } + } + }, + { + "//": "walls of ant tunnel that can shift from side to side to make the tunnel wind", + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "ant_tunnel_bounds_v2", + "object": { + "mapgensize": [ 14, 14 ], + "rows": [ + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + "# ", + "# ", + "# ", + "# " + ], + "terrain": { "#": "t_rock" } + } + }, + { + "type": "mapgen", + "method": "json", + "om_terrain": "ants_straight", + "object": { + "fill_ter": "t_dirt_underground", + "rows": [ + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######" + ], + "terrain": { "#": "t_rock" }, + "place_nested": [ + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 0 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 1 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 2 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 3 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 4 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 5 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 6 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 7 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 8 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 9 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 10 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 11 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 12 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 13 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 14 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 15 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 16 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 17 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 18 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 19 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 20 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 21 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 22 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 23 }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 0, "y": 10, "neighbors": { "west": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 3, "y": 10, "neighbors": { "west": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 6, "y": 10, "neighbors": { "west": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 14, "y": 10, "neighbors": { "east": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 17, "y": 10, "neighbors": { "east": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 20, "y": 10, "neighbors": { "east": [ "lab" ] } } + ] + } + }, + { + "type": "mapgen", + "method": "json", + "om_terrain": "ants_end", + "object": { + "fill_ter": "t_dirt_underground", + "rows": [ + "########################", + "########################", + "########################", + "########################", + "########################", + "########################", + "########################", + "########################", + "########################", + "########################", + "########################", + "########################", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######" + ], + "terrain": { "#": "t_rock" }, + "place_nested": [ + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 12 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 13 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 14 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 15 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 16 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 17 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 18 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 19 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 20 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 21 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 22 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 23 }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 0, "y": 10, "neighbors": { "west": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 3, "y": 10, "neighbors": { "west": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 7, "y": 11, "neighbors": { "west": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 14, "y": 11, "neighbors": { "east": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 17, "y": 10, "neighbors": { "east": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 20, "y": 10, "neighbors": { "east": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_v" ], "x": 10, "y": 0, "neighbors": { "north": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_v" ], "x": 10, "y": 4, "neighbors": { "north": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_v" ], "x": 10, "y": 8, "neighbors": { "north": [ "lab" ] } } + ] + } + }, + { + "type": "mapgen", + "method": "json", + "om_terrain": "ants_curved", + "object": { + "fill_ter": "t_dirt_underground", + "rows": [ + "########################", + "########################", + "########################", + "########################", + "########################", + "########################", + "########################", + "####### ", + "####### ", + "####### ", + "####### ", + "####### ", + "####### ", + "####### ", + "####### ", + "####### ", + "####### ", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######" + ], + "terrain": { "#": "t_rock" }, + "place_nested": [ + { "chunks": [ "ant_tunnel_bounds_h1" ], "x": [ 4, 6 ], "y": 7 }, + { "chunks": [ "ant_tunnel_bounds_h1" ], "x": [ 4, 6 ], "y": 8 }, + { "chunks": [ "ant_tunnel_bounds_h1" ], "x": [ 4, 6 ], "y": 9 }, + { "chunks": [ "ant_tunnel_bounds_h1" ], "x": [ 4, 6 ], "y": 10 }, + { "chunks": [ "ant_tunnel_bounds_h1" ], "x": [ 4, 6 ], "y": 11 }, + { "chunks": [ "ant_tunnel_bounds_h1" ], "x": [ 4, 6 ], "y": 12 }, + { "chunks": [ "ant_tunnel_bounds_h1" ], "x": [ 4, 6 ], "y": 13 }, + { "chunks": [ "ant_tunnel_bounds_h1" ], "x": [ 4, 6 ], "y": 14 }, + { "chunks": [ "ant_tunnel_bounds_h1" ], "x": [ 4, 6 ], "y": 15 }, + { "chunks": [ "ant_tunnel_bounds_h1" ], "x": [ 4, 6 ], "y": 16 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 17 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 18 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 19 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 20 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 21 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 22 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 23 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 7 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 8 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 9 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 10 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 11 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 12 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 13 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 14 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 15 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 16 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 17 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 18 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 19 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 20 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 21 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 22 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 23 }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 0, "y": 10, "neighbors": { "west": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 3, "y": 10, "neighbors": { "west": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 6, "y": 10, "neighbors": { "west": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_v" ], "x": 10, "y": 0, "neighbors": { "north": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_v" ], "x": 10, "y": 3, "neighbors": { "north": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_v" ], "x": 10, "y": 6, "neighbors": { "north": [ "lab" ] } } + ] + } + }, + { + "type": "mapgen", + "method": "json", + "om_terrain": "ants_tee", + "object": { + "fill_ter": "t_dirt_underground", + "rows": [ + "########################", + "########################", + "########################", + "########################", + "########################", + "########################", + "########################", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######" + ], + "terrain": { "#": "t_rock" }, + "place_nested": [ + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 17 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 18 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 19 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 20 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 21 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 22 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 23 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 0 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 1 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 2 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 3 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 4 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 5 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 6 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 7 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 8 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 9 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 10 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 11 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 12 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 13 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 14 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 15 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 16 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 17 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 18 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 19 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 20 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 21 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 22 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 23 }, + { "chunks": [ "ant_narrow_passage_v" ], "x": 10, "y": 0, "neighbors": { "north": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_v" ], "x": 10, "y": 3, "neighbors": { "north": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_v" ], "x": 10, "y": 6, "neighbors": { "north": [ "lab" ] } } + ] + } + }, + { + "type": "mapgen", + "method": "json", + "om_terrain": "ants_four_way", + "object": { + "fill_ter": "t_dirt_underground", + "rows": [ + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######" + ], + "terrain": { "#": "t_rock" }, + "place_nested": [ + { "chunks": [ "ant_up_to_surface" ], "joins": { "above": "tunnel_to_surface" }, "x": 11, "y": 11 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 0 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 1 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 2 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 3 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 4 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 5 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 6 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 17 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 18 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 19 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 20 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 21 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 22 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 23 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 0 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 1 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 2 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 3 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 4 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 5 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 6 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 17 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 18 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 19 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 20 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 21 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 22 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 23 } + ] + } + }, + { + "type": "mapgen", + "method": "json", + "om_terrain": "ants_food", + "object": { + "fill_ter": "t_dirt_underground", + "rows": [ + "####### #######", + "####### #######", + "####### #######", + "###### #######", + "####### #######", + "###### ######", + "###### #####", + "##### ####", + "#### #####", + "##### #####", + "#### ####", + "#### ####", + "### ###", + "### ####", + "#### ######", + "####### #####", + "######### ####", + "######## ######", + "####### ######", + "######## #####", + "########## ########", + "############ ##########", + "########################", + "########################" + ], + "terrain": { "#": "t_rock" }, + "place_nested": [ + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 0 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 1 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 2 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 3 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 4 }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 0, "y": 10, "neighbors": { "west": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 20, "y": 10, "neighbors": { "east": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_v" ], "x": 10, "y": 20, "neighbors": { "south": [ "lab" ] } } + ], + "items": { " ": { "item": "ant_food", "chance": 4 } } + } + }, + { + "type": "mapgen", + "method": "json", + "om_terrain": "ants_larvae", + "object": { + "fill_ter": "t_dirt_underground", + "rows": [ + "####### #######", + "####### #######", + "####### #######", + "###### #######", + "####### #######", + "###### ######", + "###### #####", + "##### ####", + "#### #####", + "##### #####", + "#### ####", + "#### ####", + "### ###", + "### ####", + "#### ######", + "####### #####", + "######### ####", + "######## ######", + "####### ######", + "######## #####", + "########## ########", + "############ ##########", + "########################", + "########################" + ], + "terrain": { "#": "t_rock" }, + "place_nested": [ + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 0 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 1 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 2 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 3 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 4 }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 0, "y": 10, "neighbors": { "west": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 20, "y": 10, "neighbors": { "east": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_v" ], "x": 10, "y": 20, "neighbors": { "south": [ "lab" ] } } + ], + "place_monster": [ { "monster": "mon_ant_larva", "x": [ 6, 17 ], "y": [ 6, 22 ], "repeat": 10 } ] + } + }, + { + "type": "mapgen", + "method": "json", + "om_terrain": "ants_queen", + "object": { + "fill_ter": "t_dirt_underground", + "rows": [ + "####### #######", + "####### #######", + "####### #######", + "###### #######", + "####### #######", + "###### ######", + "###### #####", + "##### ####", + "#### #####", + "##### #####", + "#### ####", + "#### ####", + "### ###", + "### ####", + "#### ######", + "####### #####", + "######### ####", + "######## ######", + "####### ######", + "######## #####", + "########## ########", + "############ ##########", + "########################", + "########################" + ], + "terrain": { "#": "t_rock" }, + "place_nested": [ + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 0 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 1 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 2 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 3 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 4 }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 0, "y": 10, "neighbors": { "west": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 20, "y": 10, "neighbors": { "east": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_v" ], "x": 10, "y": 20, "neighbors": { "south": [ "lab" ] } } + ], + "place_monster": [ { "monster": "mon_ant_queen", "x": [ 6, 17 ], "y": [ 6, 22 ] } ] + } + }, + { + "type": "mapgen", + "method": "json", + "om_terrain": "acid_ants_straight", + "object": { + "fill_ter": "t_dirt_underground", + "rows": [ + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######" + ], + "terrain": { "#": "t_rock" }, + "place_nested": [ + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 0 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 1 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 2 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 3 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 4 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 5 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 6 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 7 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 8 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 9 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 10 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 11 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 12 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 13 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 14 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 15 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 16 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 17 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 18 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 19 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 20 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 21 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 22 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 23 }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 0, "y": 10, "neighbors": { "west": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 3, "y": 10, "neighbors": { "west": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 6, "y": 10, "neighbors": { "west": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 14, "y": 10, "neighbors": { "east": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 17, "y": 10, "neighbors": { "east": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 20, "y": 10, "neighbors": { "east": [ "lab" ] } } + ] + } + }, + { + "type": "mapgen", + "method": "json", + "om_terrain": "acid_ants_end", + "object": { + "fill_ter": "t_dirt_underground", + "rows": [ + "########################", + "########################", + "########################", + "########################", + "########################", + "########################", + "########################", + "########################", + "########################", + "########################", + "########################", + "########################", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######" + ], + "terrain": { "#": "t_rock" }, + "place_nested": [ + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 12 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 13 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 14 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 15 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 16 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 17 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 18 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 19 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 20 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 21 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 22 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 23 }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 0, "y": 10, "neighbors": { "west": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 3, "y": 10, "neighbors": { "west": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 6, "y": 11, "neighbors": { "west": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 14, "y": 11, "neighbors": { "east": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 17, "y": 10, "neighbors": { "east": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 20, "y": 10, "neighbors": { "east": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_v" ], "x": 10, "y": 0, "neighbors": { "north": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_v" ], "x": 10, "y": 4, "neighbors": { "north": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_v" ], "x": 10, "y": 8, "neighbors": { "north": [ "lab" ] } } + ] + } + }, + { + "type": "mapgen", + "method": "json", + "om_terrain": "acid_ants_curved", + "object": { + "fill_ter": "t_dirt_underground", + "rows": [ + "########################", + "########################", + "########################", + "########################", + "########################", + "########################", + "########################", + "####### ", + "####### ", + "####### ", + "####### ", + "####### ", + "####### ", + "####### ", + "####### ", + "####### ", + "####### ", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######" + ], + "terrain": { "#": "t_rock" }, + "place_nested": [ + { "chunks": [ "ant_tunnel_bounds_h1" ], "x": [ 4, 6 ], "y": 7 }, + { "chunks": [ "ant_tunnel_bounds_h1" ], "x": [ 4, 6 ], "y": 8 }, + { "chunks": [ "ant_tunnel_bounds_h1" ], "x": [ 4, 6 ], "y": 9 }, + { "chunks": [ "ant_tunnel_bounds_h1" ], "x": [ 4, 6 ], "y": 10 }, + { "chunks": [ "ant_tunnel_bounds_h1" ], "x": [ 4, 6 ], "y": 11 }, + { "chunks": [ "ant_tunnel_bounds_h1" ], "x": [ 4, 6 ], "y": 12 }, + { "chunks": [ "ant_tunnel_bounds_h1" ], "x": [ 4, 6 ], "y": 13 }, + { "chunks": [ "ant_tunnel_bounds_h1" ], "x": [ 4, 6 ], "y": 14 }, + { "chunks": [ "ant_tunnel_bounds_h1" ], "x": [ 4, 6 ], "y": 15 }, + { "chunks": [ "ant_tunnel_bounds_h1" ], "x": [ 4, 6 ], "y": 16 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 17 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 18 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 19 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 20 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 21 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 22 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 23 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 7 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 8 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 9 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 10 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 11 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 12 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 13 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 14 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 15 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 16 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 17 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 18 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 19 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 20 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 21 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 22 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 23 }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 0, "y": 10, "neighbors": { "west": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 3, "y": 10, "neighbors": { "west": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 6, "y": 10, "neighbors": { "west": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_v" ], "x": 10, "y": 0, "neighbors": { "north": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_v" ], "x": 10, "y": 3, "neighbors": { "north": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_v" ], "x": 10, "y": 6, "neighbors": { "north": [ "lab" ] } } + ] + } + }, + { + "type": "mapgen", + "method": "json", + "om_terrain": "acid_ants_tee", + "object": { + "fill_ter": "t_dirt_underground", + "rows": [ + "########################", + "########################", + "########################", + "########################", + "########################", + "########################", + "########################", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######" + ], + "terrain": { "#": "t_rock" }, + "place_nested": [ + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 17 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 18 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 19 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 20 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 21 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 22 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 23 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 0 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 1 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 2 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 3 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 4 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 5 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 6 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 7 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 8 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 9 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 10 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 11 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 12 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 13 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 14 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 15 }, + { "chunks": [ "ant_tunnel_bounds_v1" ], "y": [ 4, 6 ], "x": 16 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 17 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 18 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 19 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 20 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 21 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 22 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 23 }, + { "chunks": [ "ant_narrow_passage_v" ], "x": 10, "y": 0, "neighbors": { "north": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_v" ], "x": 10, "y": 3, "neighbors": { "north": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_v" ], "x": 10, "y": 6, "neighbors": { "north": [ "lab" ] } } + ] + } + }, + { + "type": "mapgen", + "method": "json", + "om_terrain": "acid_ants_four_way", + "object": { + "fill_ter": "t_dirt_underground", + "rows": [ + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######", + "####### #######" + ], + "terrain": { "#": "t_rock" }, + "place_nested": [ + { "chunks": [ "ant_up_to_surface" ], "joins": { "above": "tunnel_to_surface" }, "x": 11, "y": 11 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 0 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 1 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 2 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 3 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 4 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 5 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 6 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 17 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 18 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 19 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 20 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 21 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 22 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 23 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 0 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 1 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 2 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 3 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 4 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 5 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 6 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 17 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 18 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 19 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 20 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 21 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 22 }, + { "chunks": [ "ant_tunnel_bounds_v" ], "y": [ 4, 6 ], "x": 23 } + ] + } + }, + { + "type": "mapgen", + "method": "json", + "om_terrain": "acid_ants_food", + "object": { + "fill_ter": "t_dirt_underground", + "rows": [ + "####### #######", + "####### #######", + "####### #######", + "###### #######", + "####### #######", + "###### ######", + "###### #####", + "##### ####", + "#### #####", + "##### #####", + "#### ####", + "#### ####", + "### ###", + "### ####", + "#### ######", + "####### #####", + "######### ####", + "######## ######", + "####### ######", + "######## #####", + "########## ########", + "############ ##########", + "########################", + "########################" + ], + "terrain": { "#": "t_rock" }, + "place_nested": [ + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 0 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 1 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 2 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 3 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 4 }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 0, "y": 10, "neighbors": { "west": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 20, "y": 10, "neighbors": { "east": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_v" ], "x": 10, "y": 20, "neighbors": { "south": [ "lab" ] } } + ], + "items": { " ": { "item": "ant_food", "chance": 4 } } + } + }, + { + "type": "mapgen", + "method": "json", + "om_terrain": "acid_ants_larvae", + "object": { + "fill_ter": "t_dirt_underground", + "rows": [ + "####### #######", + "####### #######", + "####### #######", + "###### #######", + "####### #######", + "###### ######", + "###### #####", + "##### ####", + "#### #####", + "##### #####", + "#### ####", + "#### ####", + "### ###", + "### ####", + "#### ######", + "####### #####", + "######### ####", + "######## ######", + "####### ######", + "######## #####", + "########## ########", + "############ ##########", + "########################", + "########################" + ], + "terrain": { "#": "t_rock" }, + "place_nested": [ + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 0 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 1 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 2 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 3 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 4 }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 0, "y": 10, "neighbors": { "west": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 20, "y": 10, "neighbors": { "east": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_v" ], "x": 10, "y": 20, "neighbors": { "south": [ "lab" ] } } + ], + "place_monster": [ { "monster": "mon_ant_acid_larva", "x": [ 6, 17 ], "y": [ 6, 22 ], "repeat": 10 } ] + } + }, + { + "type": "mapgen", + "method": "json", + "om_terrain": "acid_ants_queen", + "object": { + "fill_ter": "t_dirt_underground", + "rows": [ + "####### #######", + "####### #######", + "####### #######", + "###### #######", + "####### #######", + "###### ######", + "###### #####", + "##### ####", + "#### #####", + "##### #####", + "#### ####", + "#### ####", + "### ###", + "### ####", + "#### ######", + "####### #####", + "######### ####", + "######## ######", + "####### ######", + "######## #####", + "########## ########", + "############ ##########", + "########################", + "########################" + ], + "terrain": { "#": "t_rock" }, + "place_nested": [ + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 0 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 1 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 2 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 3 }, + { "chunks": [ "ant_tunnel_bounds_h" ], "x": [ 4, 6 ], "y": 4 }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 0, "y": 10, "neighbors": { "west": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_h" ], "x": 20, "y": 10, "neighbors": { "east": [ "lab" ] } }, + { "chunks": [ "ant_narrow_passage_v" ], "x": 10, "y": 20, "neighbors": { "south": [ "lab" ] } } + ], + "place_monster": [ { "monster": "mon_ant_acid_queen", "x": [ 6, 17 ], "y": [ 6, 22 ] } ] + } + } +] diff --git a/data/json/mapgen/lab/lab_trains.json b/data/json/mapgen/lab/lab_trains.json index 64df9439a644..55e4713e51ac 100644 --- a/data/json/mapgen/lab/lab_trains.json +++ b/data/json/mapgen/lab/lab_trains.json @@ -53,10 +53,10 @@ "p": "t_sewage_pipe" }, "place_nested": [ - { "chunks": [ "lab_train_subway_east" ], "x": 0, "y": 0, "neighbors": { "east": [ "subway" ] } }, - { "chunks": [ "lab_train_subway_west" ], "x": 0, "y": 0, "neighbors": { "west": [ "subway" ] } }, - { "chunks": [ "lab_train_subway_north" ], "x": 0, "y": 0, "neighbors": { "north": [ "subway" ] } }, - { "chunks": [ "lab_train_subway_south" ], "x": 0, "y": 0, "neighbors": { "south": [ "subway" ] } }, + { "chunks": [ "lab_train_subway_east" ], "x": 0, "y": 0, "connections": { "east": [ "subway_tunnel" ] } }, + { "chunks": [ "lab_train_subway_west" ], "x": 0, "y": 0, "connections": { "west": [ "subway_tunnel" ] } }, + { "chunks": [ "lab_train_subway_north" ], "x": 0, "y": 0, "connections": { "north": [ "subway_tunnel" ] } }, + { "chunks": [ "lab_train_subway_south" ], "x": 0, "y": 0, "connections": { "south": [ "subway_tunnel" ] } }, { "chunks": [ "lab_train_entrance_north" ], "x": 0, "y": 0, "neighbors": { "north": [ "lab" ] } }, { "chunks": [ "lab_train_entrance_south" ], "x": 0, "y": 0, "neighbors": { "south": [ "lab" ] } }, { "chunks": [ "lab_train_entrance_east" ], "x": 0, "y": 0, "neighbors": { "east": [ "lab" ] } }, diff --git a/data/json/obsoletion/terrains.json b/data/json/obsoletion/terrains.json index 56557304db91..51c44c8ead4e 100644 --- a/data/json/obsoletion/terrains.json +++ b/data/json/obsoletion/terrains.json @@ -20,7 +20,14 @@ "spiral", "spiral_hub", "underground_sub_station", - "sewer_sub_station" + "sewer_sub_station", + "anthill", + "acid_anthill", + "ants_food", + "ants_larvae", + "ants_larvae_acid", + "ants_queen", + "ants_queen_acid" ] } ] diff --git a/data/json/overmap/multitile_city_buildings.json b/data/json/overmap/multitile_city_buildings.json index efa4faa187a6..59c64959625b 100644 --- a/data/json/overmap/multitile_city_buildings.json +++ b/data/json/overmap/multitile_city_buildings.json @@ -2407,13 +2407,13 @@ { "point": [ 6, 1, 0 ], "overmap": "mall_a_75_south" }, { "point": [ 5, 1, 0 ], "overmap": "mall_a_76_south" }, { "point": [ 4, 1, 0 ], "overmap": "mall_a_77_south" }, - { "point": [ 4, 1, -2 ], "overmap": "microlab_sub_connector_north" }, { "point": [ 3, 1, 0 ], "overmap": "mall_a_78_south" }, { "point": [ 2, 1, 0 ], "overmap": "mall_a_79_south" }, { "point": [ 1, 1, 0 ], "overmap": "mall_a_80_south" }, { "point": [ 0, 1, 0 ], "overmap": "mall_a_81_south" }, { "point": [ 4, 0, 0 ], "overmap": "road_end_north" } - ] + ], + "connections": [ { "point": [ 4, 1, -2 ], "connection": "subway_tunnel", "from": [ 4, 2, -2 ] } ] }, { "type": "city_building", diff --git a/data/json/overmap/overmap_connections.json b/data/json/overmap/overmap_connections.json index c7ab01fe63bd..b79d2d98c95b 100644 --- a/data/json/overmap/overmap_connections.json +++ b/data/json/overmap/overmap_connections.json @@ -21,9 +21,10 @@ { "type": "overmap_connection", "id": "subway_tunnel", + "layout": "p2p", "default_terrain": "subway", "subtypes": [ - { "terrain": "underground_sub_station", "locations": [ "underground_sub_station" ], "flags": [ "ORTHOGONAL" ] }, + { "terrain": "underground_sub_station", "locations": [ ], "flags": [ "ORTHOGONAL" ] }, { "terrain": "subway", "locations": [ "subterranean_subway" ], "flags": [ "ORTHOGONAL" ] } ] }, diff --git a/data/json/overmap/overmap_mutable/anthill.json b/data/json/overmap/overmap_mutable/anthill.json new file mode 100644 index 000000000000..cd80b95fb196 --- /dev/null +++ b/data/json/overmap/overmap_mutable/anthill.json @@ -0,0 +1,244 @@ +[ + { + "type": "overmap_special", + "id": "Anthill", + "subtype": "mutable", + "locations": [ "subterranean_empty" ], + "city_distance": [ 10, -1 ], + "city_sizes": [ 0, 20 ], + "occurrences": [ 40, 100 ], + "flags": [ "ANT", "UNIQUE", "CLASSIC", "WILDERNESS" ], + "spawns": { "group": "GROUP_ANT", "population": [ 1000, 2000 ], "radius": [ 10, 30 ] }, + "check_for_locations": [ + [ [ 0, 0, 0 ], [ "land" ] ], + [ [ 0, 0, -1 ], [ "subterranean_empty" ] ], + [ [ 1, 0, -1 ], [ "subterranean_empty" ] ], + [ [ 0, 1, -1 ], [ "subterranean_empty" ] ], + [ [ -1, 0, -1 ], [ "subterranean_empty" ] ], + [ [ 0, -1, -1 ], [ "subterranean_empty" ] ] + ], + "joins": [ + { "id": "surface_to_tunnel", "opposite": "tunnel_to_surface" }, + { "id": "tunnel_to_surface", "opposite": "surface_to_tunnel", "into_locations": [ "land" ] }, + "tunnel_to_tunnel" + ], + "overmaps": { + "surface": { "overmap": "anthill_north", "below": "surface_to_tunnel", "locations": [ "land" ] }, + "below_entrance": { + "overmap": "ants_nesw", + "above": "tunnel_to_surface", + "north": "tunnel_to_tunnel", + "east": "tunnel_to_tunnel", + "south": "tunnel_to_tunnel", + "west": "tunnel_to_tunnel" + }, + "crossroads": { + "overmap": "ants_nesw", + "north": "tunnel_to_tunnel", + "east": "tunnel_to_tunnel", + "south": "tunnel_to_tunnel", + "west": "tunnel_to_tunnel" + }, + "tee": { "overmap": "ants_nes", "north": "tunnel_to_tunnel", "east": "tunnel_to_tunnel", "south": "tunnel_to_tunnel" }, + "straight_tunnel": { "overmap": "ants_ns", "north": "tunnel_to_tunnel", "south": "tunnel_to_tunnel" }, + "corner": { "overmap": "ants_ne", "north": "tunnel_to_tunnel", "east": "tunnel_to_tunnel" }, + "dead_end": { "overmap": "ants_end_south", "north": "tunnel_to_tunnel" }, + "queen": { "overmap": "ants_queen_north", "north": "tunnel_to_tunnel" }, + "larvae": { "overmap": "ants_larvae_north", "north": "tunnel_to_tunnel" }, + "food": { "overmap": "ants_food_north", "north": "tunnel_to_tunnel" } + }, + "root": "surface", + "shared": { "size": [ 1, 5 ] }, + "phases": [ + [ { "overmap": "below_entrance", "max": 1 } ], + [ + { "overmap": "straight_tunnel", "scale": "size", "max": { "poisson": 10 } }, + { "overmap": "corner", "scale": "size", "max": { "poisson": 2.5 } }, + { "overmap": "tee", "scale": "size", "max": { "poisson": 5 } }, + { "overmap": "below_entrance", "scale": "size", "max": { "poisson": 0.35 } } + ], + [ { "overmap": "queen", "max": 1 } ], + [ + { "overmap": "food", "scale": "size", "max": { "poisson": 2.5 } }, + { "overmap": "larvae", "scale": "size", "max": { "poisson": 2.5 } } + ], + [ + { "overmap": "dead_end", "weight": 2000 }, + { "overmap": "straight_tunnel", "weight": 100 }, + { "overmap": "corner", "weight": 100 }, + { "overmap": "tee", "weight": 10 }, + { "overmap": "crossroads", "weight": 1 }, + { "overmap": "surface", "weight": 1 } + ] + ] + }, + { + "type": "overmap_special", + "id": "Acid Anthill", + "subtype": "mutable", + "locations": [ "subterranean_empty" ], + "city_distance": [ 10, -1 ], + "occurrences": [ 40, 100 ], + "flags": [ "ANT", "UNIQUE", "WILDERNESS" ], + "spawns": { "group": "GROUP_ANT_ACID", "population": [ 1000, 2000 ], "radius": [ 10, 30 ] }, + "check_for_locations": [ + [ [ 0, 0, 0 ], [ "land" ] ], + [ [ 0, 0, -1 ], [ "subterranean_empty" ] ], + [ [ 1, 0, -1 ], [ "subterranean_empty" ] ], + [ [ 0, 1, -1 ], [ "subterranean_empty" ] ], + [ [ -1, 0, -1 ], [ "subterranean_empty" ] ], + [ [ 0, -1, -1 ], [ "subterranean_empty" ] ] + ], + "joins": [ + { "id": "surface_to_tunnel", "opposite": "tunnel_to_surface" }, + { "id": "tunnel_to_surface", "opposite": "surface_to_tunnel", "into_locations": [ "land" ] }, + "tunnel_to_tunnel" + ], + "overmaps": { + "surface": { "overmap": "anthill_north", "below": "surface_to_tunnel", "locations": [ "land" ] }, + "below_entrance": { + "overmap": "acid_ants_nesw", + "above": "tunnel_to_surface", + "north": "tunnel_to_tunnel", + "east": "tunnel_to_tunnel", + "south": "tunnel_to_tunnel", + "west": "tunnel_to_tunnel" + }, + "crossroads": { + "overmap": "acid_ants_nesw", + "north": "tunnel_to_tunnel", + "east": "tunnel_to_tunnel", + "south": "tunnel_to_tunnel", + "west": "tunnel_to_tunnel" + }, + "tee": { "overmap": "acid_ants_nes", "north": "tunnel_to_tunnel", "east": "tunnel_to_tunnel", "south": "tunnel_to_tunnel" }, + "straight_tunnel": { "overmap": "acid_ants_ns", "north": "tunnel_to_tunnel", "south": "tunnel_to_tunnel" }, + "corner": { "overmap": "acid_ants_ne", "north": "tunnel_to_tunnel", "east": "tunnel_to_tunnel" }, + "dead_end": { "overmap": "acid_ants_end_south", "north": "tunnel_to_tunnel" }, + "queen": { "overmap": "acid_ants_queen_north", "north": "tunnel_to_tunnel" }, + "larvae": { "overmap": "acid_ants_larvae_north", "north": "tunnel_to_tunnel" }, + "food": { "overmap": "acid_ants_food_north", "north": "tunnel_to_tunnel" } + }, + "root": "surface", + "shared": { "size": [ 1, 5 ] }, + "phases": [ + [ { "overmap": "below_entrance", "max": 1 } ], + [ + { "overmap": "straight_tunnel", "scale": "size", "max": { "poisson": 10 } }, + { "overmap": "corner", "scale": "size", "max": { "poisson": 2.5 } }, + { "overmap": "tee", "scale": "size", "max": { "poisson": 5 } }, + { "overmap": "below_entrance", "scale": "size", "max": { "poisson": 0.35 } } + ], + [ { "overmap": "queen", "max": 1 } ], + [ + { "overmap": "food", "scale": "size", "max": { "poisson": 2.5 } }, + { "overmap": "larvae", "scale": "size", "max": { "poisson": 2.5 } } + ], + [ + { "overmap": "dead_end", "weight": 2000 }, + { "overmap": "straight_tunnel", "weight": 100 }, + { "overmap": "corner", "weight": 100 }, + { "overmap": "tee", "weight": 10 }, + { "overmap": "crossroads", "weight": 1 }, + { "overmap": "surface", "weight": 1 } + ] + ] + }, + { + "type": "overmap_special", + "id": "Lab with Anthill", + "subtype": "mutable", + "locations": [ "subterranean_empty" ], + "city_distance": [ 4, -1 ], + "city_sizes": [ 0, 20 ], + "occurrences": [ 30, 100 ], + "flags": [ "ANT", "UNIQUE" ], + "connections": [ { "point": [ 0, -1, 0 ], "connection": "local_road" } ], + "place_nested": [ { "point": [ 0, 0, -1 ], "special": "lab_basement" } ], + "spawns": { "group": "GROUP_ANT", "population": [ 1000, 2000 ], "radius": [ 10, 30 ] }, + "check_for_locations": [ + [ [ 3, 1, 0 ], [ "land" ] ], + [ [ 3, 1, -1 ], [ "subterranean_empty" ] ], + [ [ 4, 1, -1 ], [ "subterranean_empty" ] ], + [ [ 3, 2, -1 ], [ "subterranean_empty" ] ], + [ [ 2, 1, -1 ], [ "subterranean_empty" ] ], + [ [ 3, 0, -1 ], [ "subterranean_empty" ] ], + [ [ 0, 0, 0 ], [ "land" ] ], + [ [ 0, 0, -1 ], [ "subterranean_empty" ] ] + ], + "joins": [ + { "id": "surface_to_tunnel", "opposite": "tunnel_to_surface" }, + { "id": "tunnel_to_surface", "opposite": "surface_to_tunnel", "into_locations": [ "land" ] }, + "tunnel_to_tunnel", + "lab" + ], + "overmaps": { + "lab_stairs": { "overmap": "lab_stairs", "below": "lab", "locations": [ "land" ] }, + "below_stairs": { + "overmap": "empty_rock", + "above": "lab", + "north": { "id": "lab", "type": "available" }, + "east": { "id": "lab", "type": "available" }, + "south": { "id": "lab", "type": "available" }, + "west": { "id": "lab", "type": "available" } + }, + "surface": { "overmap": "anthill_north", "below": "surface_to_tunnel", "locations": [ "land" ] }, + "below_entrance": { + "overmap": "ants_nesw", + "above": "tunnel_to_surface", + "north": "tunnel_to_tunnel", + "east": "tunnel_to_tunnel", + "south": "tunnel_to_tunnel", + "west": "tunnel_to_tunnel" + }, + "crossroads": { + "overmap": "ants_nesw", + "north": "tunnel_to_tunnel", + "east": "tunnel_to_tunnel", + "south": "tunnel_to_tunnel", + "west": "tunnel_to_tunnel" + }, + "tee": { "overmap": "ants_nes", "north": "tunnel_to_tunnel", "east": "tunnel_to_tunnel", "south": "tunnel_to_tunnel" }, + "straight_tunnel": { "overmap": "ants_ns", "north": "tunnel_to_tunnel", "south": "tunnel_to_tunnel" }, + "corner": { "overmap": "ants_ne", "north": "tunnel_to_tunnel", "east": "tunnel_to_tunnel" }, + "dead_end": { "overmap": "ants_end_south", "north": "tunnel_to_tunnel" }, + "queen": { "overmap": "ants_queen_north", "north": "tunnel_to_tunnel" }, + "larvae": { "overmap": "ants_larvae_north", "north": "tunnel_to_tunnel" }, + "food": { "overmap": "ants_food_north", "north": "tunnel_to_tunnel" } + }, + "root": "lab_stairs", + "shared": { "size": [ 1, 5 ] }, + "phases": [ + [ + { + "name": "ants_lab", + "chunk": [ + { "overmap": "below_stairs", "pos": [ 0, 0, 0 ] }, + { "overmap": "surface", "pos": [ 3, 1, 1 ] }, + { "overmap": "below_entrance", "pos": [ 3, 1, 0 ] } + ], + "max": 1 + } + ], + [ + { "overmap": "straight_tunnel", "scale": "size", "max": { "poisson": 10 } }, + { "overmap": "corner", "scale": "size", "max": { "poisson": 2.5 } }, + { "overmap": "tee", "scale": "size", "max": { "poisson": 5 } }, + { "overmap": "below_entrance", "scale": "size", "max": { "poisson": 0.35 } } + ], + [ { "overmap": "queen", "max": 1 } ], + [ + { "overmap": "food", "scale": "size", "max": { "poisson": 2.5 } }, + { "overmap": "larvae", "scale": "size", "max": { "poisson": 2.5 } } + ], + [ + { "overmap": "dead_end", "weight": 2000 }, + { "overmap": "straight_tunnel", "weight": 100 }, + { "overmap": "corner", "weight": 100 }, + { "overmap": "tee", "weight": 10 }, + { "overmap": "crossroads", "weight": 1 }, + { "overmap": "surface", "weight": 1 } + ] + ] + } +] diff --git a/data/json/overmap/overmap_mutable/lab.json b/data/json/overmap/overmap_mutable/lab.json new file mode 100644 index 000000000000..869258828be4 --- /dev/null +++ b/data/json/overmap/overmap_mutable/lab.json @@ -0,0 +1,432 @@ +[ + { + "//": "Nested special, does not spawn by itself", + "type": "overmap_special", + "id": "lab_basement", + "subtype": "mutable", + "locations": [ "subterranean_empty" ], + "occurrences": [ 0, 0 ], + "check_for_locations": [ [ [ 0, 0, 0 ], [ "subterranean_empty" ] ] ], + "joins": [ "lab_to_lab" ], + "overmaps": { + "lab_stairs": { + "overmap": "lab_stairs", + "below": "lab_to_lab", + "north": { "id": "lab_to_lab", "type": "optional" }, + "east": { "id": "lab_to_lab", "type": "optional" }, + "south": { "id": "lab_to_lab", "type": "optional" }, + "west": { "id": "lab_to_lab", "type": "optional" } + }, + "lab_core": { + "overmap": "lab_core", + "above": { "id": "lab_to_lab", "type": "available" }, + "north": { "id": "lab_to_lab", "type": "optional" }, + "east": { "id": "lab_to_lab", "type": "optional" }, + "south": { "id": "lab_to_lab", "type": "optional" }, + "west": { "id": "lab_to_lab", "type": "optional" } + }, + "lab": { + "overmap": "lab", + "north": { "id": "lab_to_lab", "type": "optional" }, + "east": { "id": "lab_to_lab", "type": "optional" }, + "south": { "id": "lab_to_lab", "type": "optional" }, + "west": { "id": "lab_to_lab", "type": "optional" } + }, + "lab_finale": { + "overmap": "lab_finale", + "north": { "id": "lab_to_lab", "type": "optional" }, + "east": { "id": "lab_to_lab", "type": "optional" }, + "south": { "id": "lab_to_lab", "type": "optional" }, + "west": { "id": "lab_to_lab", "type": "optional" } + }, + "lab_escape_cells": { + "overmap": "lab_escape_cells", + "north": { "id": "lab_to_lab", "type": "reject" }, + "east": { "id": "lab_to_lab", "type": "reject" }, + "west": { "id": "lab_to_lab", "type": "reject" } + }, + "lab_escape_entrance": { + "overmap": "lab_escape_entrance", + "east": { "id": "lab_to_lab", "type": "optional" }, + "south": { "id": "lab_to_lab", "type": "optional" }, + "west": { "id": "lab_to_lab", "type": "optional" } + }, + "lab_train_depot": { + "overmap": "lab_train_depot", + "north": { "id": "lab_to_lab", "type": "reject" }, + "east": { "id": "lab_to_lab", "type": "optional" }, + "south": { "id": "lab_to_lab", "type": "optional" }, + "west": { "id": "lab_to_lab", "type": "optional" } + } + }, + "root": "lab_core", + "shared": { "size": [ 1, 7 ] }, + "phases": [ + [ + { "overmap": "lab", "scale": "size", "max": { "poisson": 15 } }, + { + "name": "stairs_and_core", + "chunk": [ { "overmap": "lab_stairs", "pos": [ 0, 0, 0 ] }, { "overmap": "lab_core", "pos": [ 0, 0, -1 ] } ], + "z": "bottom", + "scale": "size", + "max": { "poisson": 3 } + } + ], + [ + { + "//": "Should always be pointing north, this mapgen doesn't rotate", + "name": "lab_escape", + "chunk": [ { "overmap": "lab_escape_entrance", "pos": [ 0, 0, 0 ] }, { "overmap": "lab_escape_cells", "pos": [ 0, -1, 0 ] } ], + "rotate": false, + "z": [ -10, -4 ], + "scale": "size", + "max": { "chance": 0.2 } + } + ], + [ + { + "overmap": "lab_train_depot", + "z": -2, + "scale": "size", + "max": { "chance": 0.15 }, + "connections": [ { "point": [ 0, -1, 0 ], "connection": "subway_tunnel", "from": [ 0, 0, 0 ] } ] + } + ], + [ + { + "overmap": "lab_train_depot", + "z": -4, + "scale": "size", + "max": { "chance": 0.05 }, + "connections": [ { "point": [ 0, -1, 0 ], "connection": "subway_tunnel", "from": [ 0, 0, 0 ] } ] + } + ], + [ { "overmap": "lab", "z": "bottom", "scale": "size", "max": { "poisson": 1 } } ], + [ { "overmap": "lab_finale", "z": "bottom", "max": 1 } ] + ] + }, + { + "type": "overmap_special", + "id": "Ice Lab", + "subtype": "mutable", + "locations": [ "subterranean_empty" ], + "city_distance": [ 10, -1 ], + "occurrences": [ 50, 0 ], + "flags": [ "LAB", "UNIQUE" ], + "connections": [ { "point": [ 0, -1, 0 ], "connection": "local_road" } ], + "check_for_locations": [ + [ [ 0, 0, 0 ], [ "wilderness" ] ], + [ [ 0, 0, -1 ], [ "subterranean_empty" ] ], + [ [ 1, 0, -1 ], [ "subterranean_empty" ] ], + [ [ 0, 1, -1 ], [ "subterranean_empty" ] ], + [ [ -1, 0, -1 ], [ "subterranean_empty" ] ], + [ [ 0, -1, -1 ], [ "subterranean_empty" ] ] + ], + "joins": [ "lab_to_lab" ], + "overmaps": { + "surface": { "overmap": "ice_lab_stairs", "locations": [ "wilderness" ], "below": "lab_to_lab" }, + "ice_lab_stairs": { + "overmap": "ice_lab_stairs", + "below": "lab_to_lab", + "north": { "id": "lab_to_lab", "type": "optional" }, + "east": { "id": "lab_to_lab", "type": "optional" }, + "south": { "id": "lab_to_lab", "type": "optional" }, + "west": { "id": "lab_to_lab", "type": "optional" } + }, + "ice_lab_core": { + "overmap": "ice_lab_core", + "above": "lab_to_lab", + "north": { "id": "lab_to_lab", "type": "optional" }, + "east": { "id": "lab_to_lab", "type": "optional" }, + "south": { "id": "lab_to_lab", "type": "optional" }, + "west": { "id": "lab_to_lab", "type": "optional" } + }, + "ice_lab": { + "overmap": "ice_lab", + "north": { "id": "lab_to_lab", "type": "optional" }, + "east": { "id": "lab_to_lab", "type": "optional" }, + "south": { "id": "lab_to_lab", "type": "optional" }, + "west": { "id": "lab_to_lab", "type": "optional" } + }, + "ice_lab_finale": { + "overmap": "ice_lab_finale", + "north": { "id": "lab_to_lab", "type": "optional" }, + "east": { "id": "lab_to_lab", "type": "optional" }, + "south": { "id": "lab_to_lab", "type": "optional" }, + "west": { "id": "lab_to_lab", "type": "optional" } + }, + "lab_escape_cells": { + "overmap": "lab_escape_cells", + "north": { "id": "lab_to_lab", "type": "reject" }, + "east": { "id": "lab_to_lab", "type": "reject" }, + "west": { "id": "lab_to_lab", "type": "reject" } + }, + "lab_escape_entrance": { + "overmap": "lab_escape_entrance", + "east": { "id": "lab_to_lab", "type": "optional" }, + "south": { "id": "lab_to_lab", "type": "optional" }, + "west": { "id": "lab_to_lab", "type": "optional" } + }, + "lab_train_depot": { + "overmap": "lab_train_depot", + "north": { "id": "lab_to_lab", "type": "reject" }, + "east": { "id": "lab_to_lab", "type": "optional" }, + "south": { "id": "lab_to_lab", "type": "optional" }, + "west": { "id": "lab_to_lab", "type": "optional" } + } + }, + "root": "surface", + "shared": { "size": [ 1, 7 ] }, + "phases": [ + [ { "overmap": "ice_lab_core", "max": 1 } ], + [ + { "overmap": "ice_lab", "scale": "size", "max": { "poisson": 15 } }, + { + "name": "stairs_and_core", + "chunk": [ { "overmap": "ice_lab_stairs", "pos": [ 0, 0, 0 ] }, { "overmap": "ice_lab_core", "pos": [ 0, 0, -1 ] } ], + "z": "bottom", + "scale": "size", + "max": { "poisson": 3 } + } + ], + [ + { + "//": "Should always be pointing north, this mapgen doesn't rotate", + "name": "lab_escape", + "chunk": [ { "overmap": "lab_escape_entrance", "pos": [ 0, 0, 0 ] }, { "overmap": "lab_escape_cells", "pos": [ 0, -1, 0 ] } ], + "rotate": false, + "z": [ -10, -4 ], + "scale": "size", + "max": { "chance": 0.2 } + } + ], + [ + { + "overmap": "lab_train_depot", + "z": -2, + "scale": "size", + "max": { "chance": 0.15 }, + "connections": [ { "point": [ 0, -1, 0 ], "connection": "subway_tunnel", "from": [ 0, 0, 0 ] } ] + } + ], + [ + { + "overmap": "lab_train_depot", + "z": -4, + "scale": "size", + "max": { "chance": 0.05 }, + "connections": [ { "point": [ 0, -1, 0 ], "connection": "subway_tunnel", "from": [ 0, 0, 0 ] } ] + } + ], + [ { "overmap": "ice_lab", "z": "bottom", "scale": "size", "max": { "poisson": 1 } } ], + [ { "overmap": "ice_lab_finale", "z": "bottom", "max": 1 } ] + ] + }, + { + "type": "overmap_special", + "id": "Central Lab", + "subtype": "mutable", + "locations": [ "subterranean_empty" ], + "city_distance": [ 20, -1 ], + "city_sizes": [ 0, 6 ], + "occurrences": [ 1, 1 ], + "flags": [ "LAB", "UNIQUE", "ENDGAME" ], + "rotate": false, + "check_for_locations": [ + [ [ 0, 0, 0 ], [ "wilderness" ] ], + [ [ 0, 0, -1 ], [ "subterranean_empty" ] ], + [ [ 0, 0, -2 ], [ "subterranean_empty" ] ], + [ [ 1, 0, -2 ], [ "subterranean_empty" ] ], + [ [ 2, 0, -2 ], [ "subterranean_empty" ] ], + [ [ 0, 1, -2 ], [ "subterranean_empty" ] ], + [ [ 1, 1, -2 ], [ "subterranean_empty" ] ], + [ [ 2, 1, -2 ], [ "subterranean_empty" ] ], + [ [ 0, 2, -2 ], [ "subterranean_empty" ] ], + [ [ 1, 2, -2 ], [ "subterranean_empty" ] ], + [ [ 2, 2, -2 ], [ "subterranean_empty" ] ], + [ [ 3, 0, -2 ], [ "subterranean_empty" ] ], + [ [ 3, 2, -2 ], [ "subterranean_empty" ] ], + [ [ 4, 0, -2 ], [ "subterranean_empty" ] ], + [ [ 4, 1, -2 ], [ "subterranean_empty" ] ], + [ [ 4, 2, -2 ], [ "subterranean_empty" ] ], + [ [ 5, 1, -2 ], [ "subterranean_empty" ] ], + [ [ 6, 1, -2 ], [ "subterranean_empty" ] ], + [ [ 5, 1, -3 ], [ "subterranean_empty" ] ], + [ [ -1, 0, -2 ], [ "subterranean_empty" ] ], + [ [ -1, 2, -2 ], [ "subterranean_empty" ] ], + [ [ -2, 0, -2 ], [ "subterranean_empty" ] ], + [ [ -2, 1, -2 ], [ "subterranean_empty" ] ], + [ [ -2, 2, -2 ], [ "subterranean_empty" ] ], + [ [ -3, 1, -2 ], [ "subterranean_empty" ] ], + [ [ -4, 1, -2 ], [ "subterranean_empty" ] ], + [ [ -3, 1, -3 ], [ "subterranean_empty" ] ], + [ [ 0, 3, -2 ], [ "subterranean_empty" ] ], + [ [ 2, 3, -2 ], [ "subterranean_empty" ] ], + [ [ 0, 4, -2 ], [ "subterranean_empty" ] ], + [ [ 1, 4, -2 ], [ "subterranean_empty" ] ], + [ [ 2, 4, -2 ], [ "subterranean_empty" ] ], + [ [ 1, 5, -2 ], [ "subterranean_empty" ] ], + [ [ 1, 6, -2 ], [ "subterranean_empty" ] ], + [ [ 1, 5, -3 ], [ "subterranean_empty" ] ], + [ [ 0, -1, -2 ], [ "subterranean_empty" ] ], + [ [ 2, -1, -2 ], [ "subterranean_empty" ] ], + [ [ 0, -2, -2 ], [ "subterranean_empty" ] ], + [ [ 1, -2, -2 ], [ "subterranean_empty" ] ], + [ [ 2, -2, -2 ], [ "subterranean_empty" ] ], + [ [ 1, -3, -2 ], [ "subterranean_empty" ] ], + [ [ 1, -4, -2 ], [ "subterranean_empty" ] ], + [ [ 1, -3, -3 ], [ "subterranean_empty" ] ] + ], + "joins": [ "lab_to_lab", "entrance_to_shaft" ], + "overmaps": { + "surface": { "overmap": "central_lab_entrance", "locations": [ "wilderness" ], "below": "entrance_to_shaft" }, + "central_lab_shaft": { "overmap": "central_lab_shaft", "above": "entrance_to_shaft" }, + "central_lab_hq_1": { "overmap": "central_lab_hq_1_north" }, + "central_lab_hq_2": { "overmap": "central_lab_hq_2_north" }, + "central_lab_hq_3": { "overmap": "central_lab_hq_3_north" }, + "central_lab_hq_4": { "overmap": "central_lab_hq_4_north" }, + "central_lab_hq_5": { "overmap": "central_lab_hq_5_north" }, + "central_lab_hq_6": { "overmap": "central_lab_hq_6_north" }, + "central_lab_hq_7": { "overmap": "central_lab_hq_7_north" }, + "central_lab_hq_8": { "overmap": "central_lab_hq_8_north" }, + "central_lab_hq_9": { "overmap": "central_lab_hq_9_north" }, + "central_lab_core_stub": { "overmap": "central_lab_core" }, + "central_lab_stub": { "overmap": "central_lab_core" }, + "central_lab_stairs_stub": { + "overmap": "central_lab_core", + "below": "lab_to_lab", + "north": { "id": "lab_to_lab", "type": "reject" }, + "east": { "id": "lab_to_lab", "type": "reject" }, + "south": { "id": "lab_to_lab", "type": "reject" }, + "west": { "id": "lab_to_lab", "type": "reject" } + }, + "central_lab_stairs": { + "overmap": "central_lab_stairs", + "below": "lab_to_lab", + "north": { "id": "lab_to_lab", "type": "optional" }, + "east": { "id": "lab_to_lab", "type": "optional" }, + "south": { "id": "lab_to_lab", "type": "optional" }, + "west": { "id": "lab_to_lab", "type": "optional" } + }, + "central_lab_core": { + "overmap": "central_lab_core", + "above": "lab_to_lab", + "north": { "id": "lab_to_lab", "type": "optional" }, + "east": { "id": "lab_to_lab", "type": "optional" }, + "south": { "id": "lab_to_lab", "type": "optional" }, + "west": { "id": "lab_to_lab", "type": "optional" } + }, + "central_lab": { + "overmap": "central_lab", + "north": { "id": "lab_to_lab", "type": "optional" }, + "east": { "id": "lab_to_lab", "type": "optional" }, + "south": { "id": "lab_to_lab", "type": "optional" }, + "west": { "id": "lab_to_lab", "type": "optional" } + }, + "central_lab_finale": { + "overmap": "central_lab_finale", + "north": { "id": "lab_to_lab", "type": "optional" }, + "east": { "id": "lab_to_lab", "type": "optional" }, + "south": { "id": "lab_to_lab", "type": "optional" }, + "west": { "id": "lab_to_lab", "type": "optional" } + }, + "central_lab_train_depot": { + "overmap": "central_lab_train_depot", + "north": { "id": "lab_to_lab", "type": "reject" }, + "east": { "id": "lab_to_lab", "type": "optional" }, + "south": { "id": "lab_to_lab", "type": "optional" }, + "west": { "id": "lab_to_lab", "type": "optional" } + }, + "central_lab_endgame": { + "overmap": "central_lab_endgame", + "north": { "id": "lab_to_lab", "type": "optional" }, + "east": { "id": "lab_to_lab", "type": "optional" }, + "south": { "id": "lab_to_lab", "type": "optional" }, + "west": { "id": "lab_to_lab", "type": "optional" } + } + }, + "root": "surface", + "phases": [ + [ + { + "name": "main_lab", + "chunk": [ + { "overmap": "central_lab_shaft", "pos": [ 0, 0, 0 ] }, + { "overmap": "central_lab_hq_3", "pos": [ 0, 0, -1 ] }, + { "overmap": "central_lab_hq_2", "pos": [ 1, 0, -1 ] }, + { "overmap": "central_lab_hq_1", "pos": [ 2, 0, -1 ] }, + { "overmap": "central_lab_hq_6", "pos": [ 0, 1, -1 ] }, + { "overmap": "central_lab_hq_5", "pos": [ 1, 1, -1 ] }, + { "overmap": "central_lab_hq_4", "pos": [ 2, 1, -1 ] }, + { "overmap": "central_lab_hq_9", "pos": [ 0, 2, -1 ] }, + { "overmap": "central_lab_hq_8", "pos": [ 1, 2, -1 ] }, + { "overmap": "central_lab_hq_7", "pos": [ 2, 2, -1 ] }, + { "overmap": "central_lab_stub", "pos": [ 3, 0, -1 ] }, + { "overmap": "central_lab_stub", "pos": [ 3, 2, -1 ] }, + { "overmap": "central_lab_stub", "pos": [ 4, 0, -1 ] }, + { "overmap": "central_lab_stub", "pos": [ 4, 1, -1 ] }, + { "overmap": "central_lab_stub", "pos": [ 4, 2, -1 ] }, + { "overmap": "central_lab_stairs_stub", "pos": [ 5, 1, -1 ] }, + { "overmap": "central_lab_core", "pos": [ 5, 1, -2 ] }, + { "overmap": "central_lab_core_stub", "pos": [ 6, 1, -1 ] }, + { "overmap": "central_lab_stub", "pos": [ -1, 0, -1 ] }, + { "overmap": "central_lab_stub", "pos": [ -1, 2, -1 ] }, + { "overmap": "central_lab_stub", "pos": [ -2, 0, -1 ] }, + { "overmap": "central_lab_stub", "pos": [ -2, 1, -1 ] }, + { "overmap": "central_lab_stub", "pos": [ -2, 2, -1 ] }, + { "overmap": "central_lab_stairs_stub", "pos": [ -3, 1, -1 ] }, + { "overmap": "central_lab_core", "pos": [ -3, 1, -2 ] }, + { "overmap": "central_lab_core_stub", "pos": [ -4, 1, -1 ] }, + { "overmap": "central_lab_stub", "pos": [ 0, 3, -1 ] }, + { "overmap": "central_lab_stub", "pos": [ 2, 3, -1 ] }, + { "overmap": "central_lab_stub", "pos": [ 0, 4, -1 ] }, + { "overmap": "central_lab_stub", "pos": [ 1, 4, -1 ] }, + { "overmap": "central_lab_stub", "pos": [ 2, 4, -1 ] }, + { "overmap": "central_lab_stairs_stub", "pos": [ 1, 5, -1 ] }, + { "overmap": "central_lab_core", "pos": [ 1, 5, -2 ] }, + { "overmap": "central_lab_core_stub", "pos": [ 1, 6, -1 ] }, + { "overmap": "central_lab_stub", "pos": [ 0, -1, -1 ] }, + { "overmap": "central_lab_stub", "pos": [ 2, -1, -1 ] }, + { "overmap": "central_lab_stub", "pos": [ 0, -2, -1 ] }, + { "overmap": "central_lab_stub", "pos": [ 1, -2, -1 ] }, + { "overmap": "central_lab_stub", "pos": [ 2, -2, -1 ] }, + { "overmap": "central_lab_stairs_stub", "pos": [ 1, -3, -1 ] }, + { "overmap": "central_lab_core", "pos": [ 1, -3, -2 ] }, + { "overmap": "central_lab_core_stub", "pos": [ 1, -4, -1 ] } + ], + "max": 1 + } + ], + [ + { + "name": "stairs_and_core", + "chunk": [ { "overmap": "central_lab_stairs", "pos": [ 0, 0, 0 ] }, { "overmap": "central_lab_core", "pos": [ 0, 0, -1 ] } ], + "z": "bottom", + "max": 1 + } + ], + [ + { "overmap": "central_lab", "z": [ -10, -3 ], "max": { "poisson": 180 } }, + { + "name": "stairs_and_core", + "chunk": [ { "overmap": "central_lab_stairs", "pos": [ 0, 0, 0 ] }, { "overmap": "central_lab_core", "pos": [ 0, 0, -1 ] } ], + "z": [ -10, -4 ], + "max": { "poisson": 50 } + } + ], + [ + { + "overmap": "central_lab_train_depot", + "rotate": true, + "z": -4, + "max": [ 1, 3 ], + "connections": [ { "point": [ 0, -1, 0 ], "connection": "subway_tunnel", "from": [ 0, 0, 0 ] } ] + } + ], + [ { "overmap": "central_lab", "z": "bottom", "max": { "poisson": 3 } } ], + [ { "overmap": "central_lab_finale", "z": "bottom", "max": 1 } ], + [ { "overmap": "central_lab_finale", "z": [ -10, -5 ], "max": { "poisson": 2 } } ], + [ { "overmap": "central_lab_endgame", "om_pos": [ 0, 0 ], "z": "bottom", "max": 1 } ] + ] + } +] diff --git a/data/json/overmap/overmap_special/specials.json b/data/json/overmap/overmap_special/specials.json index 0d86c40f0588..7565d8c1a149 100644 --- a/data/json/overmap/overmap_special/specials.json +++ b/data/json/overmap/overmap_special/specials.json @@ -633,85 +633,13 @@ { "type": "overmap_special", "id": "Lab", - "overmaps": [ { "point": [ 0, 0, 0 ], "overmap": "lab_stairs" } ], + "overmaps": [ { "point": [ 0, 0, 0 ], "overmap": "lab_stairs", "locations": [ "wilderness" ] } ], "connections": [ { "point": [ 0, -1, 0 ], "connection": "local_road" } ], - "locations": [ "wilderness" ], + "place_nested": [ { "point": [ 0, 0, -1 ], "special": "lab_basement" } ], "city_distance": [ 4, -1 ], "occurrences": [ 50, 100 ], "flags": [ "LAB", "UNIQUE" ] }, - { - "type": "overmap_special", - "id": "Lab with Anthill", - "overmaps": [ { "point": [ 0, 0, 0 ], "overmap": "lab_stairs" }, { "point": [ 3, 1, 0 ], "overmap": "anthill" } ], - "connections": [ { "point": [ 0, -1, 0 ], "connection": "local_road" } ], - "locations": [ "wilderness" ], - "city_distance": [ 4, -1 ], - "occurrences": [ 30, 100 ], - "flags": [ "ANT", "UNIQUE" ], - "spawns": { "group": "GROUP_ANT", "population": [ 1000, 2000 ], "radius": [ 10, 30 ] } - }, - { - "type": "overmap_special", - "id": "Central Lab", - "overmaps": [ - { "point": [ 0, 0, 0 ], "overmap": "central_lab_entrance" }, - { "point": [ 0, 0, -1 ], "overmap": "central_lab_shaft" }, - { "point": [ 0, 0, -2 ], "overmap": "central_lab_hq_3_north" }, - { "point": [ 1, 0, -2 ], "overmap": "central_lab_hq_2_north" }, - { "point": [ 2, 0, -2 ], "overmap": "central_lab_hq_1_north" }, - { "point": [ 0, 1, -2 ], "overmap": "central_lab_hq_6_north" }, - { "point": [ 1, 1, -2 ], "overmap": "central_lab_hq_5_north" }, - { "point": [ 2, 1, -2 ], "overmap": "central_lab_hq_6_north" }, - { "point": [ 0, 2, -2 ], "overmap": "central_lab_hq_9_north" }, - { "point": [ 1, 2, -2 ], "overmap": "central_lab_hq_8_north" }, - { "point": [ 2, 2, -2 ], "overmap": "central_lab_hq_7_north" }, - { "point": [ 3, 0, -2 ], "overmap": "central_lab" }, - { "point": [ 3, 2, -2 ], "overmap": "central_lab" }, - { "point": [ 4, 0, -2 ], "overmap": "central_lab" }, - { "point": [ 4, 1, -2 ], "overmap": "central_lab" }, - { "point": [ 4, 2, -2 ], "overmap": "central_lab" }, - { "point": [ 5, 1, -2 ], "overmap": "central_lab_stairs" }, - { "point": [ 6, 1, -2 ], "overmap": "central_lab_core" }, - { "point": [ -1, 0, -2 ], "overmap": "central_lab" }, - { "point": [ -1, 2, -2 ], "overmap": "central_lab" }, - { "point": [ -2, 0, -2 ], "overmap": "central_lab" }, - { "point": [ -2, 1, -2 ], "overmap": "central_lab" }, - { "point": [ -2, 2, -2 ], "overmap": "central_lab" }, - { "point": [ -3, 1, -2 ], "overmap": "central_lab_stairs" }, - { "point": [ -4, 1, -2 ], "overmap": "central_lab_core" }, - { "point": [ 0, 3, -2 ], "overmap": "central_lab" }, - { "point": [ 2, 3, -2 ], "overmap": "central_lab" }, - { "point": [ 0, 4, -2 ], "overmap": "central_lab" }, - { "point": [ 1, 4, -2 ], "overmap": "central_lab" }, - { "point": [ 2, 4, -2 ], "overmap": "central_lab" }, - { "point": [ 1, 5, -2 ], "overmap": "central_lab_stairs" }, - { "point": [ 1, 6, -2 ], "overmap": "central_lab_core" }, - { "point": [ 0, -1, -2 ], "overmap": "central_lab" }, - { "point": [ 2, -1, -2 ], "overmap": "central_lab" }, - { "point": [ 0, -2, -2 ], "overmap": "central_lab" }, - { "point": [ 1, -2, -2 ], "overmap": "central_lab" }, - { "point": [ 2, -2, -2 ], "overmap": "central_lab" }, - { "point": [ 1, -3, -2 ], "overmap": "central_lab_stairs" }, - { "point": [ 1, -4, -2 ], "overmap": "central_lab_core" } - ], - "locations": [ "forest" ], - "city_distance": [ 20, -1 ], - "city_sizes": [ 0, 6 ], - "occurrences": [ 1, 1 ], - "flags": [ "UNIQUE", "LAB", "ENDGAME" ], - "rotate": false - }, - { - "type": "overmap_special", - "id": "Ice Lab", - "overmaps": [ { "point": [ 0, 0, 0 ], "overmap": "ice_lab_stairs" } ], - "connections": [ { "point": [ 0, -1, 0 ], "connection": "local_road" } ], - "locations": [ "wilderness" ], - "city_distance": [ 10, -1 ], - "occurrences": [ 50, 100 ], - "flags": [ "LAB", "UNIQUE" ] - }, { "type": "overmap_special", "id": "FEMA Camp", @@ -978,34 +906,13 @@ { "point": [ 1, 2, 2 ], "overmap": "prison_1_3f_7_north" } ], "connections": [ { "point": [ 0, -1, 0 ], "from": [ 0, 0, 0 ], "connection": "local_road" } ], + "place_nested": [ { "point": [ 0, 2, -2 ], "special": "lab_basement" } ], "locations": [ "wilderness" ], "city_distance": [ 10, 40 ], "city_sizes": [ 4, -1 ], "occurrences": [ 30, 100 ], "flags": [ "LAB", "UNIQUE" ] }, - { - "type": "overmap_special", - "id": "Anthill", - "overmaps": [ { "point": [ 0, 0, 0 ], "overmap": "anthill" } ], - "locations": [ "wilderness" ], - "city_distance": [ 10, -1 ], - "occurrences": [ 80, 100 ], - "flags": [ "ANT", "UNIQUE" ], - "rotate": false, - "spawns": { "group": "GROUP_ANT", "population": [ 1000, 2000 ], "radius": [ 10, 30 ] } - }, - { - "type": "overmap_special", - "id": "Acid Anthill", - "overmaps": [ { "point": [ 0, 0, 0 ], "overmap": "acid_anthill" } ], - "locations": [ "wilderness" ], - "city_distance": [ 10, -1 ], - "occurrences": [ 80, 100 ], - "flags": [ "ANT", "UNIQUE" ], - "rotate": false, - "spawns": { "group": "GROUP_ANT_ACID", "population": [ 800, 1600 ], "radius": [ 10, 30 ] } - }, { "type": "overmap_special", "id": "Spider Pit", @@ -4220,7 +4127,6 @@ "type": "overmap_special", "id": "4x4_microlab", "overmaps": [ - { "point": [ 2, -2, -2 ], "overmap": "microlab_sub_connector_north" }, { "point": [ 2, -1, -2 ], "overmap": "microlab_sub_station_north" }, { "point": [ 0, -1, -2 ], "overmap": "microlab_rock_border" }, { "point": [ 1, -1, -2 ], "overmap": "microlab_rock_border" }, @@ -4257,6 +4163,7 @@ { "point": [ 3, 4, -2 ], "overmap": "microlab_rock_border" }, { "point": [ 4, 4, -2 ], "overmap": "microlab_rock_border" } ], + "connections": [ { "point": [ 2, -2, -2 ], "connection": "subway_tunnel", "from": [ 2, -1, -2 ] } ], "locations": [ "land" ], "occurrences": [ 0, 2 ], "flags": [ "LAB", "ELECTRIC_GRID" ] @@ -4267,7 +4174,6 @@ "overmaps": [ { "point": [ 2, 1, 1 ], "overmap": "microlab_generic_surface_roof_north" }, { "point": [ 2, 1, 0 ], "overmap": "microlab_generic_surface_north" }, - { "point": [ 2, -2, -2 ], "overmap": "microlab_sub_connector_north" }, { "point": [ 2, -1, -2 ], "overmap": "microlab_sub_station_north" }, { "point": [ 0, -1, -2 ], "overmap": "microlab_rock_border" }, { "point": [ 1, -1, -2 ], "overmap": "microlab_rock_border" }, @@ -4304,7 +4210,10 @@ { "point": [ 3, 4, -2 ], "overmap": "microlab_rock_border" }, { "point": [ 4, 4, -2 ], "overmap": "microlab_rock_border" } ], - "connections": [ { "point": [ 2, 0, 0 ], "connection": "local_road", "from": [ 2, 1, 0 ] } ], + "connections": [ + { "point": [ 2, 0, 0 ], "connection": "local_road", "from": [ 2, 1, 0 ] }, + { "point": [ 2, -2, -2 ], "connection": "subway_tunnel", "from": [ 2, -1, -2 ] } + ], "locations": [ "wilderness" ], "city_distance": [ 10, -1 ], "occurrences": [ 30, 100 ], @@ -4319,7 +4228,6 @@ { "point": [ 2, 1, -1 ], "overmap": "lab_subway_vent_shaft-1" }, { "point": [ 2, 1, -2 ], "overmap": "lab_subway_vent_shaft-2" }, { "point": [ 2, 1, -3 ], "overmap": "lab_subway_vent_shaft-3" }, - { "point": [ 2, -2, -4 ], "overmap": "microlab_sub_connector_north" }, { "point": [ 2, -1, -4 ], "overmap": "microlab_sub_station_north" }, { "point": [ 0, -1, -4 ], "overmap": "microlab_rock_border" }, { "point": [ 1, -1, -4 ], "overmap": "microlab_rock_border" }, @@ -4356,6 +4264,7 @@ { "point": [ 3, 4, -4 ], "overmap": "microlab_rock_border" }, { "point": [ 4, 4, -4 ], "overmap": "microlab_rock_border" } ], + "connections": [ { "point": [ 2, -2, -4 ], "connection": "subway_tunnel", "from": [ 2, -1, -4 ] } ], "locations": [ "forest" ], "city_distance": [ 10, -1 ], "occurrences": [ 30, 100 ], @@ -4366,7 +4275,6 @@ "type": "overmap_special", "id": "double_microlab", "overmaps": [ - { "point": [ 3, -2, -2 ], "overmap": "microlab_sub_connector_north" }, { "point": [ 0, -1, -2 ], "overmap": "microlab_rock_border" }, { "point": [ 1, -1, -2 ], "overmap": "microlab_rock_border" }, { "point": [ 2, -1, -2 ], "overmap": "microlab_rock_border" }, @@ -4415,7 +4323,6 @@ { "point": [ 5, 4, -2 ], "overmap": "microlab_rock_border" }, { "point": [ 6, 4, -2 ], "overmap": "microlab_rock_border" }, { "point": [ 3, 4, -3 ], "overmap": "microlab_generic_isolated_stairs_odd_north" }, - { "point": [ 3, -2, -4 ], "overmap": "microlab_sub_connector_north" }, { "point": [ 0, -1, -4 ], "overmap": "microlab_rock_border" }, { "point": [ 1, -1, -4 ], "overmap": "microlab_rock_border" }, { "point": [ 2, -1, -4 ], "overmap": "microlab_rock_border" }, @@ -4465,6 +4372,10 @@ { "point": [ 6, 4, -4 ], "overmap": "microlab_rock_border" }, { "point": [ 3, 4, -5 ], "overmap": "microlab_generic_isolated_elevator_pit_north" } ], + "connections": [ + { "point": [ 3, -2, -4 ], "connection": "subway_tunnel", "from": [ 3, -1, -4 ] }, + { "point": [ 3, -2, -2 ], "connection": "subway_tunnel", "from": [ 3, -1, -2 ] } + ], "locations": [ "land" ], "occurrences": [ 0, 2 ], "flags": [ "LAB", "ELECTRIC_GRID" ] @@ -4479,9 +4390,11 @@ { "point": [ 0, 0, -2 ], "overmap": "lab_subway_vent_shaft-2" }, { "point": [ 0, 0, -3 ], "overmap": "lab_subway_vent_shaft-3" }, { "point": [ 0, 0, -4 ], "overmap": "lab_subway_vent_shaft-4" }, - { "point": [ 1, 0, -4 ], "overmap": "subway_ns" }, - { "point": [ 1, 1, -4 ], "overmap": "microlab_sub_connector_north" }, - { "point": [ 1, -1, -4 ], "overmap": "microlab_sub_connector_north" } + { "point": [ 1, 0, -4 ], "overmap": "subway_ns" } + ], + "connections": [ + { "point": [ 1, -1, -4 ], "connection": "subway_tunnel", "from": [ 1, 0, -4 ] }, + { "point": [ 1, 1, -4 ], "connection": "subway_tunnel", "from": [ 1, 0, -4 ] } ], "locations": [ "forest" ], "city_distance": [ 10, -1 ], @@ -4720,6 +4633,7 @@ { "point": [ 4, 4, 4 ], "overmap": "lab_surface_brick_block5E4_north" } ], "connections": [ { "point": [ 2, -1, 0 ], "connection": "local_road", "from": [ 2, 0, 0 ] } ], + "place_nested": [ { "point": [ 3, 1, -2 ], "special": "lab_basement" } ], "locations": [ "wilderness" ], "city_distance": [ 10, 120 ], "city_sizes": [ 4, -1 ], diff --git a/data/json/overmap/overmap_terrain/overmap_terrain_ants.json b/data/json/overmap/overmap_terrain/overmap_terrain_ants.json new file mode 100644 index 000000000000..8675f1bdf45f --- /dev/null +++ b/data/json/overmap/overmap_terrain/overmap_terrain_ants.json @@ -0,0 +1,75 @@ +[ + { + "type": "overmap_terrain", + "id": "anthill", + "name": "anthill", + "sym": "%", + "color": "brown", + "see_cost": 2, + "flags": [ "KNOWN_DOWN", "RISK_HIGH" ] + }, + { + "type": "overmap_terrain", + "id": "ants_food", + "name": "ant food storage", + "sym": "O", + "color": "green", + "see_cost": 5, + "spawns": { "group": "GROUP_ANT", "population": [ 6, 10 ], "chance": 100 } + }, + { + "type": "overmap_terrain", + "id": "acid_ants_food", + "name": "ant food storage", + "sym": "O", + "color": "green", + "see_cost": 5, + "spawns": { "group": "GROUP_ANT_ACID", "population": [ 6, 10 ], "chance": 100 } + }, + { + "type": "overmap_terrain", + "id": "ants_larvae", + "copy-from": "ants_food", + "name": "ant larva chamber", + "color": "white" + }, + { + "type": "overmap_terrain", + "id": "acid_ants_larvae", + "copy-from": "ants_food", + "name": "ant larva chamber", + "color": "white" + }, + { + "type": "overmap_terrain", + "id": "ants_queen", + "copy-from": "ants_food", + "name": "ant queen chamber", + "color": "red" + }, + { + "type": "overmap_terrain", + "id": "acid_ants_queen", + "copy-from": "ants_food", + "name": "ant queen chamber", + "color": "red" + }, + { + "type": "overmap_terrain", + "id": "ants", + "name": "ant tunnel", + "color": "brown", + "see_cost": 5, + "spawns": { "group": "GROUP_ANT", "population": [ 4, 6 ], "chance": 100 }, + "flags": [ "LINEAR" ] + }, + { + "type": "overmap_terrain", + "id": "acid_ants", + "name": "ant tunnel", + "color": "brown", + "see_cost": 5, + "spawns": { "group": "GROUP_ANT_ACID", "population": [ 4, 6 ], "chance": 100 }, + "flags": [ "LINEAR" ] + } +] diff --git a/data/json/overmap/overmap_terrain/overmap_terrain_hardcoded.json b/data/json/overmap/overmap_terrain/overmap_terrain_hardcoded.json index 1477c71ce13a..02a292db1f12 100644 --- a/data/json/overmap/overmap_terrain/overmap_terrain_hardcoded.json +++ b/data/json/overmap/overmap_terrain/overmap_terrain_hardcoded.json @@ -197,24 +197,6 @@ "see_cost": 3, "flags": [ "NO_ROTATE", "RISK_HIGH" ] }, - { - "type": "overmap_terrain", - "id": "anthill", - "name": "anthill", - "sym": "%", - "color": "brown", - "see_cost": 2, - "flags": [ "KNOWN_DOWN", "NO_ROTATE", "RISK_HIGH" ] - }, - { - "type": "overmap_terrain", - "id": "acid_anthill", - "name": "sulfurous anthill", - "sym": "%", - "color": "green", - "see_cost": 2, - "flags": [ "KNOWN_DOWN", "NO_ROTATE", "RISK_HIGH" ] - }, { "type": "overmap_terrain", "id": "slimepit", @@ -306,41 +288,6 @@ "see_cost": 2, "flags": [ "KNOWN_DOWN", "NO_ROTATE", "RISK_HIGH" ] }, - { - "type": "overmap_terrain", - "id": "ants", - "name": "ant tunnel", - "color": "brown", - "see_cost": 5, - "flags": [ "LINEAR" ] - }, - { - "type": "overmap_terrain", - "id": "ants_food", - "name": "ant food storage", - "sym": "O", - "color": "green", - "see_cost": 5, - "flags": [ "NO_ROTATE" ] - }, - { - "type": "overmap_terrain", - "id": [ "ants_larvae", "ants_larvae_acid" ], - "name": "ant larva chamber", - "sym": "O", - "color": "white", - "see_cost": 5, - "flags": [ "NO_ROTATE" ] - }, - { - "type": "overmap_terrain", - "id": [ "ants_queen", "ants_queen_acid" ], - "name": "ant queen chamber", - "sym": "O", - "color": "red", - "see_cost": 5, - "flags": [ "NO_ROTATE" ] - }, { "type": "overmap_terrain", "id": "tutorial", diff --git a/data/json/overmap/overmap_terrain/overmap_terrain_transportation.json b/data/json/overmap/overmap_terrain/overmap_terrain_transportation.json index f89d81ccbf21..a73440d8425d 100644 --- a/data/json/overmap/overmap_terrain/overmap_terrain_transportation.json +++ b/data/json/overmap/overmap_terrain/overmap_terrain_transportation.json @@ -37,7 +37,7 @@ "mapgen_end": [ { "method": "builtin", "name": "road_end" } ], "mapgen_tee": [ { "method": "builtin", "name": "road_tee" } ], "mapgen_four_way": [ { "method": "builtin", "name": "road_four_way" } ], - "flags": [ "LINEAR" ] + "flags": [ "LINEAR", "IGNORE_ROTATION_FOR_ADJACENCY" ] }, { "type": "overmap_terrain", diff --git a/data/json/overmap/special_locations.json b/data/json/overmap/special_locations.json index 794d4caf368a..af9961a752af 100644 --- a/data/json/overmap/special_locations.json +++ b/data/json/overmap/special_locations.json @@ -55,6 +55,11 @@ "id": "subterranean", "terrains": [ "cavern", "empty_rock", "rock", "slimepit", "sewer" ] }, + { + "type": "overmap_location", + "id": "subterranean_empty", + "terrains": [ "empty_rock", "rock" ] + }, { "type": "overmap_location", "id": "subterranean_subway", @@ -99,10 +104,5 @@ "type": "overmap_location", "id": "lake_shore", "terrains": [ "lake_shore" ] - }, - { - "type": "overmap_location", - "id": "underground_sub_station", - "terrains": [ "underground_sub_station" ] } ] diff --git a/data/mods/Graphical_Overmap/go_overmap_terrain_hardcoded.json b/data/mods/Graphical_Overmap/go_overmap_terrain_hardcoded.json index b191a9c5b564..44967e38a868 100644 --- a/data/mods/Graphical_Overmap/go_overmap_terrain_hardcoded.json +++ b/data/mods/Graphical_Overmap/go_overmap_terrain_hardcoded.json @@ -151,18 +151,6 @@ "copy-from": "spider_pit_under", "sym": "\u00B0" }, - { - "type": "overmap_terrain", - "id": "anthill", - "copy-from": "anthill", - "sym": "\u00F3" - }, - { - "type": "overmap_terrain", - "id": "acid_anthill", - "copy-from": "acid_anthill", - "sym": "\u00F3" - }, { "type": "overmap_terrain", "id": "slimepit", diff --git a/data/mods/TEST_DATA/overmap_specials.json b/data/mods/TEST_DATA/overmap_specials.json new file mode 100644 index 000000000000..0143031103b8 --- /dev/null +++ b/data/mods/TEST_DATA/overmap_specials.json @@ -0,0 +1,99 @@ +[ + { + "type": "overmap_special", + "id": "test_anthill", + "subtype": "mutable", + "locations": [ "subterranean_empty" ], + "city_distance": [ 10, -1 ], + "occurrences": [ 80, 100 ], + "flags": [ "ANT", "UNIQUE", "CLASSIC", "WILDERNESS" ], + "check_for_locations": [ + [ [ 0, 0, 0 ], [ "land" ] ], + [ [ 0, 0, -1 ], [ "subterranean_empty" ] ], + [ [ 1, 0, -1 ], [ "subterranean_empty" ] ], + [ [ 0, 1, -1 ], [ "subterranean_empty" ] ], + [ [ -1, 0, -1 ], [ "subterranean_empty" ] ], + [ [ 0, -1, -1 ], [ "subterranean_empty" ] ] + ], + "joins": [ + { "id": "surface_to_tunnel", "opposite": "tunnel_to_surface" }, + { "id": "tunnel_to_surface", "opposite": "surface_to_tunnel", "into_locations": [ "land" ] }, + "tunnel_to_tunnel" + ], + "overmaps": { + "surface": { "overmap": "anthill_north", "below": "surface_to_tunnel", "locations": [ "land" ] }, + "below_entrance": { + "overmap": "ants_nesw", + "above": "tunnel_to_surface", + "north": "tunnel_to_tunnel", + "east": "tunnel_to_tunnel", + "south": "tunnel_to_tunnel", + "west": "tunnel_to_tunnel" + }, + "crossroads": { + "overmap": "ants_nesw", + "north": "tunnel_to_tunnel", + "east": "tunnel_to_tunnel", + "south": "tunnel_to_tunnel", + "west": "tunnel_to_tunnel" + }, + "tee": { "overmap": "ants_nes", "north": "tunnel_to_tunnel", "east": "tunnel_to_tunnel", "south": "tunnel_to_tunnel" }, + "straight_tunnel": { "overmap": "ants_ns", "north": "tunnel_to_tunnel", "south": "tunnel_to_tunnel" }, + "corner": { "overmap": "ants_ne", "north": "tunnel_to_tunnel", "east": "tunnel_to_tunnel" }, + "dead_end": { "overmap": "ants_end_south", "north": "tunnel_to_tunnel" } + }, + "root": "surface", + "phases": [ + [ { "overmap": "below_entrance", "max": 1 } ], + [ + { "overmap": "straight_tunnel", "max": 20 }, + { "overmap": "corner", "max": 5 }, + { "overmap": "tee", "max": 10 }, + { "overmap": "below_entrance", "max": { "poisson": 0.7 } } + ], + [ + { "overmap": "dead_end", "weight": 2000 }, + { "overmap": "straight_tunnel", "weight": 100 }, + { "overmap": "corner", "weight": 100 }, + { "overmap": "tee", "weight": 10 }, + { "overmap": "crossroads", "weight": 1 }, + { "overmap": "surface", "weight": 1 } + ] + ] + }, + { + "type": "overmap_special", + "id": "test_crater", + "subtype": "mutable", + "locations": [ "land" ], + "city_distance": [ 0, -1 ], + "occurrences": [ 0, 2 ], + "flags": [ "CLASSIC", "WILDERNESS" ], + "check_for_locations": [ + [ [ 0, 0, 0 ], [ "land" ] ], + [ [ 1, 0, 0 ], [ "land" ] ], + [ [ 0, 1, 0 ], [ "land" ] ], + [ [ -1, 0, 0 ], [ "land" ] ], + [ [ 0, -1, 0 ], [ "land" ] ] + ], + "joins": [ "crater_to_crater" ], + "overmaps": { + "crater_all": { + "overmap": "crater", + "north": "crater_to_crater", + "east": "crater_to_crater", + "south": "crater_to_crater", + "west": "crater_to_crater" + }, + "crater_edge": { + "overmap": "crater", + "north": "crater_to_crater", + "east": { "id": "crater_to_crater", "type": "available" }, + "south": { "id": "crater_to_crater", "type": "available" }, + "west": { "id": "crater_to_crater", "type": "available" } + } + }, + "root": "crater_all", + "phases": [ [ { "overmap": "crater_all", "max": { "poisson": 5 } } ], [ { "overmap": "crater_edge", "weight": 1 } ] ] + } +] diff --git a/doc/src/content/docs/en/mod/json/reference/json_flags.md b/doc/src/content/docs/en/mod/json/reference/json_flags.md index 49fc2f674028..d597cbd5cc71 100644 --- a/doc/src/content/docs/en/mod/json/reference/json_flags.md +++ b/doc/src/content/docs/en/mod/json/reference/json_flags.md @@ -1282,6 +1282,9 @@ These branches are also the valid entries for the categories of `dreams` in `dre NOT be generated, just ID). - `RIVER` It's a river tile. - `SIDEWALK` Has sidewalks on the sides adjacent to roads. +- `IGNORE_ROTATION_FOR_ADJACENCY` When mapgen for this OMT performs neighbour checks, the directions + will be treated as absolute, rather than rotated to account for the rotation of the mapgen itself. + Probably only useful for hardcoded mapgen. - `LAKE` Consider this location to be a valid lake terrain for mapgen purposes. - `LAKE_SHORE` Consider this location to be a valid lake shore terrain for mapgen purposes. - `SOURCE_FUEL` For NPC AI, this location may contain fuel for looting. diff --git a/doc/src/content/docs/en/mod/json/reference/map/mapgen.md b/doc/src/content/docs/en/mod/json/reference/map/mapgen.md index bf56f09ff7b0..c9b8abec6b26 100644 --- a/doc/src/content/docs/en/mod/json/reference/map/mapgen.md +++ b/doc/src/content/docs/en/mod/json/reference/map/mapgen.md @@ -163,6 +163,17 @@ Example: In this example, the "rows" property should be 48x48, with each quadrant of 24x24 being associated with each of the four apartments_mod_tower overmap terrain ids specified. +### "om_terrain" for linear terrain + +Some overmap terrains are _linear_. This is used for things like roads, tunnels, etc. where they +form lines which can connect in various ways. Such terrains are defined with the `LINEAR` flag in +their `overmap_terrain` definition (see the [OVERMAP docs](OVERMAP.md)). + +When defining the JSON mapgen for such terrain, you must define several instances, for each type of +connection that might exist. Each gets a suffix added to the `overmap_terrain` id. The suffixes are: +`_end`, `_straight`, `_curved`, `_tee`, `_four_way`. For an example, see the definitions for `ants` +in [`ants.json`](../data/json/mapgen/bugs/ants.json). + ### Define mapgen "weight" (optional) When the game randomly picks mapgen functions, each function's weight value determines @@ -962,6 +973,48 @@ directly. - "transform": (required, string) the id of the `ter_furn_transform` to run. +### Spawn nested chunks based on overmap neighbors with "place_nested" + +Place_nested allows for limited conditional spawning of chunks based on the `"id"`s of their overmap +neighbors and the joins that were used in placing a mutable overmap special. This is useful for +creating smoother transitions between biome types or to dynamically create walls at the edges of a +mutable structure. + +| Field | Description | +| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| chunks/else_chunks | (required, string) the nested_mapgen_id of the chunk that will be conditionally placed. Chunks are placed if the specified neighbor matches, and "else_chunks" otherwise. | +| x and y | (required, int) the cardinal position in which the chunk will be placed. | +| neighbors | (optional) Any of the neighboring overmaps that should be checked before placing the chunk. Each direction is associated with a list of overmap `"id"` substrings. | +| joins | (optional) Any mutable overmap special joins that should be checked before placing the chunk. Each direction is associated with a list of join `"id"` strings. | +| connections | (optional) Any connection that should be directed toward this overmap before placing the chunk. Each direction is associated with a list of connection `"id"` strings. | +| | | + +The adjacent overmaps which can be checked in this manner are: + +- the direct cardinal neighbors ( `"north"`, `"east"`, `"south"`, `"west"` ), +- the inter cardinal neighbors ( `"north_east"`, `"north_west"`, `"south_east"`, `"south_west"` ), +- the direct vertical neighbors ( `"above"`, `"below"` ). + +Joins can be checked only for the cardinal directions, `"above"`, and `"below"` + +Example: + +```json +"place_nested": [ + { "chunks": [ "concrete_wall_ew" ], "x": 0, "y": 0, "neighbors": { "north": [ "empty_rock", "field" ] } }, + { "chunks": [ "gate_north" ], "x": 0, "y": 0, "joins": { "north": [ "interior_to_exterior" ] } }, + { "else_chunks": [ "concrete_wall_ns" ], "x": 0, "y": 0, "neighbors": { "north_west": [ "field", "microlab" ] } } +], +``` + +The code excerpt above will place chunks as follows: + +- `"concrete_wall_ew"` if the north neighbor is either a field or solid rock +- `"gate_north"` if the join `"interior_to_exterior"` was used to the north during mutable overmap + placement. +- `"concrete_wall_ns"`if the north west neighbor is neither a field nor any of the microlab + overmaps. + ## Rotate the map with "rotation" Rotates the generated map after all the other mapgen stuff has been done. The value can be a single diff --git a/doc/src/content/docs/en/mod/json/reference/map/overmap.md b/doc/src/content/docs/en/mod/json/reference/map/overmap.md index b5bde1e4142b..adc1ead89c68 100644 --- a/doc/src/content/docs/en/mod/json/reference/map/overmap.md +++ b/doc/src/content/docs/en/mod/json/reference/map/overmap.md @@ -194,7 +194,7 @@ referenced overmap terrains (e.g. the `_north` version for all). | Identifier | Description | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `type` | Must be "overmap_terrain". | +| `type` | Must be `overmap_terrain`. | | `id` | Unique id. | | `name` | Name for the location shown in game. | | `sym` | Symbol used when drawing the location, like `"F"` (or you may use an ASCII value like `70`). | @@ -208,11 +208,11 @@ referenced overmap terrains (e.g. the `_north` version for all). | `spawns` | Spawns added once at mapgen. Monster group, % chance, population range (min/max). | | `flags` | See `Overmap terrains` in [JSON_FLAGS.md](../JSON_FLAGS). | | `mapgen` | Specify a C++ mapgen function. Don't do this--use JSON. | -| `mapgen_straight` | Specify a C++ mapgen function for a LINEAR feature variation. | -| `mapgen_curved` | Specify a C++ mapgen function for a LINEAR feature variation. | -| `mapgen_end` | Specify a C++ mapgen function for a LINEAR feature variation. | -| `mapgen_tee` | Specify a C++ mapgen function for a LINEAR feature variation. | -| `mapgen_four_way` | Specify a C++ mapgen function for a LINEAR feature variation. | +| `mapgen_straight` | Specify a C++ mapgen function for a LINEAR feature variation. Prefer JSON instead. | +| `mapgen_curved` | Specify a C++ mapgen function for a LINEAR feature variation. Prefer JSON instead. | +| `mapgen_end` | Specify a C++ mapgen function for a LINEAR feature variation. Prefer JSON instead. | +| `mapgen_tee` | Specify a C++ mapgen function for a LINEAR feature variation. Prefer JSON instead. | +| `mapgen_four_way` | Specify a C++ mapgen function for a LINEAR feature variation. Prefer JSON instead. | ### Example @@ -248,16 +248,43 @@ completed--they are the "non-city" counterpart to the **city_building** type. Th up of multiple overmap terrains (though not always), may have overmap connections (e.g. roads, sewers, subways), and have JSON-defined rules guiding their placement. -### Mandatory Overmap Specials +## Placement rules -There are a finite number of "slots" in which overmap specials can be placed during overmap -generation, defined by the width of the overmap, height of the overmap, and an "overmap special -frequency" (at the time of writing there are 72 "slots" per overmap). As a result, you are -encouraged to exercise restraint when specifying some attributes of the overmap special, such as -required minimum occurrences. The game gives precedence to "mandatory overmap specials" (e.g. those -with a minimum greater than 0) and consequently too many mandatory overmap specials may exhaust the -number of slots before any optional specials can even attempt placement. As a general rule, the -minimum should be 0. +For each special, mapgen first decides how many instances it wants to place by rolling a random +number between `min` and `max` of its occurrences. This number will be adjusted with a few +multipliers: configured specials density, terrain ratio, and crowdedness ratio. + +The "terrain ratio" is a number representing the ratio between land and lake tiles on the overmap. A +30% flooded overmap will have a 0.7 multiplier for land specials and a 0.3 multiplier for lake +specials. + +The "crowdedness ratio" is a number representing the ratio between the total overmap area and the +expected average area required to place all specials with the current range and density settings. It +is capped and normally stays at x1. However, trying to spawn more specials than physically possible +will cause it to decrease. + +After considering all these factors, it may result in a number such as 2.4, which means that the +overmap will have a 60% chance to place 2 instances and a 40% chance to place 3 instances of that +special. The final number will never be lower than the raw `min` value of occurrences. + +Once the exact amount is chosen, mapgen will search for places to spawn specials. If a special +depends on a city, its instances will be distributed in the vicinity of different matching cities. +For example, if mapgen wants to place three instances and the overmap has two cities of the required +size, it won't place more than two instances near the same city. + +### Fixed vs mutable specials + +There are two subtypes of overmap special: fixed and mutable. Fixed overmap specials have a fixed +defined layout which can span many OMTs, and can rotate (see below) but will always look essentially +the same. + +Mutable overmap specials have a more flexible layout. They are defined in terms of a collection of +overmap terrains and the ways they can fit together, like a jigsaw puzzle where pieces can fit +together in multiple ways. This can be used to form more organic shapes, such as tunnels dug +underground by giant ants, or larger sprawling buildings with mismatched wings and extensions. + +Mutable specials require a lot more care to design such that they can be reliably placed without +error. ### Rotation @@ -282,10 +309,11 @@ level value and then only specify it for individual entries that differ. | Identifier | Description | | --------------- | ----------------------------------------------------------------------------------------------------- | -| `type` | Must be "overmap_special". | +| `type` | Must be `"overmap_special"`. | | `id` | Unique id. | -| `overmaps` | List of overmap terrains and their relative `[ x, y, z ]` location within the special. | | `connections` | List of overmap connections and their relative `[ x, y, z ]` location within the special. | +| `place_nested` | Array of `{ "point": [x, y, z], "special": id }` with nested specials, relative to this one. | +| `subtype` | Either `"fixed"` or `"mutable"`. Defaults to `"fixed"` if not specified. | | `locations` | List of `overmap_location` ids that the special may be placed on. | | `city_distance` | Min/max distance from a city that the special may be placed. Use -1 for unbounded. | | `city_sizes` | Min/max city size for a city that the special may be placed near. Use -1 for unbounded. | @@ -293,7 +321,26 @@ level value and then only specify it for individual entries that differ. | `flags` | See `Overmap specials` in [JSON_FLAGS.md](../JSON_FLAGS). | | `rotate` | Whether the special can rotate. True if not specified. | -### Example +Depending on the subtype, there are further relevant fields: + +#### Further fields for fixed overmap specials + +| Identifier | Description | +| ---------- | -------------------------------------------------------------------------------------- | +| `overmaps` | List of overmap terrains and their relative `[ x, y, z ]` location within the special. | + +#### Further fields for mutable overmap specials + +| Identifier | Description | +| -------------------------- | ------------------------------------------------------------------------------------------------------------------ | +| `check_for_locations` | List of pairs `[ [ x, y, z ], [ locations, ... ] ]` defining the locations that must exist for initial placement. | +| `check_for_locations_area` | List of check_for_locations area objects to be considered in addition to the explicit `check_for_locations` pairs. | +| `overmaps` | Definitions of the various overmaps and how they join to one another. | +| `root` | The initial overmap from which the mutable special will be grown. | +| `shared` | List of multipliers defined as `"id": value` that can be used to scale some parts of the special. | +| `phases` | A specification of how to grow the overmap special from the root OMT. | + +### Example fixed special ```json [ @@ -317,7 +364,7 @@ level value and then only specify it for individual entries that differ. ] ``` -### Overmaps +### Fixed special overmaps | Identifier | Description | | ----------- | -------------------------------------------------------------------------- | @@ -333,6 +380,413 @@ level value and then only specify it for individual entries that differ. | `connection` | Id of the `overmap_connection` to build. | | `from` | Optional point `[ x, y, z]` within the special to treat as the origin of the connection. | +### Example mutable special + +```json +[ + { + "type": "overmap_special", + "id": "anthill", + "subtype": "mutable", + "locations": ["subterranean_empty"], + "city_distance": [25, -1], + "city_sizes": [0, 20], + "occurrences": [0, 1], + "flags": ["CLASSIC", "WILDERNESS"], + "check_for_locations": [ + [[0, 0, 0], ["land"]], + [[0, 0, -1], ["subterranean_empty"]], + [[1, 0, -1], ["subterranean_empty"]], + [[0, 1, -1], ["subterranean_empty"]], + [[-1, 0, -1], ["subterranean_empty"]], + [[0, -1, -1], ["subterranean_empty"]] + ], + "//1": "Same as writing out 'check_for_locations' 9 times with different points.", + "check_for_locations_area": [ + [ { "type": [ "subterranean_empty" ], "from": [ 1, 1, -2 ], "to": [ -1, -1, -2 ] } ] + ], + "//2": "The anthill will have 3 possible sizes", + "shared": { "size": [ 1, 3 ] }, + "joins": ["surface_to_tunnel", "tunnel_to_tunnel"], + "overmaps": { + "surface": { "overmap": "anthill", "below": "surface_to_tunnel", "locations": ["land"] }, + "below_entrance": { + "overmap": "ants_nesw", + "above": "surface_to_tunnel", + "north": "tunnel_to_tunnel", + "east": "tunnel_to_tunnel", + "south": "tunnel_to_tunnel", + "west": "tunnel_to_tunnel" + }, + "crossroads": { + "overmap": "ants_nesw", + "north": "tunnel_to_tunnel", + "east": "tunnel_to_tunnel", + "south": "tunnel_to_tunnel", + "west": "tunnel_to_tunnel" + }, + "tee": { + "overmap": "ants_nes", + "north": "tunnel_to_tunnel", + "east": "tunnel_to_tunnel", + "south": "tunnel_to_tunnel" + }, + "straight_tunnel": { + "overmap": "ants_ns", + "north": "tunnel_to_tunnel", + "south": "tunnel_to_tunnel" + }, + "corner": { "overmap": "ants_ne", "north": "tunnel_to_tunnel", "east": "tunnel_to_tunnel" }, + "dead_end": { "overmap": "ants_end_south", "north": "tunnel_to_tunnel" }, + "queen": { "overmap": "ants_queen", "north": "tunnel_to_tunnel" }, + "larvae": { "overmap": "ants_larvae", "north": "tunnel_to_tunnel" }, + "food": { "overmap": "ants_food", "north": "tunnel_to_tunnel" } + }, + "root": "surface", + "phases": [ + [{ "overmap": "below_entrance", "max": 1 }], + [ + "//1": "Shared multiplier 'size' will affect the size", + "//2": "of the tunnel system generated in this phase.", + { "overmap": "straight_tunnel", "max": 10, "scale": "size" }, + { "overmap": "corner", "max": { "poisson": 2.5 }, "scale": "size" }, + { "overmap": "tee", "max": 5, "scale": "size" } + ], + [{ "overmap": "queen", "max": 1 }], + [{ "overmap": "food", "max": 5 }, { "overmap": "larvae", "max": 5 }], + [ + { "overmap": "dead_end", "weight": 2000 }, + { "overmap": "straight_tunnel", "weight": 100 }, + { "overmap": "corner", "weight": 100 }, + { "overmap": "tee", "weight": 10 }, + { "overmap": "crossroads", "weight": 1 } + ] + ] + } +] +``` + +### How mutable specials are placed + +#### Overmaps and joins + +Note: "overmap" in the following context should not be confused with "overmap level" or "overmap" as +a 180x180 chunk of OMTs that the near-infinite world map is divided into. + +A mutable special has a collection of _overmaps_ which define the OMTs used to build it and _joins_ +which define the way in which they are permitted to connect with one another. Each overmap may +specify a join for each of its edges (four cardinal directions, above, and below). These joins must +match the opposite join for the adjacent overmap in that direction. + +In the above example, we see that the `surface` overmap specifies `"below": "surface_to_tunnel"`, +meaning that the join below it must be `surface_to_tunnel`. So, the overmap below must specify +`"above": "surface_to_tunnel"`. Only the `below_entrance` overmap does that, so we know that overmap +must be placed beneath the `surface` overmap. + +Overmaps can always be rotated, so a `north` constraint can correspond to other directions. So, the +above `dead_end` overmap can represent a dead end tunnel in any direction, but it's important that +the chosen OMT `ants_end_south` is consistent with the `north` join for the generated map to make +sense. + +#### Layout phases + +After all the joins and overmaps are defined, the manner in which the special is laid out is given +by `root` and `phases`. + +`root` specifies the overmap which is placed first, at the origin point for this special. + +Then `phases` gives a list of growth phases used to place more overmaps. These phases are processed +strictly in order. + +Each _phase_ is a list of rules. Each _rule_ specifies an overmap and an integer `max` and/or +`weight`. + +Weight must always be a simple integer, but `max` may also be an object defining a probability +distribution over integers. Each time the special is spawned, a value is sampled from that +distribution. Poisson distribution is supported via an object such as `{ "poisson": 5 }` where 5 +will be the mean of the distribution (λ). Flat distribution is supported via `[min, max]` pairs. +`max` can also be `scale`d by another multiplier `shared` within the special, allowing to scale +amounts of multiple different overmaps proportionaly to each other. Each `shared` multiplier can be +defined as a probability distribution same way as the `max` value, and it is also sampled once on +special placement. + +Within each phase, the game looks for unsatisfied joins from the existing overmaps and attempts to +find an overmap from amongst those available in its rules to satisfy that join. Priority is given to +whichever joins are listed first in the list which defines the joins for this special, but if +multiple joins of the same (highest priority) id are present then one is chosen at random. + +First the rules are filtered to contain only those which can satisfy the joins for a particular +location, and then a weighted selection from the filtered list is made. The weight is given by the +minimum of `max` and `weight` specified in the rule. The difference between `max` and `weight` is +that each time a rule is used, `max` is decremented by one. Therefore, it limits the number of times +that rule can be chosen. Rules which only specify `weight` can be chosen an arbitrary number of +times. + +If no rule in the current phase is able to satisfy the joins for a particular location, that +location is set aside to be tried again in later phases. + +Once all joins are either satisfied or set aside, the phase ends and generation proceeds to the next +phase. + +If all phases complete and unsatisfied joins remain, this is considered an error and a debugmsg will +be displayed with more details. + +#### Chunks + +A placement rule in the phases can specify multiple overmaps to be placed in a particular +configuration. This is useful if you want to place some feature that's larger than a single OMT. +Here is an example from the microlab: + +```json +{ + "name": "subway_chunk_at_-2", + "chunk": [ + { "overmap": "microlab_sub_entry", "pos": [0, 0, 0], "rot": "north" }, + { "overmap": "microlab_sub_station", "pos": [0, -1, 0] }, + { "overmap": "microlab_subway", "pos": [0, -2, 0] } + ], + "max": 1 +} +``` + +The `"name"` of a chunk is only for debugging messages when something goes wrong. `"max"` and +`"weight"` are handled as above. + +The new feature is `"chunk"` which specifies a list of overmaps and their relative positions and +rotations. The overmaps are taken from the ones defined for this special. Rotation of `"north"` is +the default, so specifying that has no effect, but it's included here to demonstrate the syntax. + +The postions and rotations are relative. The chunk can be placed at any offset and rotation, so long +as all the overmaps are shifted and rotated together like a rigid body. + +#### Techniques to avoid placement errors + +To help avoid these errors, some additional features of the mutable special placement can help you. + +##### `check_for_locations` + +`check_for_locations` defines a list of extra constraints that are checked before the special is +attempted to be placed. Each constraint is a pair of a position (relative to the root) and a set of +locations. The existing OMT in each postion must fall into one of the given locations, else the +attempted placement is aborted. + +The `check_for_locations` constraints ensure that the `below_entrance` overmap can be placed below +the root and that all four cardinal-adjacent OMTs are `subterranean_empty`, which is needed to add +any further overmaps satisfying the four other joins of `below_entrance`. + +`check_for_locations_area` lets you define an area to check instead of having to repeat +`check_for_locations` for individual points. + +##### `into_locations` + +Each join also has an associated list of locations. This defaults to the locations for the special, +but it can be overridden for a particular join like this: + +```json +"joins": [ + { "id": "surface_to_surface", "into_locations": [ "land" ] }, + "tunnel_to_tunnel" +] +``` + +For an overmap to be placed when satisfying an unresolved join, it is not sufficient for it to +satisfy the existing joins adjacent to a particular location. Any residual joins it possesses beyond +those which already match up must point to OMTs with terrains consistent with that join's locations. + +For the particular case of the anthill example above, we can see how these two additions ensure that +placement is always successful and no unsatisfied joins remain. + +The next few phases of placement will attempt to place various tunnels. The join constraints will +ensure that the unsatisfied joins (the open ends of tunnels) will always point into +`subterranean_empty` OMTs. + +##### Ensuring complete coverage in the final phase + +In the final phase, we have five different rules intended to cap off any unsatisfied joins without +growing the anthill further. It is important that the rules whose overmaps have fewer joins get +higher weights. In the normal case, every unsatisfied join will be simply closed off using +`dead_end`. However, we also have to cater for the possibility that two unsatisfied joins point to +the same OMT, in which case `dead_end` will not fit (and will be filtered out), but +`straight_tunnel` or `corner` will, and one of those will likely be chosen since they have the +highest weights. We don't want `tee` to be chosen (even though it might fit) because that would lead +to a new unsatisfied join and further grow the tunnels. But it's not a big problem if `tee` is +chosen occasionally in this situation; the new join will likely simply be satisfied using +`dead_end`. + +When designing your own mutable overmap specials, you will have to think through these permutations +to ensure that all joins will be satisfied by the end of the last phase. + +#### Optional joins + +Rather than having lots of rules designed to satisfy all possible situations in the final phase, in +some situations you can make this easier using optional joins. This feature can also be used in +other phases. + +When specifying the joins associated with an overmap in a mutable special, you can elaborate with a +type, like this example from the `Crater` overmap special: + +```json +"overmaps": { + "crater_core": { + "overmap": "crater_core", + "north": "crater_to_crater", + "east": "crater_to_crater", + "south": "crater_to_crater", + "west": "crater_to_crater" + }, + "crater_edge": { + "overmap": "crater", + "north": "crater_to_crater", + "east": { "id": "crater_to_crater", "type": "available" }, + "south": { "id": "crater_to_crater", "type": "available" }, + "west": { "id": "crater_to_crater", "type": "available" } + } +}, +``` + +The definition of `crater_edge` has one mandatory join to the north, and three 'available' joins to +the other cardinal directions. The semantics of an 'available' join are that it will not be +considered an unresolved join, and therefore will never cause more overmaps to be placed, but it can +satisfy other joins into a particular tile when necessary to allow an existing unresolved join to be +satisfied. + +The overmap will always be rotated in such a way that as many of its mandatory joins as possible are +satisfied and available joins are left to point in other directions that don't currently need joins. + +As such, this `crater_edge` overmap can satisfy any unresolved joins for the `Crater` special +without generating any new unresolved joins of its own. This makes it great to finish off the +special in the final phase. + +Third type of joins - 'optional', it's a mix of both above - they do actively generate new +unresolved joins to build upon, but such joins are not mandatory, and can be left unvesolved. + +Since 'optional' and 'available' joins does not require any kind of resolving they may end in +undesired places, that can be preventrd by adding pseudo-join of 'reject' type, which will forbid +other joins of same id linking to it. + +#### Asymmetric joins + +Sometimes you want two different OMTs to connect, but wouldn't want either to connect with +themselves. In this case you wouldn't want to use the same join on both. Instead, you can define two +joins which form a pair, by specifying one as the opposite of the other. + +Another situation where this can arise is when the two sides of a join need different location +constraints. For example, in the anthill, the surface and subterranean components need different +locations. We could improve the definition of its joins by making the join between surface and +tunnels asymmetric, like this: + +```json +"joins": [ + { "id": "surface_to_tunnel", "opposite": "tunnel_to_surface" }, + { "id": "tunnel_to_surface", "opposite": "surface_to_tunnel", "into_locations": [ "land" ] }, + "tunnel_to_tunnel" +], +``` + +As you can see, the `tunnel_to_surface` part of the pair needs to override the default value of +`into_locations` because it points towards the surface. + +#### Alternative joins + +Sometimes you want the next phase(s) of a mutable special to be able to link to existing unresolved +joins without themselves generating any unresolved joins of that type. This helps to create a clean +break between the old and the new. + +For example, this happens in the `microlab_mutable` special. This special has some structured +`hallway` OMTs surrounded by a clump of `microlab` OMTs. The hallways have `hallway_to_microlab` +joins pointing out to their sides, so we need `microlab` OMTs to have `microlab_to_hallway` joins +(the opposite of `hallway_to_microlab`) in order to match them. + +However, we don't want the unresolved edges of a `microlab` OMT to require more hallways all around, +so we mostly want them to use `microlab_to_microlab` joins. How can we satisfy these apparently +conflicting requirements without making many different variants of `microlab` with different numbers +of each type of join? Alternative joins can help us here. + +The definition of the `microlab` overmap might look like this: + +```json +"microlab": { + "overmap": "microlab_generic", + "north": { "id": "microlab_to_microlab", "alternatives": [ "microlab_to_hallway" ] }, + "east": { "id": "microlab_to_microlab", "alternatives": [ "microlab_to_hallway" ] }, + "south": { "id": "microlab_to_microlab", "alternatives": [ "microlab_to_hallway" ] }, + "west": { "id": "microlab_to_microlab", "alternatives": [ "microlab_to_hallway" ] } +}, +``` + +This allows it to join with hallways which are already placed on the overmap, but new unresolved +joins will only match more `microlab`s. + +#### Testing your new mutable special + +If you want to exhaustively test your mutable special for placement errors, and you are in a +position to compile the game, then an easy way to do so is to use the existing test in +`tests/overmap_test.cpp`. + +In that file, look for `TEST_CASE( "mutable_overmap_placement"`. At the start of that function there +is a list of mutable special ids that tests tries spawning. Replace one of them with your new +special's id, recompile and run the test. + +The test will attempt to place your special a few thousand times, and should find most ways in which +placement might fail. + +### Joins + +A join definition can be a simple string, which will be its id. Alternatively, it can be a +dictionary with some of these keys: + +| Identifier | Description | +| ---------------- | ------------------------------------------------------------------- | +| `id` | Id of the join being defined. | +| `opposite` | Id of the join which must match this one from the adjacent terrain. | +| `into_locations` | List of `overmap_location` ids that this join may point towards. | + +### Mutable special overmaps + +The overmaps are a JSON dictionary. Each overmap must have an id (local to this special) which is +the JSON dictionary key, and then the fields within the value may be: + +| Identifier | Description | +| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `overmap` | Id of the `overmap_terrain` to place at the location. | +| `locations` | List of `overmap_location` ids that this overmap terrain may be placed on. If not specified, defaults to the `locations` value from the special definition. | +| `north` | Join which must align with the north edge of this OMT | +| `east` | Join which must align with the east edge of this OMT | +| `south` | Join which must align with the south edge of this OMT | +| `west` | Join which must align with the west edge of this OMT | +| `above` | Join which must link this to the OMT above | +| `below` | Join which must link this to the OMT below | + +Each join associated with a direction can be a simple string, interpreted as a join id. +Alternatively it can be a JSON object with the following keys: + +| Identifier | Description | +| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | Id of the join used here. | +| `type` | Either `"mandatory"` or `"available"`. Default: `"mandatory"`. | +| `alternatives` | List of join ids that may be used instead of the one listed under `id`, but only when placing this overmap. Unresolved joins created by its placement will only be the primary join `id`. | + +### Generation rules + +| Identifier | Description | +| -------------------- | --------------------------------------------------------------------------------------------------------------- | +| `overmap` or `chunk` | Id of the `overmap` to place, chunk configuration. | +| `connections` | List of overmap connections and their relative `[ x, y, z ]` location to overmap or chunk. | +| `join` | Id of `join` which must be resolved during current phase. | +| `z` | Z level restrictions for this phase. | +| `om_pos` | Absolute coordinates `[ x, y ]` of the overmap (180x180 chunk of the world) this phase is allowed to run in. | +| `rotate` | True or false, whether current piece can be rotated or not. Takes a priority over special's rotatable property. | +| `max` | Maximum number of times this rule should be used. | +| `scale` | Id of the shared multiplier to scale the `max` by. | +| `weight` | Weight with which to select this rule. | + +Z level restrictions supports numbers, `["min", "mix"]` ranges with absolute coordinate +restrictions, "top"\"bottom" strings refering to boundaries of other tiles of special placed so far, +and objects with "top"\"bottom" properties and offsets from above boundaries. + +One of `max` and `weight` must be specified. `max` will be used as the weight when `weight` is not +specified. + ## City Building A city building is an entity that is placed in the overmap during the city generation process--they @@ -392,6 +846,10 @@ the frequency assigned to the city building within the `region_settings`. Consul | `id` | Unique id. | | `default_terrain` | Default `overmap_terrain` to use for undirected connections and existance checks. | | `subtypes` | List of entries used to determine valid locations, terrain cost, and resulting overmap terrain. | +| `layout` | (Optional) Connections layout, default is `city`. | + +With `city` layout each connection point will be linked to the center of closest city. With `p2p` +layout each connection point will be linked to the closest connection of same type. ### Example diff --git a/src/all_enum_values.h b/src/all_enum_values.h new file mode 100644 index 000000000000..232a59fa3513 --- /dev/null +++ b/src/all_enum_values.h @@ -0,0 +1,27 @@ +#pragma once +#ifndef CATA_SRC_ALL_ENUM_VALUES_H +#define CATA_SRC_ALL_ENUM_VALUES_H + +#include "enum_traits.h" + +template +constexpr size_t num_enum_values() +{ + return static_cast( enum_traits::last ); +} + +template +auto all_enum_values_helper( std::index_sequence ) -> +const std::array()> & +{ + static constexpr std::array()> result{ static_cast( I )... }; + return result; +} + +template +auto all_enum_values() -> const std::array()> & +{ + return all_enum_values_helper( std::make_index_sequence()> {} ); +} + +#endif // CATA_SRC_ALL_ENUM_VALUES_H \ No newline at end of file diff --git a/src/cata_tiles.cpp b/src/cata_tiles.cpp index c3cb5bc35d4c..a3ce874a938f 100644 --- a/src/cata_tiles.cpp +++ b/src/cata_tiles.cpp @@ -3677,7 +3677,7 @@ void cata_tiles::draw_sct_frame( std::multimap &overlay_s const auto direction = iter->getDirecton(); // Compensate for string length offset added at SCT creation // (it will be readded using font size and proper encoding later). - const int direction_offset = ( -direction_XY( direction ).x + 1 ) * full_text_length / 2; + const int direction_offset = ( -displace_XY( direction ).x + 1 ) * full_text_length / 2; overlay_strings.emplace( player_to_screen( iD + point( direction_offset, 0 ) ), diff --git a/src/cube_direction.h b/src/cube_direction.h new file mode 100644 index 000000000000..6eb2b49aacdd --- /dev/null +++ b/src/cube_direction.h @@ -0,0 +1,45 @@ +#ifndef CATA_SRC_CUBE_DIRECTION_H +#define CATA_SRC_CUBE_DIRECTION_H + +#include + +#include "enum_traits.h" + +namespace om_direction +{ +enum class type : int; +} // namespace om_direction + +// We have other direction enums, but for this purpose we need to have one for +// the six rectilinear directions. These correspond to the faces of a cube, so +// I've called it cube_direction +enum class cube_direction : int { + north, + east, + south, + west, + above, + below, + last +}; + +template<> +struct enum_traits { + static constexpr cube_direction last = cube_direction::last; +}; + +namespace std +{ +template <> struct hash { + std::size_t operator()( const cube_direction &d ) const { + return static_cast( d ); + } +}; +} // namespace std + +cube_direction operator+( cube_direction, om_direction::type ); +cube_direction operator+( cube_direction, int i ); +cube_direction operator-( cube_direction, om_direction::type ); +cube_direction operator-( cube_direction, int i ); + +#endif // CATA_SRC_CUBE_DIRECTION_H diff --git a/src/distribution.cpp b/src/distribution.cpp new file mode 100644 index 000000000000..1418cd63fb30 --- /dev/null +++ b/src/distribution.cpp @@ -0,0 +1,147 @@ +#include "distribution.h" + +#include + +#include "json.h" +#include "rng.h" + +struct int_distribution_impl { + virtual ~int_distribution_impl() = default; + virtual int minimum() const = 0; + virtual int sample( int scale = 1 ) = 0; + virtual std::string description() const = 0; +}; + +struct fixed_distribution : int_distribution_impl { + int value; + + explicit fixed_distribution( int v ) + : value( v ) + {} + + int minimum() const override { + return value; + } + + int sample( int scale ) override { + return value * scale; + } + + std::string description() const override { + return string_format( "Fixed(%d)", value ); + } +}; + +struct range_distribution : int_distribution_impl { + int min; + int max; + + explicit range_distribution( int min, int max ) + : min( min ) + , max( max ) + {} + + int minimum() const override { + return std::min( min, max ); + } + + int sample( int scale ) override { + return rng( min * scale, max * scale ); + } + + std::string description() const override { + return string_format( "Range(%d - %d)", min, max ); + } +}; + +struct poisson_distribution : int_distribution_impl { + double mean; + + explicit poisson_distribution( double mean ) + : mean( mean ) + {} + + int minimum() const override { + return 0; + } + + int sample( int scale ) override { + std::poisson_distribution dist( mean * scale ); + return dist( rng_get_engine() ); + } + + std::string description() const override { + std::poisson_distribution dist( mean ); + return string_format( "Poisson(%.0f)", dist.mean() ); + } +}; + +struct chance_distribution : int_distribution_impl { + double chance; + + explicit chance_distribution( double chance ) + : chance( chance ) + {} + + int minimum() const override { + return 0; + } + + int sample( int scale ) override { + return ( chance * scale ) >= rng_float( 0, 1 ) ? 1 : 0; + } + + std::string description() const override { + return string_format( "Chance(%.2f)", chance ); + } +}; + +int_distribution::int_distribution() + : impl_( make_shared_fast( 0 ) ) +{} + +int_distribution::int_distribution( int v ) + : impl_( make_shared_fast( v ) ) +{} + +int int_distribution::minimum() const +{ + return impl_->minimum(); +} + +int int_distribution::sample( int scale ) const +{ + return impl_->sample( scale ); +} + +std::string int_distribution::description() const +{ + return impl_->description(); +} + +void int_distribution::deserialize( JsonIn &jsin ) +{ + if( jsin.test_int() ) { + int v = jsin.get_int(); + impl_ = make_shared_fast( v ); + } else if( jsin.test_object() ) { + JsonObject jo = jsin.get_object(); + if( jo.has_member( "poisson" ) ) { + double mean = jo.get_float( "poisson", true ); + impl_ = make_shared_fast( mean ); + } else if( jo.has_member( "chance" ) ) { + double chance = jo.get_float( "chance", true ); + impl_ = make_shared_fast( chance ); + } else { + jo.throw_error( R"(Expected "poisson" member)" ); + } + } else if( jsin.test_array() ) { + JsonArray ja = jsin.get_array(); + if( ja.size() != 2 ) { + ja.throw_error( "Range should be in format [min, max]." ); + } + impl_ = make_shared_fast( ja.get_int( 0 ), ja.get_int( 1 ) ); + } else { + jsin.error( "expected number, array, or object" ); + } +} diff --git a/src/distribution.h b/src/distribution.h new file mode 100644 index 000000000000..77b773a35bc4 --- /dev/null +++ b/src/distribution.h @@ -0,0 +1,27 @@ +#ifndef CATA_SRC_DISTRIBUTION_H +#define CATA_SRC_DISTRIBUTION_H + +#include "memory_fast.h" + +class JsonIn; + +struct int_distribution_impl; + +// This represents a probability distribution over the integers, which is +// abstract and can be read from a JSON definition +class int_distribution +{ + public: + int_distribution(); + explicit int_distribution( int value ); + + int minimum() const; + int sample( int scale = 1 ) const; + std::string description() const; + + void deserialize( JsonIn & ); + private: + shared_ptr_fast impl_; +}; + +#endif // CATA_SRC_DISTRIBUTION_H diff --git a/src/game.cpp b/src/game.cpp index 8f3f0bc3456f..f8eae41c7f2a 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -9294,7 +9294,7 @@ point game::place_player( const tripoint &dest_loc ) }; for( auto &elem : adjacentDir ) { - forage( u.pos() + direction_XY( elem ) ); + forage( u.pos() + displace_XY( elem ) ); } } @@ -9328,7 +9328,7 @@ point game::place_player( const tripoint &dest_loc ) if( pulp_butcher == "pulp_adjacent" ) { for( auto &elem : adjacentDir ) { - pulp( u.pos() + direction_XY( elem ) ); + pulp( u.pos() + displace_XY( elem ) ); } } else { pulp( u.pos() ); diff --git a/src/line.cpp b/src/line.cpp index f21d8ce4d433..2701b5f4ae5e 100644 --- a/src/line.cpp +++ b/src/line.cpp @@ -430,7 +430,69 @@ direction direction_from( const tripoint &p, const tripoint &q ) return direction_from( q - p ); } -point direction_XY( const direction dir ) +tripoint displace( direction dir ) +{ + switch( dir ) { + case direction::NORTHWEST: + return tripoint_north_west; + case direction::ABOVENORTHWEST: + return point_north_west + tripoint_above; + case direction::BELOWNORTHWEST: + return point_north_west + tripoint_below; + case direction::NORTH: + return tripoint_north; + case direction::ABOVENORTH: + return point_north + tripoint_above; + case direction::BELOWNORTH: + return point_north + tripoint_below; + case direction::NORTHEAST: + return tripoint_north_east; + case direction::ABOVENORTHEAST: + return point_north_east + tripoint_above; + case direction::BELOWNORTHEAST: + return point_north_east + tripoint_below; + case direction::WEST: + return tripoint_west; + case direction::ABOVEWEST: + return point_west + tripoint_above; + case direction::BELOWWEST: + return point_west + tripoint_below; + case direction::CENTER: + return tripoint_zero; + case direction::ABOVECENTER: + return tripoint_above; + case direction::BELOWCENTER: + return tripoint_below; + case direction::EAST: + return tripoint_east; + case direction::ABOVEEAST: + return point_east + tripoint_above; + case direction::BELOWEAST: + return point_east + tripoint_below; + case direction::SOUTHWEST: + return tripoint_south_west; + case direction::ABOVESOUTHWEST: + return point_south_west + tripoint_above; + case direction::BELOWSOUTHWEST: + return point_south_west + tripoint_below; + case direction::SOUTH: + return tripoint_south; + case direction::ABOVESOUTH: + return point_south + tripoint_above; + case direction::BELOWSOUTH: + return point_south + tripoint_below; + case direction::SOUTHEAST: + return tripoint_south_east; + case direction::ABOVESOUTHEAST: + return point_south_east + tripoint_above; + case direction::BELOWSOUTHEAST: + return point_south_east + tripoint_below; + } + + return tripoint_zero; +} + +point displace_XY( const direction dir ) { switch( dir % 9 ) { case direction::NORTHWEST: diff --git a/src/line.h b/src/line.h index d21be35c9e02..ddb830f91c43 100644 --- a/src/line.h +++ b/src/line.h @@ -109,7 +109,8 @@ direction direction_from( const tripoint &p ) noexcept; direction direction_from( point p1, point p2 ) noexcept; direction direction_from( const tripoint &p, const tripoint &q ); -point direction_XY( direction dir ); +tripoint displace( direction dir ); +point displace_XY( direction dir ); std::string direction_name( direction dir ); std::string direction_name_short( direction dir ); diff --git a/src/map.h b/src/map.h index 8a6df2e79732..79804e4172c9 100644 --- a/src/map.h +++ b/src/map.h @@ -1761,7 +1761,6 @@ class map void draw_lab( mapgendata &dat ); void draw_temple( mapgendata &dat ); void draw_mine( mapgendata &dat ); - void draw_anthill( mapgendata &dat ); void draw_slimepit( mapgendata &dat ); void draw_triffid( mapgendata &dat ); void draw_connections( mapgendata &dat ); diff --git a/src/mapgen.cpp b/src/mapgen.cpp index c829db17667e..038a9414cb7d 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -14,6 +14,7 @@ #include #include +#include "all_enum_values.h" #include "basecamp.h" #include "calendar.h" #include "catacharset.h" @@ -56,6 +57,7 @@ #include "options.h" #include "overmap.h" #include "overmapbuffer.h" +#include "overmap_connection.h" #include "player.h" #include "point.h" #include "point_float.h" @@ -448,6 +450,9 @@ load_mapgen_function( const JsonObject &jio, point offset, point total ) jio.throw_error( "function does not exist", "name" ); } } else if( mgtype == "json" ) { + if( !jio.has_object( "object" ) ) { + jio.throw_error( R"(mapgen with method "json" must define key "object")" ); + } JsonObject jo = jio.get_object( "object" ); const json_source_location jsrc = jo.get_source_location(); jo.allow_omitted_members(); @@ -1802,26 +1807,36 @@ static void load_weighted_entries( const JsonObject &jsi, const std::string &jso class jmapgen_nested : public jmapgen_piece { private: - class neighborhood_check + class neighbor_oter_check { private: // To speed up the most common case: no checks bool has_any = false; - std::array, om_direction::size> neighbors; - std::set above; + std::array, om_direction::size> neighbors; + std::set above; public: - neighborhood_check( const JsonObject &jsi ) { + neighbor_oter_check( const JsonObject &jsi ) { for( om_direction::type dir : om_direction::all ) { int index = static_cast( dir ); - neighbors[index] = jsi.get_tags( om_direction::id( dir ) ); + neighbors[index] = jsi.get_tags( io::enum_to_string( dir ) ); has_any |= !neighbors[index].empty(); - above = jsi.get_tags( "above" ); + above = jsi.get_tags( "above" ); has_any |= !above.empty(); } } - bool test( mapgendata &dat ) const { + void check( const std::string &oter_name ) const { + for( const std::set &p : neighbors ) { + for( const oter_type_str_id &id : p ) { + if( !id.is_valid() ) { + debugmsg( "Invalid oter_type_str_id '%s' in %s", id.str(), oter_name ); + } + } + } + } + + bool test( const mapgendata &dat ) const { if( !has_any ) { return true; } @@ -1829,14 +1844,14 @@ class jmapgen_nested : public jmapgen_piece bool all_directions_match = true; for( om_direction::type dir : om_direction::all ) { int index = static_cast( dir ); - const std::set &allowed_neighbors = neighbors[index]; + const std::set &allowed_neighbors = neighbors[index]; if( allowed_neighbors.empty() ) { continue; // no constraints on this direction, skip. } bool this_direction_matches = false; - for( const oter_str_id &allowed_neighbor : allowed_neighbors ) { + for( const oter_type_str_id &allowed_neighbor : allowed_neighbors ) { this_direction_matches |= is_ot_match( allowed_neighbor.str(), dat.neighbor_at( dir ).id(), ot_match_type::contains ); } @@ -1845,7 +1860,7 @@ class jmapgen_nested : public jmapgen_piece if( !above.empty() ) { bool above_matches = false; - for( const oter_str_id &allowed_neighbor : above ) { + for( const oter_type_str_id &allowed_neighbor : above ) { above_matches |= is_ot_match( allowed_neighbor.str(), dat.above().id(), ot_match_type::contains ); } all_directions_match &= above_matches; @@ -1855,16 +1870,112 @@ class jmapgen_nested : public jmapgen_piece } }; + class neighbor_join_check + { + private: + std::unordered_map> neighbors; + public: + explicit neighbor_join_check( const JsonObject &jsi ) { + for( cube_direction dir : all_enum_values() ) { + cata::flat_set dir_neighbours = + jsi.get_tags>( + io::enum_to_string( dir ) ); + if( !dir_neighbours.empty() ) { + neighbors[dir] = std::move( dir_neighbours ); + } + } + } + + void check( const std::string & ) const { + // TODO: check join ids are valid + } + + bool test( const mapgendata &dat ) const { + for( const std::pair> &p : + neighbors ) { + const cube_direction dir = p.first; + const cata::flat_set &allowed_joins = p.second; + + assert( !allowed_joins.empty() ); + + bool this_direction_matches = false; + for( const std::string &allowed_join : allowed_joins ) { + this_direction_matches |= dat.has_join( dir, allowed_join ); + } + if( !this_direction_matches ) { + return false; + } + } + return true; + } + }; + + class neighbor_connection_check + { + private: + std::unordered_map> neighbors; + public: + neighbor_connection_check( const JsonObject &jsi ) { + for( om_direction::type dir : om_direction::all ) { + std::set dir_connections = jsi.get_tags + ( io::enum_to_string( dir ) ); + if( !dir_connections.empty() ) { + neighbors[dir] = std::move( dir_connections ); + } + } + } + + void check( const std::string &oter_name ) const { + for( const auto &p : neighbors ) { + for( const overmap_connection_id &id : p.second ) { + if( !id.is_valid() ) { + debugmsg( "Invalid overmap_connection_id '%s' in %s", id.str(), oter_name ); + } + } + } + } + + bool test( const mapgendata &dat ) const { + for( const auto &p : neighbors ) { + const om_direction::type dir = p.first; + const std::set &allowed_connections = p.second; + + bool this_direction_matches = false; + for( const overmap_connection_id &connection : allowed_connections ) { + const oter_id neighbor = dat.neighbor_at( dir ); + this_direction_matches |= connection->has( neighbor ) && + neighbor->has_connection( om_direction::opposite( dir ) ); + } + if( !this_direction_matches ) { + return false; + } + } + return true; + } + }; + public: weighted_int_list entries; weighted_int_list else_entries; - neighborhood_check neighbors; - jmapgen_nested( const JsonObject &jsi ) : neighbors( jsi.get_object( "neighbors" ) ) { + neighbor_oter_check neighbor_oters; + neighbor_join_check neighbor_joins; + neighbor_connection_check neighbor_connections; + jmapgen_nested( const JsonObject &jsi ) + : neighbor_oters( jsi.get_object( "neighbors" ) ) + , neighbor_joins( jsi.get_object( "joins" ) ) + , neighbor_connections( jsi.get_object( "connections" ) ) { load_weighted_entries( jsi, "chunks", entries ); load_weighted_entries( jsi, "else_chunks", else_entries ); } + const weighted_int_list &get_entries( const mapgendata &dat ) const { + if( neighbor_oters.test( dat ) && neighbor_joins.test( dat ) && neighbor_connections.test( dat ) ) { + return entries; + } else { + return else_entries; + } + } void apply( mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y ) const override { - const std::string *res = neighbors.test( dat ) ? entries.pick() : else_entries.pick(); + const std::string *res = get_entries( dat ).pick(); if( res == nullptr || res->empty() || *res == "null" ) { // This will be common when neighbors.test(...) is false, since else_entires is often empty. return; @@ -1884,9 +1995,15 @@ class jmapgen_nested : public jmapgen_piece ( *ptr )->nest( dat, point( x.get(), y.get() ) ); } + + void check( const std::string &oter_name ) const override { + neighbor_oters.check( oter_name ); + neighbor_joins.check( oter_name ); + neighbor_connections.check( oter_name ); + } bool has_vehicle_collision( mapgendata &dat, point p ) const override { - const weighted_int_list &selected_entries = neighbors.test( - dat ) ? entries : else_entries; + const weighted_int_list &selected_entries = get_entries( dat ); + if( selected_entries.empty() ) { return false; } @@ -2811,6 +2928,8 @@ void mapgen_function_json::generate( mapgendata &md ) if( fill_ter != t_null ) { m->draw_fill_background( fill_ter ); } + const oter_t &ter = *md.terrain_type(); + if( predecessor_mapgen != oter_str_id::NULL_ID() ) { mapgendata predecessor_mapgen_dat( md, predecessor_mapgen ); run_mapgen_func( predecessor_mapgen.id().str(), predecessor_mapgen_dat ); @@ -2825,8 +2944,8 @@ void mapgen_function_json::generate( mapgendata &md ) m->rotate( ( -rotation.get() + 4 ) % 4 ); - if( md.terrain_type()->is_rotatable() ) { - m->rotate( ( -static_cast( md.terrain_type()->get_dir() ) + 4 ) % 4 ); + if( ter.is_rotatable() || ter.is_linear() ) { + m->rotate( ( -ter.get_rotation() + 4 ) % 4 ); } } if( do_format ) { @@ -2842,8 +2961,8 @@ void mapgen_function_json::generate( mapgendata &md ) m->rotate( rotation.get() ); - if( md.terrain_type()->is_rotatable() ) { - mapgen_rotate( m, md.terrain_type(), false ); + if( ter.is_rotatable() || ter.is_linear() ) { + m->rotate( ter.get_rotation() ); } } @@ -2938,8 +3057,6 @@ void map::draw_map( mapgendata &dat ) draw_temple( dat ); } else if( is_ot_match( "mine", terrain_type, ot_match_type::prefix ) ) { draw_mine( dat ); - } else if( is_ot_match( "anthill", terrain_type, ot_match_type::contains ) ) { - draw_anthill( dat ); } else if( is_ot_match( "lab", terrain_type, ot_match_type::contains ) ) { draw_lab( dat ); } else { @@ -3556,6 +3673,7 @@ void map::draw_lab( mapgendata &dat ) bool ice_lab = true; bool central_lab = false; bool tower_lab = false; + bool ants_lab = false; point p2; @@ -3575,6 +3693,10 @@ void map::draw_lab( mapgendata &dat ) ice_lab = is_ot_match( "ice_lab", terrain_type, ot_match_type::prefix ); central_lab = is_ot_match( "central_lab", terrain_type, ot_match_type::prefix ); tower_lab = is_ot_match( "tower_lab", terrain_type, ot_match_type::prefix ); + ants_lab = is_ot_match( "ants", dat.north(), ot_match_type::contains ) || + is_ot_match( "ants", dat.east(), ot_match_type::contains ) || + is_ot_match( "ants", dat.south(), ot_match_type::contains ) || + is_ot_match( "ants", dat.west(), ot_match_type::contains ); if( ice_lab ) { int temperature = -20 + 30 * ( dat.zlevel() ); @@ -3971,7 +4093,7 @@ void map::draw_lab( mapgendata &dat ) } // end aboveground vs belowground // Ants will totally wreck up the place - if( is_ot_match( "ants", terrain_type, ot_match_type::contains ) ) { + if( ants_lab ) { for( int i = 0; i < SEEX * 2; i++ ) { for( int j = 0; j < SEEY * 2; j++ ) { // Carve out a diamond area that covers 2 spaces on each edge. @@ -4860,24 +4982,6 @@ void map::draw_mine( mapgendata &dat ) } } -void map::draw_anthill( mapgendata &dat ) -{ - const oter_id &terrain_type = dat.terrain_type(); - if( terrain_type == "anthill" || terrain_type == "acid_anthill" ) { - for( int i = 0; i < SEEX * 2; i++ ) { - for( int j = 0; j < SEEY * 2; j++ ) { - if( i < 8 || j < 8 || i > SEEX * 2 - 9 || j > SEEY * 2 - 9 ) { - ter_set( point( i, j ), dat.groundcover() ); - } else if( ( i == 11 || i == 12 ) && ( j == 11 || j == 12 ) ) { - ter_set( point( i, j ), t_slope_down ); - } else { - ter_set( point( i, j ), t_dirtmound ); - } - } - } - } -} - void map::draw_slimepit( mapgendata &dat ) { const oter_id &terrain_type = dat.terrain_type(); @@ -6509,12 +6613,7 @@ std::pair, std::map> get_changed_ids_from_up ::fake_map fake_map( f_null, t_dirt, tr_null, fake_map_z ); - oter_id any = oter_id( "field" ); - // just need a variable here, it doesn't need to be valid - const regional_settings dummy_settings; - - mapgendata fake_md( any, any, any, any, any, any, any, any, - any, any, 0, dummy_settings, fake_map, any, 0.0f, calendar::turn, nullptr ); + mapgendata fake_md( fake_map, mapgendata::dummy_settings ); if( update_function->second[0]->update_map( fake_md ) ) { for( const tripoint &pos : fake_map.points_on_zlevel( fake_map_z ) ) { diff --git a/src/mapgen.h b/src/mapgen.h index 399abb527258..02f7912d3837 100644 --- a/src/mapgen.h +++ b/src/mapgen.h @@ -456,7 +456,6 @@ enum room_type { // helpful functions bool connects_to( const oter_id &there, int dir ); -void mapgen_rotate( map *m, oter_id terrain_type, bool north_is_down = false ); // wrappers for map:: functions void line( map *m, const ter_id &type, point p1, point p2 ); void line_furn( map *m, const furn_id &type, point p1, point p2 ); diff --git a/src/mapgen_functions.cpp b/src/mapgen_functions.cpp index a3aeb8cfe870..15f40f5b5b68 100644 --- a/src/mapgen_functions.cpp +++ b/src/mapgen_functions.cpp @@ -39,10 +39,6 @@ static const itype_id itype_hat_hard( "hat_hard" ); static const itype_id itype_jackhammer( "jackhammer" ); static const itype_id itype_mask_dust( "mask_dust" ); -static const mtype_id mon_ant_larva( "mon_ant_larva" ); -static const mtype_id mon_ant_acid_larva( "mon_ant_acid_larva" ); -static const mtype_id mon_ant_queen( "mon_ant_queen" ); -static const mtype_id mon_ant_acid_queen( "mon_ant_acid_queen" ); static const mtype_id mon_bee( "mon_bee" ); static const mtype_id mon_beekeeper( "mon_beekeeper" ); static const mtype_id mon_zombie_jackson( "mon_zombie_jackson" ); @@ -140,17 +136,6 @@ building_gen_pointer get_mapgen_cfunction( const std::string &ident ) { "sewer_tee", &mapgen_sewer_tee }, { "sewer_four_way", &mapgen_sewer_four_way }, - { "ants_straight", &mapgen_ants_straight }, - { "ants_curved", &mapgen_ants_curved }, - // TODO: Add a dedicated dead-end function. For now it copies the straight section above. - { "ants_end", &mapgen_ants_straight }, - { "ants_tee", &mapgen_ants_tee }, - { "ants_four_way", &mapgen_ants_four_way }, - { "ants_food", &mapgen_ants_food }, - { "ants_larvae", &mapgen_ants_larvae }, - { "ants_larvae_acid", &mapgen_ants_larvae_acid }, - { "ants_queen", &mapgen_ants_queen }, - { "ants_queen_acid", &mapgen_ants_queen_acid }, { "tutorial", &mapgen_tutorial }, { "lake_shore", &mapgen_lake_shore }, } @@ -175,12 +160,6 @@ ter_id clay_or_sand() return t_clay; } -void mapgen_rotate( map *m, oter_id terrain_type, bool north_is_down ) -{ - const auto dir = terrain_type->get_dir(); - m->rotate( static_cast( north_is_down ? om_direction::opposite( dir ) : dir ) ); -} - ///////////////////////////////////////////////////////////////////////////////////////////////// ///// builtin terrain-specific mapgen functions. big multi-overmap-tile terrains are located in ///// mapgen_functions_big.cpp @@ -2143,288 +2122,6 @@ void mapgen_hellmouth( mapgendata &dat ) } -void mapgen_ants_curved( mapgendata &dat ) -{ - map *const m = &dat.m; - point p( SEEX, 1 ); - int rn = 0; - // First, set it all to rock - fill_background( m, t_rock ); - - for( int i = SEEX - 2; i <= SEEX + 3; i++ ) { - m->ter_set( point( i, 0 ), t_rock_floor ); - m->ter_set( point( i, 1 ), t_rock_floor ); - m->ter_set( point( i, 2 ), t_rock_floor ); - m->ter_set( point( SEEX * 2 - 1, i ), t_rock_floor ); - m->ter_set( point( SEEX * 2 - 2, i ), t_rock_floor ); - m->ter_set( point( SEEX * 2 - 3, i ), t_rock_floor ); - } - do { - for( int i = p.x - 2; i <= p.x + 3; i++ ) { - for( int j = p.y - 2; j <= p.y + 3; j++ ) { - if( i > 0 && i < SEEX * 2 - 1 && j > 0 && j < SEEY * 2 - 1 ) { - m->ter_set( point( i, j ), t_rock_floor ); - } - } - } - if( rn < SEEX ) { - p.x += rng( -1, 1 ); - p.y++; - } else { - p.x++; - if( !one_in( p.x - SEEX ) ) { - p.y += rng( -1, 1 ); - } else if( p.y < SEEY ) { - p.y++; - } else if( p.y > SEEY ) { - p.y--; - } - } - rn++; - } while( p.x < SEEX * 2 - 1 || p.y != SEEY ); - for( int i = p.x - 2; i <= p.x + 3; i++ ) { - for( int j = p.y - 2; j <= p.y + 3; j++ ) { - if( i > 0 && i < SEEX * 2 - 1 && j > 0 && j < SEEY * 2 - 1 ) { - m->ter_set( point( i, j ), t_rock_floor ); - } - } - } - if( dat.terrain_type() == "ants_es" ) { - m->rotate( 1 ); - } - if( dat.terrain_type() == "ants_sw" ) { - m->rotate( 2 ); - } - if( dat.terrain_type() == "ants_wn" ) { - m->rotate( 3 ); - } - -} - -void mapgen_ants_four_way( mapgendata &dat ) -{ - map *const m = &dat.m; - fill_background( m, t_rock ); - int x = SEEX; - for( int j = 0; j < SEEY * 2; j++ ) { - for( int i = x - 2; i <= x + 3; i++ ) { - if( i >= 1 && i < SEEX * 2 - 1 ) { - m->ter_set( point( i, j ), t_rock_floor ); - } - } - x += rng( -1, 1 ); - while( std::abs( SEEX - x ) > SEEY * 2 - j - 1 ) { - if( x < SEEX ) { - x++; - } - if( x > SEEX ) { - x--; - } - } - } - - int y = SEEY; - for( int i = 0; i < SEEX * 2; i++ ) { - for( int j = y - 2; j <= y + 3; j++ ) { - if( j >= 1 && j < SEEY * 2 - 1 ) { - m->ter_set( point( i, j ), t_rock_floor ); - } - } - y += rng( -1, 1 ); - while( std::abs( SEEY - y ) > SEEX * 2 - i - 1 ) { - if( y < SEEY ) { - y++; - } - if( y > SEEY ) { - y--; - } - } - } - -} - -void mapgen_ants_straight( mapgendata &dat ) -{ - map *const m = &dat.m; - int x = SEEX; - fill_background( m, t_rock ); - for( int j = 0; j < SEEY * 2; j++ ) { - for( int i = x - 2; i <= x + 3; i++ ) { - if( i >= 1 && i < SEEX * 2 - 1 ) { - m->ter_set( point( i, j ), t_rock_floor ); - } - } - x += rng( -1, 1 ); - while( std::abs( SEEX - x ) > SEEX * 2 - j - 1 ) { - if( x < SEEX ) { - x++; - } - if( x > SEEX ) { - x--; - } - } - } - if( dat.terrain_type() == "ants_ew" ) { - m->rotate( 1 ); - } - -} - -void mapgen_ants_tee( mapgendata &dat ) -{ - map *const m = &dat.m; - fill_background( m, t_rock ); - int x = SEEX; - for( int j = 0; j < SEEY * 2; j++ ) { - for( int i = x - 2; i <= x + 3; i++ ) { - if( i >= 1 && i < SEEX * 2 - 1 ) { - m->ter_set( point( i, j ), t_rock_floor ); - } - } - x += rng( -1, 1 ); - while( std::abs( SEEX - x ) > SEEY * 2 - j - 1 ) { - if( x < SEEX ) { - x++; - } - if( x > SEEX ) { - x--; - } - } - } - int y = SEEY; - for( int i = SEEX; i < SEEX * 2; i++ ) { - for( int j = y - 2; j <= y + 3; j++ ) { - if( j >= 1 && j < SEEY * 2 - 1 ) { - m->ter_set( point( i, j ), t_rock_floor ); - } - } - y += rng( -1, 1 ); - while( std::abs( SEEY - y ) > SEEX * 2 - 1 - i ) { - if( y < SEEY ) { - y++; - } - if( y > SEEY ) { - y--; - } - } - } - if( dat.terrain_type() == "ants_new" ) { - m->rotate( 3 ); - } - if( dat.terrain_type() == "ants_nsw" ) { - m->rotate( 2 ); - } - if( dat.terrain_type() == "ants_esw" ) { - m->rotate( 1 ); - } - -} - -static void mapgen_ants_generic( mapgendata &dat ) -{ - map *const m = &dat.m; - - for( int i = 0; i < SEEX * 2; i++ ) { - for( int j = 0; j < SEEY * 2; j++ ) { - if( i < SEEX - 4 || i > SEEX + 5 || j < SEEY - 4 || j > SEEY + 5 ) { - m->ter_set( point( i, j ), t_rock ); - } else { - m->ter_set( point( i, j ), t_rock_floor ); - } - } - } - int rn = rng( 10, 20 ); - point p; - for( int n = 0; n < rn; n++ ) { - int cw = rng( 1, 8 ); - do { - p.x = rng( 1 + cw, SEEX * 2 - 2 - cw ); - p.y = rng( 1 + cw, SEEY * 2 - 2 - cw ); - } while( m->ter( p ) == t_rock ); - for( int i = p.x - cw; i <= p.x + cw; i++ ) { - for( int j = p.y - cw; j <= p.y + cw; j++ ) { - if( trig_dist( p, point( i, j ) ) <= cw ) { - m->ter_set( point( i, j ), t_rock_floor ); - } - } - } - } - if( connects_to( dat.north(), 2 ) || - is_ot_match( "ants_lab", dat.north(), ot_match_type::contains ) ) { - for( int i = SEEX - 2; i <= SEEX + 3; i++ ) { - for( int j = 0; j <= SEEY; j++ ) { - m->ter_set( point( i, j ), t_rock_floor ); - } - } - } - if( connects_to( dat.east(), 3 ) || - is_ot_match( "ants_lab", dat.east(), ot_match_type::contains ) ) { - for( int i = SEEX; i <= SEEX * 2 - 1; i++ ) { - for( int j = SEEY - 2; j <= SEEY + 3; j++ ) { - m->ter_set( point( i, j ), t_rock_floor ); - } - } - } - if( connects_to( dat.south(), 0 ) || - is_ot_match( "ants_lab", dat.south(), ot_match_type::contains ) ) { - for( int i = SEEX - 2; i <= SEEX + 3; i++ ) { - for( int j = SEEY; j <= SEEY * 2 - 1; j++ ) { - m->ter_set( point( i, j ), t_rock_floor ); - } - } - } - if( connects_to( dat.west(), 1 ) || - is_ot_match( "ants_lab", dat.west(), ot_match_type::contains ) ) { - for( int i = 0; i <= SEEX; i++ ) { - for( int j = SEEY - 2; j <= SEEY + 3; j++ ) { - m->ter_set( point( i, j ), t_rock_floor ); - } - } - } -} - -void mapgen_ants_food( mapgendata &dat ) -{ - mapgen_ants_generic( dat ); - dat.m.place_items( item_group_id( "ant_food" ), 92, point_zero, point( SEEX * 2 - 1, SEEY * 2 - 1 ), - true, - dat.when() ); -} - -void mapgen_ants_larvae( mapgendata &dat ) -{ - mapgen_ants_generic( dat ); - dat.m.place_items( item_group_id( "ant_egg" ), 98, point_zero, point( SEEX * 2 - 1, SEEY * 2 - 1 ), - true, - dat.when() ); - dat.m.add_spawn( mon_ant_larva, 10, { SEEX, SEEY, dat.m.get_abs_sub().z } ); -} - -void mapgen_ants_larvae_acid( mapgendata &dat ) -{ - mapgen_ants_generic( dat ); - dat.m.place_items( item_group_id( "ant_egg" ), 98, point_zero, - point( SEEX * 2 - 1, SEEY * 2 - 1 ), true, dat.when() ); - dat.m.add_spawn( mon_ant_acid_larva, 10, { SEEX, SEEY, dat.m.get_abs_sub().z } ); -} - -void mapgen_ants_queen( mapgendata &dat ) -{ - mapgen_ants_generic( dat ); - dat.m.place_items( item_group_id( "ant_egg" ), 98, point_zero, point( SEEX * 2 - 1, SEEY * 2 - 1 ), - true, - dat.when() ); - dat.m.add_spawn( mon_ant_queen, 1, { SEEX, SEEY, dat.m.get_abs_sub().z } ); -} - -void mapgen_ants_queen_acid( mapgendata &dat ) -{ - mapgen_ants_generic( dat ); - dat.m.place_items( item_group_id( "ant_egg" ), 98, point_zero, - point( SEEX * 2 - 1, SEEY * 2 - 1 ), true, dat.when() ); - dat.m.add_spawn( mon_ant_acid_queen, 1, { SEEX, SEEY, dat.m.get_abs_sub().z } ); -} - void mapgen_tutorial( mapgendata &dat ) { map *const m = &dat.m; diff --git a/src/mapgen_functions.h b/src/mapgen_functions.h index 7f7795a2851e..f1549ec0292f 100644 --- a/src/mapgen_functions.h +++ b/src/mapgen_functions.h @@ -68,15 +68,6 @@ void mapgen_sewer_curved( mapgendata &dat ); void mapgen_sewer_four_way( mapgendata &dat ); void mapgen_sewer_straight( mapgendata &dat ); void mapgen_sewer_tee( mapgendata &dat ); -void mapgen_ants_curved( mapgendata &dat ); -void mapgen_ants_four_way( mapgendata &dat ); -void mapgen_ants_straight( mapgendata &dat ); -void mapgen_ants_tee( mapgendata &dat ); -void mapgen_ants_food( mapgendata &dat ); -void mapgen_ants_larvae( mapgendata &dat ); -void mapgen_ants_larvae_acid( mapgendata &dat ); -void mapgen_ants_queen( mapgendata &dat ); -void mapgen_ants_queen_acid( mapgendata &dat ); void mapgen_tutorial( mapgendata &dat ); void mapgen_lake_shore( mapgendata &dat ); diff --git a/src/mapgendata.cpp b/src/mapgendata.cpp index ab13be4891c9..240e1f6d3316 100644 --- a/src/mapgendata.cpp +++ b/src/mapgendata.cpp @@ -1,5 +1,6 @@ #include "mapgendata.h" +#include "all_enum_values.h" #include "debug.h" #include "int_id.h" #include "map.h" @@ -9,36 +10,55 @@ #include "point.h" #include "regional_settings.h" -mapgendata::mapgendata( oter_id north, oter_id east, oter_id south, oter_id west, - oter_id northeast, oter_id southeast, oter_id southwest, oter_id northwest, - oter_id up, oter_id down, int z, const regional_settings &rsettings, map &mp, - const oter_id &terrain_type, const float density, const time_point &when, - ::mission *const miss ) - : terrain_type_( terrain_type ), density_( density ), when_( when ), mission_( miss ), zlevel_( z ) - , t_nesw{ north, east, south, west, northeast, southeast, southwest, northwest } - , t_above( up ) - , t_below( down ) - , region( rsettings ) +static const regional_settings dummy_regional_settings; + +mapgendata::mapgendata( map &mp, dummy_settings_t ) + : density_( 0 ) + , when_( calendar::turn ) + , mission_( nullptr ) + , zlevel_( 0 ) + , region( dummy_regional_settings ) , m( mp ) , default_groundcover( region.default_groundcover ) { + oter_id any = oter_id( "field" ); + t_above = t_below = terrain_type_ = any; + std::fill( std::begin( t_nesw ), std::end( t_nesw ), any ); } -mapgendata::mapgendata( const tripoint_abs_omt &over, map &m, const float density, +mapgendata::mapgendata( const tripoint_abs_omt &over, map &mp, const float density, const time_point &when, ::mission *const miss ) - : mapgendata( overmap_buffer.ter( over + tripoint_north ), - overmap_buffer.ter( over + tripoint_east ), - overmap_buffer.ter( over + tripoint_south ), - overmap_buffer.ter( over + tripoint_west ), - overmap_buffer.ter( over + tripoint_north_east ), - overmap_buffer.ter( over + tripoint_south_east ), - overmap_buffer.ter( over + tripoint_south_west ), - overmap_buffer.ter( over + tripoint_north_west ), - overmap_buffer.ter( over + tripoint_above ), - overmap_buffer.ter( over + tripoint_below ), - over.z(), overmap_buffer.get_settings( over ), m, - overmap_buffer.ter( over ), density, when, miss ) + : terrain_type_( overmap_buffer.ter( over ) ) + , density_( density ) + , when_( when ) + , mission_( miss ) + , zlevel_( over.z() ) + , t_above( overmap_buffer.ter( over + tripoint_above ) ) + , t_below( overmap_buffer.ter( over + tripoint_below ) ) + , region( overmap_buffer.get_settings( over ) ) + , m( mp ) + , default_groundcover( region.default_groundcover ) { + bool ignore_rotation = terrain_type_->has_flag( oter_flags::ignore_rotation_for_adjacency ); + int rotation = ignore_rotation ? 0 : terrain_type_->get_rotation(); + auto set_neighbour = [&]( int index, direction dir ) { + t_nesw[index] = + overmap_buffer.ter( over + displace( dir ).rotate( rotation ) ); + }; + set_neighbour( 0, direction::NORTH ); + set_neighbour( 1, direction::EAST ); + set_neighbour( 2, direction::SOUTH ); + set_neighbour( 3, direction::WEST ); + set_neighbour( 4, direction::NORTHEAST ); + set_neighbour( 5, direction::SOUTHEAST ); + set_neighbour( 6, direction::SOUTHWEST ); + set_neighbour( 7, direction::NORTHWEST ); + for( cube_direction dir : all_enum_values() ) { + if( std::string *join = overmap_buffer.join_used_at( { over, dir } ) ) { + cube_direction rotated_dir = dir - rotation; + joins.emplace( rotated_dir, *join ); + } + } } mapgendata::mapgendata( const mapgendata &other, const oter_id &other_id ) : mapgendata( other ) @@ -163,3 +183,10 @@ const oter_id &mapgendata::neighbor_at( om_direction::type dir ) const debugmsg( "Tried to get neighbor from invalid direction %d", dir ); return north(); } + +bool mapgendata::has_join( const cube_direction dir, const std::string &join_id ) const +{ + auto it = joins.find( dir ); + return it != joins.end() && it->second == join_id; +} + diff --git a/src/mapgendata.h b/src/mapgendata.h index 47506f059baa..db7b67c87c89 100644 --- a/src/mapgendata.h +++ b/src/mapgendata.h @@ -2,8 +2,11 @@ #ifndef CATA_SRC_MAPGENDATA_H #define CATA_SRC_MAPGENDATA_H +#include + #include "calendar.h" #include "coordinates.h" +#include "cube_direction.h" #include "type_id.h" #include "weighted_list.h" @@ -56,16 +59,18 @@ class mapgendata oter_id t_above; oter_id t_below; + std::unordered_map joins; + const regional_settings ®ion; map &m; weighted_int_list default_groundcover; - mapgendata( oter_id t_north, oter_id t_east, oter_id t_south, oter_id t_west, - oter_id northeast, oter_id southeast, oter_id southwest, oter_id northwest, - oter_id up, oter_id down, int z, const regional_settings &rsettings, map &mp, - const oter_id &terrain_type, float density, const time_point &when, ::mission *miss ); + struct dummy_settings_t {}; + static constexpr dummy_settings_t dummy_settings = {}; + + mapgendata( map &, dummy_settings_t ); mapgendata( const tripoint_abs_omt &over, map &m, float density, const time_point &when, ::mission *miss ); @@ -139,6 +144,8 @@ class mapgendata void square_groundcover( point p1, point p2 ); ter_id groundcover(); bool is_groundcover( const ter_id &iid ) const; + + bool has_join( const cube_direction, const std::string &join_id ) const; }; #endif // CATA_SRC_MAPGENDATA_H diff --git a/src/npcmove.cpp b/src/npcmove.cpp index 99c55ee67a5c..e49b7c3ddb02 100644 --- a/src/npcmove.cpp +++ b/src/npcmove.cpp @@ -256,14 +256,14 @@ tripoint npc::good_escape_direction( bool include_pos ) std::map adj_map; for( direction pt_dir : npc_threat_dir ) { - const tripoint &pt = pos() + direction_XY( pt_dir ); + const tripoint &pt = pos() + displace_XY( pt_dir ); float cur_rating = rate_pt( pt, ai_cache.threat_map[ pt_dir ] ); adj_map[pt_dir] = cur_rating; if( cur_rating == best_rating ) { - candidates.emplace_back( pos() + direction_XY( pt_dir ) ); + candidates.emplace_back( pos() + displace_XY( pt_dir ) ); } else if( cur_rating < best_rating ) { candidates.clear(); - candidates.emplace_back( pos() + direction_XY( pt_dir ) ); + candidates.emplace_back( pos() + displace_XY( pt_dir ) ); best_rating = cur_rating; } } diff --git a/src/om_direction.h b/src/om_direction.h index 14ff7d55a23e..f4661c7c22ea 100644 --- a/src/om_direction.h +++ b/src/om_direction.h @@ -7,8 +7,7 @@ #include #include -struct point; -struct tripoint; +#include "coordinates.h" /** Direction on the overmap. */ namespace om_direction @@ -21,6 +20,7 @@ enum class type : int { east, south, west, + last }; /** For the purposes of iteration. */ @@ -66,15 +66,18 @@ constexpr int get_num_ccw_rotations( type dir ) /** Number of bits needed to store directions. */ const size_t bits = static_cast( -1 ) >> ( CHAR_BIT *sizeof( size_t ) - size ); -/** Identifier for serialization purposes. */ -const std::string &id( type dir ); - /** Get Human readable name of a direction */ std::string name( type dir ); /** Various rotations. */ point rotate( point p, type dir ); tripoint rotate( const tripoint &p, type dir ); +template +auto rotate( const coords::coord_point &p, type dir ) +-> coords::coord_point +{ + return coords::coord_point { rotate( p.raw(), dir ) }; +} uint32_t rotate_symbol( uint32_t sym, type dir ); /** Returns point(0, 0) displaced in specified direction by a specified distance @@ -104,4 +107,9 @@ bool are_parallel( type dir1, type dir2 ); } // namespace om_direction +template<> +struct enum_traits { + static constexpr om_direction::type last = om_direction::type::last; +}; + #endif // CATA_SRC_OM_DIRECTION_H diff --git a/src/omdata.h b/src/omdata.h index d1d6b74e7d58..ddcf3d6b78fb 100644 --- a/src/omdata.h +++ b/src/omdata.h @@ -86,6 +86,7 @@ enum oter_flags { known_down = 0, known_up, no_rotate, // this tile doesn't have four rotated versions (north, east, south, west) + ignore_rotation_for_adjacency, river_tile, has_sidewalk, line_drawing, // does this tile have 8 versions, including straights, bends, tees, and a fourway? @@ -218,6 +219,9 @@ struct oter_t { return from_land_use_code ? type->land_use_code->color : type->color; } + // dir is only meaningful for rotatable, non-linear terrain. If you + // need an answer that also works for linear terrain, call + // get_rotation() instead. om_direction::type get_dir() const { return dir; } @@ -226,6 +230,7 @@ struct oter_t { return line; } void get_rotation_and_subtile( int &rotation, int &subtile ) const; + int get_rotation() const; unsigned char get_see_cost() const { return type->see_cost; diff --git a/src/options.cpp b/src/options.cpp index 273bd213a6e7..bf4969fc4521 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -2248,6 +2248,11 @@ void options_manager::add_options_world_default() 0.0, 10.0, 1, 0.1 ); + add( "SPECIALS_SPACING", world_default, translate_marker( "Overmap specials spacing" ), + translate_marker( "A number determing minimum distance between overmap specials. -1 allows intersections of specials." ), + -1, 18, 6 + ); + add( "SPAWN_DENSITY", world_default, translate_marker( "Spawn rate scaling factor" ), translate_marker( "A scaling factor that determines density of monster spawns." ), 0.0, 50.0, 1.0, 0.1 diff --git a/src/output.cpp b/src/output.cpp index 978af7aad7ba..48f520a6de0f 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -1733,7 +1733,7 @@ scrollingcombattext::cSCT::cSCT( point p_pos, const direction p_oDir, oLeft = iso_mode ? direction::NORTHWEST : direction::WEST; oUpLeft = iso_mode ? direction::NORTH : direction::NORTHWEST; - point pairDirXY = direction_XY( oDir ); + point pairDirXY = displace_XY( oDir ); dir = pairDirXY; diff --git a/src/overmap.cpp b/src/overmap.cpp index 13164baac014..a3ad53b63834 100644 --- a/src/overmap.cpp +++ b/src/overmap.cpp @@ -1,4 +1,5 @@ #include "om_direction.h" // IWYU pragma: associated +#include "cube_direction.h" // IWYU pragma: associated #include "omdata.h" // IWYU pragma: associated #include "overmap_special.h" // IWYU pragma: associated #include "overmap.h" // IWYU pragma: associated @@ -16,6 +17,7 @@ #include #include +#include "all_enum_values.h" #include "assign.h" #include "basecamp.h" #include "cata_utility.h" @@ -23,6 +25,7 @@ #include "character_id.h" #include "coordinate_conversions.h" #include "debug.h" +#include "distribution.h" #include "flood_fill.h" #include "fstream_utils.h" #include "game.h" @@ -50,6 +53,7 @@ #include "regional_settings.h" #include "rng.h" #include "rotatable_symbols.h" +#include "sets_intersect.h" #include "simple_pathfinding.h" #include "string_formatter.h" #include "string_utils.h" @@ -73,8 +77,6 @@ class map_extra; #define dbg(x) DebugLogFL((x),DC::MapGen) #define BUILDINGCHANCE 4 -#define MIN_ANT_SIZE 8 -#define MAX_ANT_SIZE 20 #define MIN_GOO_SIZE 1 #define MAX_GOO_SIZE 2 @@ -89,6 +91,63 @@ oter_id ot_null, const oter_type_t oter_type_t::null_type{}; +namespace io +{ + +template<> +std::string enum_to_string( overmap_special_subtype data ) +{ + switch( data ) { + // *INDENT-OFF* + case overmap_special_subtype::fixed: return "fixed"; + case overmap_special_subtype::mutable_: return "mutable"; + // *INDENT-ON* + case overmap_special_subtype::last: + break; + } + debugmsg( "Invalid overmap_special_subtype" ); + abort(); +} + +template<> +std::string enum_to_string( om_direction::type d ) +{ + switch( d ) { + // *INDENT-OFF* + case om_direction::type::north: return "north"; + case om_direction::type::east: return "east"; + case om_direction::type::south: return "south"; + case om_direction::type::west: return "west"; + // *INDENT-ON* + case om_direction::type::invalid: + case om_direction::type::last: + break; + } + debugmsg( "Invalid om_direction" ); + abort(); +} + +template<> +std::string enum_to_string( cube_direction data ) +{ + switch( data ) { + // *INDENT-OFF* + case cube_direction::north: return "north"; + case cube_direction::east: return "east"; + case cube_direction::south: return "south"; + case cube_direction::west: return "west"; + case cube_direction::above: return "above"; + case cube_direction::below: return "below"; + // *INDENT-ON* + case cube_direction::last: + break; + } + debugmsg( "Invalid cube_direction" ); + abort(); +} + +} // namespace io + namespace om_lines { @@ -174,6 +233,7 @@ static size_t from_dir( om_direction::type dir ) case om_direction::type::west: return 10; // ew case om_direction::type::invalid: + case om_direction::type::last: debugmsg( "Can't retrieve a line from the invalid direction." ); } @@ -334,27 +394,6 @@ bool operator!=( const int_id &lhs, const char *rhs ) return !( lhs == rhs ); } -namespace io -{ - -template<> -std::string enum_to_string( lab_type data ) -{ - switch( data ) { - case lab_type::standard: - return "standard"; - case lab_type::ice: - return "ice"; - case lab_type::central: - return "central"; - case lab_type::invalid: - return "invalid"; - } - return "BUGGED"; -} - -} // namespace io - static void set_oter_ids() // FIXME: constify { ot_null = oter_str_id::NULL_ID(); @@ -426,13 +465,6 @@ const std::vector &overmap_land_use_codes::get_all() return land_use_codes.get_all(); } -void overmap_special_terrain::deserialize( const JsonObject &jo ) -{ - mandatory( jo, false, "point", p ); - mandatory( jo, false, "overmap", terrain ); - optional( jo, false, "locations", locations ); -} - void overmap_special_connection::deserialize( const JsonObject &jo ) { mandatory( jo, false, "point", p ); @@ -467,6 +499,39 @@ void overmap_special_connection::deserialize( const JsonObject &jo ) optional( jo, false, "from", from ); } +void overmap_special_connection::finalize() +{ + // If the connection has a "from" hint specified, then figure out what the + // resulting direction from the hinted location to the connection point is, + // and use that as the intial direction to be passed off to the connection + // building code. + if( from ) { + const direction calculated_direction = direction_from( *from, p ); + switch( calculated_direction ) { + case direction::NORTH: + initial_dir = om_direction::type::north; + break; + case direction::EAST: + initial_dir = om_direction::type::east; + break; + case direction::SOUTH: + initial_dir = om_direction::type::south; + break; + case direction::WEST: + initial_dir = om_direction::type::west; + break; + default: + // The only supported directions are north/east/south/west + // as those are the four directions that overmap connections + // can be made in. If the direction we figured out wasn't + // one of those, just set this as invalid. We'll provide + // a warning to the user/developer in overmap_special::check(). + initial_dir = om_direction::type::invalid; + break; + } + } +} + void overmap_spawns::deserialize( const JsonObject &jo ) { mandatory( jo, false, "group", group ); @@ -520,23 +585,13 @@ const std::vector &overmap_specials::get_all() overmap_special_batch overmap_specials::get_default_batch( const point_abs_om &origin ) { - const int city_size = get_option( "CITY_SIZE" ); std::vector res; res.reserve( specials.size() ); for( const overmap_special &elem : specials.get_all() ) { - if( elem.occurrences.empty() || - std::any_of( elem.terrains.begin(), elem.terrains.end(), []( const overmap_special_terrain & t ) { - return t.locations.empty(); - } ) ) { - continue; - } - - if( city_size == 0 && elem.city_size.min > city_size ) { - continue; + if( elem.can_spawn() ) { + res.push_back( &elem ); } - - res.push_back( &elem ); } return overmap_special_batch( origin, res ); @@ -768,7 +823,7 @@ oter_t::oter_t( const oter_type_t &type ) : oter_t::oter_t( const oter_type_t &type, om_direction::type dir ) : type( &type ), - id( type.id.str() + "_" + om_direction::id( dir ) ), + id( type.id.str() + "_" + io::enum_to_string( dir ) ), dir( dir ), symbol( om_direction::rotate_symbol( type.symbol, dir ) ), symbol_alt( om_direction::rotate_symbol( type.land_use_code ? type.land_use_code->symbol : @@ -811,6 +866,20 @@ void oter_t::get_rotation_and_subtile( int &rotation, int &subtile ) const } } +int oter_t::get_rotation() const +{ + if( is_linear() ) { + const om_lines::type &t = om_lines::all[line]; + // It turns out the rotation used for linear things is the opposite of + // the rotation used for other things. Sigh. + return ( 4 - t.rotation ) % 4; + } + if( is_rotatable() ) { + return static_cast( get_dir() ); + } + return 0; +} + bool oter_t::type_is( const oter_type_id &type_id ) const { return type->id.id() == type_id; @@ -835,8 +904,6 @@ bool oter_t::is_hardcoded() const { // TODO: This set only exists because so does the monstrous 'if-else' statement in @ref map::draw_map(). Get rid of both. static const std::set hardcoded_mapgen = { - "acid_anthill", - "anthill", "ants_lab", "ants_lab_stairs", "ice_lab", @@ -940,7 +1007,8 @@ const std::vector &overmap_terrains::get_all() return terrains.get_all(); } -bool overmap_special_terrain::can_be_placed_on( const oter_id &oter ) const +static bool is_amongst_locations( const oter_id &oter, + const cata::flat_set &locations ) { return std::any_of( locations.begin(), locations.end(), [&oter]( const overmap_location_id & loc ) { @@ -948,107 +1016,1581 @@ bool overmap_special_terrain::can_be_placed_on( const oter_id &oter ) const } ); } -const overmap_special_terrain &overmap_special::get_terrain_at( const tripoint &p ) const +bool overmap_special_locations::can_be_placed_on( const oter_id &oter ) const { - const auto iter = std::find_if( terrains.begin(), - terrains.end(), [ &p ]( const overmap_special_terrain & elem ) { - return elem.p == p; - } ); - if( iter == terrains.end() ) { - static const overmap_special_terrain null_terrain{}; - return null_terrain; + return is_amongst_locations( oter, locations ); +} + +void overmap_special_locations::deserialize( JsonIn &jsin ) +{ + JsonArray ja = jsin.get_array(); + + if( ja.size() != 2 ) { + ja.throw_error( "expected array of size 2" ); } - return *iter; + + ja.read( 0, p, true ); + ja.read( 1, locations, true ); } -bool overmap_special::requires_city() const +void overmap_special_terrain::deserialize( JsonIn &jsin ) { - return city_size.min > 0 || city_distance.max < std::max( OMAPX, OMAPY ); + JsonObject om = jsin.get_object(); + om.read( "point", p ); + om.read( "overmap", terrain ); + om.read( "locations", locations ); } -bool overmap_special::can_belong_to_city( const tripoint_om_omt &p, const city &cit ) const +cube_direction operator+( const cube_direction l, const om_direction::type r ) { - if( !requires_city() ) { - return true; + return l + static_cast( r ); +} + +cube_direction operator+( const cube_direction d, int i ) +{ + switch( d ) { + case cube_direction::north: + case cube_direction::east: + case cube_direction::south: + case cube_direction::west: + return static_cast( ( static_cast( d ) + i ) % 4 ); + case cube_direction::above: + case cube_direction::below: + return d; + case cube_direction::last: + break; } - if( !cit || !city_size.contains( cit.size ) ) { - return false; + debugmsg( "Invalid cube_direction" ); + abort(); +} + +cube_direction operator-( const cube_direction l, const om_direction::type r ) +{ + return l - static_cast( r ); +} + +cube_direction operator-( const cube_direction d, int i ) +{ + switch( d ) { + case cube_direction::north: + case cube_direction::east: + case cube_direction::south: + case cube_direction::west: + return static_cast( ( static_cast( d ) - i + 4 ) % 4 ); + case cube_direction::above: + case cube_direction::below: + return d; + case cube_direction::last: + break; } - return city_distance.contains( cit.get_distance_from( p ) ); + debugmsg( "Invalid cube_direction" ); + abort(); } -void overmap_special::load( const JsonObject &jo, const std::string &src ) +static tripoint displace( cube_direction d ) +{ + switch( d ) { + case cube_direction::north: + return tripoint_north; + case cube_direction::east: + return tripoint_east; + case cube_direction::south: + return tripoint_south; + case cube_direction::west: + return tripoint_west; + case cube_direction::above: + return tripoint_above; + case cube_direction::below: + return tripoint_below; + case cube_direction::last: + break; + } + debugmsg( "Invalid cube_direction" ); + abort(); +} + +struct mutable_overmap_join { + std::string id; + std::string opposite_id; + cata::flat_set into_locations; + unsigned priority; // NOLINT(cata-serialize) + const mutable_overmap_join *opposite = nullptr; // NOLINT(cata-serialize) + + void deserialize( JsonIn &jin ) { + if( jin.test_string() ) { + id = jin.get_string(); + } else { + JsonObject jo = jin.get_object(); + jo.read( "id", id, true ); + jo.read( "into_locations", into_locations, true ); + jo.read( "opposite", opposite_id, true ); + } + } +}; + +enum class join_type { + mandatory, + optional, + available, + reject, + last +}; + +template<> +struct enum_traits { + static constexpr join_type last = join_type::last; +}; + +namespace io { - const bool strict = is_strict_enabled( src ); - // city_building is just an alias of overmap_special - // TODO: This comparison is a hack. Separate them properly. - const bool is_special = jo.get_string( "type", "" ) == "overmap_special"; - mandatory( jo, was_loaded, "overmaps", terrains ); - optional( jo, was_loaded, "locations", default_locations ); - optional( jo, was_loaded, "connections", connections ); +template<> +std::string enum_to_string( join_type data ) +{ + switch( data ) { + // *INDENT-OFF* + case join_type::mandatory: return "mandatory"; + case join_type::optional: return "optional"; + case join_type::available: return "available"; + case join_type::reject: return "reject"; + // *INDENT-ON* + case join_type::last: + break; + } + debugmsg( "Invalid join_type" ); + abort(); +} - if( is_special ) { - mandatory( jo, was_loaded, "occurrences", occurrences ); +} // namespace io + +struct mutable_overmap_terrain_join { + std::string join_id; + const mutable_overmap_join *join = nullptr; // NOLINT(cata-serialize) + cata::flat_set alternative_join_ids; + cata::flat_set alternative_joins; // NOLINT(cata-serialize) + join_type type = join_type::mandatory; + + void finalize( const std::string &context, + const std::unordered_map &joins ) { + auto join_it = joins.find( join_id ); + if( join_it != joins.end() ) { + join = join_it->second; + } else { + debugmsg( "invalid join id %s in %s", join_id, context ); + } + for( const std::string &alt_join_id : alternative_join_ids ) { + auto alt_join_it = joins.find( alt_join_id ); + if( alt_join_it != joins.end() ) { + alternative_joins.insert( alt_join_it->second ); + } else { + debugmsg( "invalid join id %s in %s", alt_join_id, context ); + } + } + } - assign( jo, "city_sizes", city_size, strict ); - assign( jo, "city_distance", city_distance, strict ); + void deserialize( JsonIn &jin ) { + if( jin.test_string() ) { + jin.read( join_id, true ); + } else if( jin.test_object() ) { + JsonObject jo = jin.get_object(); + jo.read( "id", join_id, true ); + jo.read( "type", type, true ); + jo.read( "alternatives", alternative_join_ids, true ); + } else { + jin.error( "Expected string or object" ); + } } +}; - assign( jo, "spawns", spawns, strict ); +using join_map = std::unordered_map; - assign( jo, "rotate", rotatable, strict ); - assign( jo, "flags", flags, strict ); +struct z_constraints { + enum class constraint_type { + any, + range, + top, + bottom, + offset + }; - // Another hack - if( !is_special ) { - flags.insert( "ELECTRIC_GRID" ); + constraint_type type = constraint_type::any; + int min = INT_MIN; + int max = INT_MAX; + + bool check( const tripoint_om_omt &pos, const int z_min, const int z_max ) const { + switch( type ) { + case constraint_type::any: + return true; + case constraint_type::range: + return pos.z() <= max && pos.z() >= min; + case constraint_type::top: + return pos.z() >= z_max; + case constraint_type::bottom: + return pos.z() <= z_min; + case constraint_type::offset: + return pos.z() == max + z_max || pos.z() == min + z_min; + } + return false; + } + + void deserialize( JsonIn &jsin ) { + if( jsin.test_int() ) { + int v = jsin.get_int(); + min = v; + max = v; + type = constraint_type::range; + } else if( jsin.test_array() ) { + JsonArray ja = jsin.get_array(); + if( ja.size() != 2 ) { + ja.throw_error( "Array should be in format [min, max]" ); + } + min = ja.get_int( 0 ); + max = ja.get_int( 1 ); + type = constraint_type::range; + } else if( jsin.test_string() ) { + std::string type_string = jsin.get_string(); + if( type_string == "top" ) { + type = constraint_type::top; + } else if( type_string == "bottom" ) { + type = constraint_type::bottom; + } else { + jsin.error( "String should be 'top' or 'bottom'" ); + } + } else if( jsin.test_object() ) { + JsonObject jo = jsin.get_object(); + jo.read( "top", max, true ); + jo.read( "bottom", min, true ); + type = constraint_type::offset; + if( max == INT_MAX && min == INT_MIN ) { + jo.throw_error( "Object should have at least one of 'top' and 'bottom' properties." ); + } + } else { + jsin.error( "Unrecognized z-constraints" ); + } + } +}; + +struct mutable_overmap_terrain { + oter_str_id terrain; + cata::flat_set locations; + join_map joins; + + void finalize( const std::string &context, + const std::unordered_map &special_joins, + const cata::flat_set> &default_locations ) { + if( locations.empty() ) { + locations = default_locations; + } + for( join_map::value_type &p : joins ) { + mutable_overmap_terrain_join &ter_join = p.second; + ter_join.finalize( context, special_joins ); + } + } + + void check( const std::string &context ) const { + if( !terrain.is_valid() ) { + debugmsg( "invalid overmap terrain id %s in %s", terrain.str(), context ); + } + for( const string_id &loc : locations ) { + if( !loc.is_valid() ) { + debugmsg( "invalid overmap location id %s in %s", loc.str(), context ); + } + } + } + + void deserialize( JsonIn &jin ) { + JsonObject jo = jin.get_object(); + jo.read( "overmap", terrain, true ); + jo.read( "locations", locations ); + for( int i = 0; i != static_cast( cube_direction::last ); ++i ) { + cube_direction dir = static_cast( i ); + std::string dir_s = io::enum_to_string( dir ); + if( jo.has_member( dir_s ) ) { + jo.read( dir_s, joins[dir], true ); + } + } + } +}; + +struct mutable_overmap_piece_candidate { + const mutable_overmap_terrain *overmap; // NOLINT(cata-serialize) + tripoint_om_omt pos; + om_direction::type rot = om_direction::type::north; +}; + +struct mutable_overmap_placement_rule_piece { + std::string overmap_id; + const mutable_overmap_terrain *overmap; // NOLINT(cata-serialize) + tripoint_rel_omt pos; + om_direction::type rot = om_direction::type::north; + + void deserialize( const JsonObject &jo ) { + jo.read( "overmap", overmap_id, true ); + jo.read( "pos", pos, true ); + jo.read( "rot", rot, true ); + } +}; + +struct connection_node { + tripoint_om_omt origin; + om_direction::type rot; + const overmap_special_connection *connection; +}; + +struct mutable_overmap_placement_rule_remainder; + +struct mutable_overmap_placement_rule { + std::string name; + std::string required_join; + std::string scale; + z_constraints z; + std::optional rotate; + std::optional om_pos; + std::vector pieces; + std::vector connections; + // NOLINTNEXTLINE(cata-serialize) + std::vector> outward_joins; + int_distribution max = int_distribution( INT_MAX ); + int weight = INT_MAX; + + std::string description() const { + if( !name.empty() ) { + return name; + } + std::string first_om_id = pieces[0].overmap_id; + if( pieces.size() == 1 ) { + return first_om_id; + } else { + return "chunk using overmap " + first_om_id; + } + } + + void finalize( const std::string &context, + const std::unordered_map &special_overmaps + ) { + std::unordered_map + pieces_by_pos; + for( mutable_overmap_placement_rule_piece &piece : pieces ) { + bool inserted = pieces_by_pos.emplace( piece.pos, &piece ).second; + if( !inserted ) { + debugmsg( "phase of %s has chunk with duplicated position %s", + context, piece.pos.to_string() ); + } + auto it = special_overmaps.find( piece.overmap_id ); + if( it == special_overmaps.end() ) { + throw std::runtime_error( + string_format( "phase of %s specifies overmap %s which is not defined for that special", + context, piece.overmap_id ) ); + } else { + piece.overmap = &it->second; + } + } + for( const mutable_overmap_placement_rule_piece &piece : pieces ) { + const mutable_overmap_terrain &ter = *piece.overmap; + for( const join_map::value_type &p : ter.joins ) { + const cube_direction dir = p.first; + const mutable_overmap_terrain_join &ter_join = p.second; + rel_pos_dir this_side{ piece.pos, dir + piece.rot }; + rel_pos_dir other_side = this_side.opposite(); + auto opposite_piece = pieces_by_pos.find( other_side.p ); + if( opposite_piece == pieces_by_pos.end() ) { + outward_joins.emplace_back( this_side, &ter_join ); + } else if( ter_join.type != join_type::mandatory ) { + // TODO: Validate rejects in chunks + continue; + } else { + const std::string &opposite_join = ter_join.join->opposite_id; + const mutable_overmap_placement_rule_piece &other_piece = + *opposite_piece->second; + const mutable_overmap_terrain &other_om = *other_piece.overmap; + + auto opposite_om_join = + other_om.joins.find( other_side.dir - other_piece.rot ); + if( opposite_om_join == other_om.joins.end() ) { + debugmsg( "in phase of %s, %s has adjacent pieces %s at %s and %s at " + "%s where the former has a join %s pointed towards the latter, " + "but the latter has no join pointed towards the former", + context, description(), piece.overmap_id, piece.pos.to_string(), + other_piece.overmap_id, other_piece.pos.to_string(), + ter_join.join_id ); + } else if( opposite_om_join->second.join_id != opposite_join ) { + debugmsg( "in phase of %s, %s has adjacent pieces %s at %s and %s at " + "%s where the former has a join %s pointed towards the latter, " + "expecting a matching join %s wheras the latter has the join %s " + "pointed towards the former", + context, description(), piece.overmap_id, piece.pos.to_string(), + other_piece.overmap_id, other_piece.pos.to_string(), + ter_join.join_id, opposite_join, + opposite_om_join->second.join_id ); + } + } + } + } + for( auto &elem : connections ) { + elem.finalize(); + } + } + void check( const std::string &context, + const std::unordered_map &joins, + const std::unordered_map &shared ) const { + if( pieces.empty() ) { + throw std::runtime_error( string_format( "phase of %s has chunk with zero pieces", context ) ); + } + int min_max = max.minimum(); + if( min_max < 0 ) { + debugmsg( "phase of %s specifies max which might be as low as %d; this should " + "be a positive number", context, min_max ); + } + if( !required_join.empty() ) { + auto join = joins.find( required_join ); + if( join == joins.end() ) { + debugmsg( "invalid join id %s in phase of %s", required_join, context ); + } + } + if( !scale.empty() ) { + auto dist = shared.find( scale ); + if( dist == shared.end() ) { + debugmsg( "invalid shared multiplier %s in phase of %s", scale, context ); + } + } + } + + mutable_overmap_placement_rule_remainder realise( int scale ) const; + + void deserialize( const JsonObject &jo ) { + jo.read( "name", name ); + if( jo.has_member( "overmap" ) ) { + pieces.emplace_back(); + jo.read( "overmap", pieces.back().overmap_id, true ); + } else if( jo.has_member( "chunk" ) ) { + jo.read( "chunk", pieces ); + } else { + jo.throw_error( R"(placement rule must specify at least one of "overmap" or "chunk")" ); + } + jo.read( "connections", connections ); + jo.read( "join", required_join ); + jo.read( "scale", scale ); + jo.read( "z", z ); + jo.read( "rotate", rotate ); + jo.read( "om_pos", om_pos ); + jo.read( "max", max ); + jo.read( "weight", weight ); + } +}; + +struct mutable_overmap_placement_rule_remainder { + const mutable_overmap_placement_rule *parent; + int max = INT_MAX; + int weight = INT_MAX; + + std::string description() const { + return parent->description(); + } + + int get_weight() const { + return std::min( max, weight ); + } + + bool is_exhausted() const { + return get_weight() == 0; + } + + void decrement() { + --max; } + + std::vector positions( om_direction::type rot ) const { + std::vector result; + for( const mutable_overmap_placement_rule_piece &piece : parent->pieces ) { + result.push_back( rotate( piece.pos, rot ) ); + } + return result; + } + std::vector pieces( const tripoint_om_omt &origin, + om_direction::type rot ) const { + std::vector result; + for( const mutable_overmap_placement_rule_piece &piece : parent->pieces ) { + tripoint_rel_omt rotated_offset = rotate( piece.pos, rot ); + result.push_back( { piece.overmap, origin + rotated_offset, add( rot, piece.rot ) } ); + } + return result; + } + auto outward_joins( const tripoint_om_omt &origin, om_direction::type rot ) const + -> std::vector> { + std::vector> result; + for( const std::pair &p : + parent->outward_joins ) { + tripoint_rel_omt rotated_offset = rotate( p.first.p, rot ); + om_pos_dir p_d{ origin + rotated_offset, p.first.dir + rot }; + result.emplace_back( p_d, p.second ); + } + return result; + } +}; + +mutable_overmap_placement_rule_remainder mutable_overmap_placement_rule::realise( int scale ) const +{ + return mutable_overmap_placement_rule_remainder{ this, max.sample( scale ), weight }; } -void overmap_special::finalize() +// When building a mutable overmap special we maintain a collection of +// unresolved joins. We need to be able to index that collection in +// various ways, so it gets its own struct to maintain the relevant invariants. +class joins_tracker { - // If the special has default locations, then add those to the locations - // of each of the terrains IF the terrain has no locations already. - if( !default_locations.empty() ) { - for( auto &t : terrains ) { - if( t.locations.empty() ) { - t.locations.insert( default_locations.begin(), default_locations.end() ); + public: + struct join { + om_pos_dir where; + join_type type; + const mutable_overmap_join *join; + }; + using iterator = std::list::iterator; + using const_iterator = std::list::const_iterator; + + bool any_unresolved() const { + return !unresolved.empty(); + } + + int mandatory_amount_at( const tripoint_om_omt &pos ) const { + int ret = 0; + for( iterator it : unresolved.all_at( pos ) ) { + if( it->type == join_type::mandatory ) { + ret++; + } } + return ret; } - } - for( auto &elem : connections ) { - // If the connection has a "from" hint specified, then figure out what the - // resulting direction from the hinted location to the connection point is, - // and use that as the intial direction to be passed off to the connection - // building code. - if( elem.from ) { - const direction calculated_direction = direction_from( *elem.from, elem.p ); - switch( calculated_direction ) { - case direction::NORTH: - elem.initial_dir = om_direction::type::north; - break; - case direction::EAST: - elem.initial_dir = om_direction::type::east; - break; - case direction::SOUTH: - elem.initial_dir = om_direction::type::south; - break; - case direction::WEST: - elem.initial_dir = om_direction::type::west; - break; - default: - // The only supported directions are north/east/south/west - // as those are the four directions that overmap connections - // can be made in. If the direction we figured out wasn't - // one of those, just set this as invalid. We'll provide - // a warning to the user/developer in overmap_special::check(). - elem.initial_dir = om_direction::type::invalid; - break; + std::vector all_unresolved_at( const tripoint_om_omt &pos ) const { + std::vector result; + for( iterator it : unresolved.all_at( pos ) ) { + result.push_back( &*it ); + } + return result; + } + + bool is_finished() const { + for( const auto &join : postponed ) { + if( join.type == join_type::mandatory ) { + return false; + } + } + return true; + } + + bool any_postponed_at( const tripoint_om_omt &p ) const { + return postponed.any_at( p ); + } + + void consistency_check() const { + if( test_mode ) { + // Enable this to check the class invariants, at the cost of more runtime + // verify that there are no positions in common between the + // resolved and postponed lists + for( const join &j : postponed ) { + auto j_pos = j.where.p; + if( unresolved.any_at( j_pos ) ) { + std::vector unr = unresolved.all_at( j_pos ); + if( unr.empty() ) { + debugmsg( "inconsistency between all_at and any_at" ); + } else { + const join &unr_j = *unr.front(); + debugmsg( "postponed and unresolved should be disjoint but are not at " + "%s where unresolved has %s: %s", + j_pos.to_string(), unr_j.where.p.to_string(), unr_j.join->id ); + } + abort(); + } + } + } + } + + enum class join_status { + disallowed, // Conflicts with existing join, and at least one was mandatory + matched_available, // Matches an existing non-mandatory join + matched_non_available, // Matches an existing mandatory join + mismatched_available, // Points at an incompatible join, but both are non-mandatory + free, // Doesn't point at another join at all + }; + + join_status allows( const om_pos_dir &this_side, + const mutable_overmap_terrain_join &this_ter_join ) const { + om_pos_dir other_side = this_side.opposite(); + + auto is_allowed_opposite = [&]( const std::string & candidate ) { + const mutable_overmap_join &this_join = *this_ter_join.join; + + if( this_join.opposite_id == candidate ) { + return true; + } + + for( const mutable_overmap_join *alt_join : this_ter_join.alternative_joins ) { + if( alt_join->opposite_id == candidate ) { + return true; + } + } + + return false; + }; + + if( const join *existing = resolved.find( other_side ) ) { + join_type other_type = existing->type; + if( is_allowed_opposite( existing->join->id ) ) { + if( other_type == join_type::mandatory ) { + return join_status::matched_non_available; + } else if( other_type == join_type::reject || this_ter_join.type == join_type::reject ) { + return join_status::disallowed; + } else { + return join_status::matched_available; + } + } else { + if( other_type == join_type::mandatory || this_ter_join.type == join_type::mandatory ) { + return join_status::disallowed; + } else { + return join_status::mismatched_available; + } + } + } else { + return join_status::free; + } + } + + void add_joins_for( + const mutable_overmap_terrain &ter, const tripoint_om_omt &pos, + om_direction::type rot, const std::vector &suppressed_joins ) { + consistency_check(); + + std::unordered_set avoid( + suppressed_joins.begin(), suppressed_joins.end() ); + + for( const std::pair &p : + ter.joins ) { + cube_direction dir = p.first + rot; + const mutable_overmap_terrain_join &this_side_join = p.second; + + om_pos_dir this_side{ pos, dir }; + om_pos_dir other_side = this_side.opposite(); + + if( const join *other_side_join = resolved.find( other_side ) ) { + erase_unresolved( this_side ); + if( !avoid.count( this_side ) ) { + used.emplace_back( other_side, other_side_join->join->id ); + // Because of the existence of alternative joins, we don't + // simply add this_side_join here, we add the opposite of + // the opposite that was actually present (this saves us + // from heaving to search through the alternates to find + // which one actually matched). + used.emplace_back( this_side, other_side_join->join->opposite_id ); + } + } else { + // If there were postponed joins pointing into this point, + // so we need to un-postpone them because it might now be + // possible to satisfy them. + restore_postponed_at( other_side.p ); + if( this_side_join.type == join_type::mandatory || + this_side_join.type == join_type::optional ) { + const mutable_overmap_join *opposite_join = this_side_join.join->opposite; + add_unresolved( other_side, this_side_join.type, opposite_join ); + } + } + resolved.add( this_side, this_side_join.type, this_side_join.join ); + } + consistency_check(); + } + + tripoint_om_omt pick_top_priority() const { + assert( any_unresolved() ); + auto priority_it = + std::find_if( unresolved_priority_index.begin(), unresolved_priority_index.end(), + []( const cata::flat_set &its ) { + return !its.empty(); + } ); + assert( priority_it != unresolved_priority_index.end() ); + auto it = random_entry( *priority_it ); + const tripoint_om_omt &pos = it->where.p; + assert( !postponed.any_at( pos ) ); + return pos; + } + void postpone( const tripoint_om_omt &pos ) { + consistency_check(); + for( iterator it : unresolved.all_at( pos ) ) { + postponed.add( *it ); + [[maybe_unused]] const bool erased = erase_unresolved( it->where ); + assert( erased ); + } + consistency_check(); + } + void restore_postponed_at( const tripoint_om_omt &pos ) { + for( iterator it : postponed.all_at( pos ) ) { + add_unresolved( it->where, it->type, it->join ); + postponed.erase( it ); + } + consistency_check(); + } + void restore_postponed() { + consistency_check(); + for( const join &j : postponed ) { + add_unresolved( j.where, j.type, j.join ); + } + postponed.clear(); + } + + const std::vector> &all_used() const { + return used; + } + private: + struct indexed_joins { + std::list joins; + std::unordered_map position_index; + + iterator begin() { + return joins.begin(); + } + + iterator end() { + return joins.end(); + } + + const_iterator begin() const { + return joins.begin(); + } + + const_iterator end() const { + return joins.end(); + } + + bool empty() const { + return joins.empty(); + } + + bool count( const om_pos_dir &p ) const { + return position_index.count( p ); + } + + const join *find( const om_pos_dir &p ) const { + auto it = position_index.find( p ); + if( it == position_index.end() ) { + return nullptr; + } + return &*it->second; + } + + bool any_at( const tripoint_om_omt &pos ) const { + for( cube_direction dir : all_enum_values() ) { + if( count( om_pos_dir{ pos, dir } ) ) { + return true; + } + } + return false; + } + + std::vector all_at( const tripoint_om_omt &pos ) const { + std::vector result; + for( cube_direction dir : all_enum_values() ) { + om_pos_dir key{ pos, dir }; + auto pos_it = position_index.find( key ); + if( pos_it != position_index.end() ) { + result.push_back( pos_it->second ); + } + } + return result; + } + + iterator add( const om_pos_dir &p, const join_type &t, const mutable_overmap_join *j ) { + return add( { p, t, j } ); + } + + iterator add( const join &j ) { + joins.push_front( j ); + auto it = joins.begin(); + [[maybe_unused]] const bool inserted = position_index.emplace( j.where, it ).second; + assert( inserted ); + return it; + } + + void erase( const iterator it ) { + [[maybe_unused]] const size_t erased = position_index.erase( it->where ); + assert( erased ); + joins.erase( it ); + } + + void clear() { + joins.clear(); + position_index.clear(); + } + }; + + void add_unresolved( const om_pos_dir &p, const join_type &t, const mutable_overmap_join *j ) { + iterator it = unresolved.add( p, t, j ); + unsigned priority = it->join->priority; + if( unresolved_priority_index.size() <= priority ) { + unresolved_priority_index.resize( priority + 1 ); + } + [[maybe_unused]] const bool inserted = unresolved_priority_index[priority].insert( it ).second; + assert( inserted ); + } + + bool erase_unresolved( const om_pos_dir &p ) { + auto pos_it = unresolved.position_index.find( p ); + if( pos_it == unresolved.position_index.end() ) { + return false; + } + iterator it = pos_it->second; + unsigned priority = it->join->priority; + assert( priority < unresolved_priority_index.size() ); + [[maybe_unused]] const size_t erased = unresolved_priority_index[priority].erase( it ); + assert( erased ); + unresolved.erase( it ); + return true; + } + + struct compare_iterators { + bool operator()( iterator l, iterator r ) { + return l->where < r->where; + } + }; + + indexed_joins unresolved; + std::vector> unresolved_priority_index; + + indexed_joins resolved; + indexed_joins postponed; + + std::vector> used; +}; + +struct mutable_overmap_phase_remainder { + std::vector rules; + + struct satisfy_result { + tripoint_om_omt origin; + om_direction::type dir; + mutable_overmap_placement_rule_remainder *rule; + std::vector suppressed_joins; + // For debugging purposes it's really handy to have a record of exactly + // what happened during placement of a mutable special when it fails, + // so to aid that we provide a human-readable description here which is + // only used in the event of a placement error. + std::string description; + }; + + bool all_rules_exhausted() const { + return std::all_of( rules.begin(), rules.end(), + []( const mutable_overmap_placement_rule_remainder & rule ) { + return rule.is_exhausted(); + } ); + } + + struct can_place_result { + int num_context_mandatory_joins_matched; + int num_my_non_available_matched; + std::vector supressed_joins; + + std::pair as_pair() const { + return { num_context_mandatory_joins_matched, num_my_non_available_matched }; + } + + friend bool operator==( const can_place_result &l, const can_place_result &r ) { + return l.as_pair() == r.as_pair(); + } + + friend bool operator<( const can_place_result &l, const can_place_result &r ) { + return l.as_pair() < r.as_pair(); + } + }; + + std::optional can_place( + const overmap &om, const mutable_overmap_placement_rule_remainder &rule, + const tripoint_om_omt &origin, om_direction::type dir, + const joins_tracker &unresolved + ) const { + std::vector pieces = rule.pieces( origin, dir ); + int context_mandatory_joins_shortfall = 0; + + bool have_required_join = rule.parent->required_join.empty(); + for( const mutable_overmap_piece_candidate &piece : pieces ) { + if( !overmap::inbounds( piece.pos ) ) { + return std::nullopt; + } + if( !is_amongst_locations( om.ter( piece.pos ), piece.overmap->locations ) ) { + return std::nullopt; + } + if( unresolved.any_postponed_at( piece.pos ) ) { + return std::nullopt; + } + if( !have_required_join ) { + for( const auto &join : piece.overmap->joins ) { + if( join.second.join_id == rule.parent->required_join ) { + have_required_join = true; + break; + } + } + } + context_mandatory_joins_shortfall -= unresolved.mandatory_amount_at( piece.pos ); + } + if( !have_required_join ) { + return std::nullopt; + } + + int num_my_non_available_matched = 0; + + std::vector> remaining_joins = + rule.outward_joins( origin, dir ); + std::vector suppressed_joins; + + for( const std::pair &p : + remaining_joins ) { + const om_pos_dir &pos_d = p.first; + const mutable_overmap_terrain_join &ter_join = *p.second; + const mutable_overmap_join &join = *ter_join.join; + switch( unresolved.allows( pos_d, ter_join ) ) { + case joins_tracker::join_status::disallowed: + return std::nullopt; + case joins_tracker::join_status::matched_non_available: + ++context_mandatory_joins_shortfall; + // fallthrough + case joins_tracker::join_status::matched_available: + if( ter_join.type == join_type::mandatory ) { + ++num_my_non_available_matched; + } + continue; + case joins_tracker::join_status::mismatched_available: + suppressed_joins.push_back( pos_d ); + // fallthrough + case joins_tracker::join_status::free: + if( join.id == rule.parent->required_join ) { + return std::nullopt; + } + break; + } + if( ter_join.type != join_type::mandatory ) { + continue; + } + // Verify that the remaining joins lead to + // suitable locations + tripoint_om_omt neighbour = pos_d.p + displace( pos_d.dir ); + if( !overmap::inbounds( neighbour ) ) { + return std::nullopt; + } + const oter_id &neighbour_terrain = om.ter( neighbour ); + if( !is_amongst_locations( neighbour_terrain, join.into_locations ) ) { + return std::nullopt; + } + } + return can_place_result{ context_mandatory_joins_shortfall, + num_my_non_available_matched, suppressed_joins }; + } + + satisfy_result satisfy( const overmap &om, const tripoint_om_omt &pos, + const joins_tracker &unresolved, const bool rotatable, + const int z_min, const int z_max ) { + weighted_int_list options; + + for( mutable_overmap_placement_rule_remainder &rule : rules ) { + std::vector pos_dir_options; + can_place_result best_result{ 0, 0, {} }; + + if( !rule.parent->z.check( pos, z_min, z_max ) ) { + continue; + } + + for( om_direction::type dir : om_direction::all ) { + for( const tripoint_rel_omt &piece_pos : rule.positions( dir ) ) { + tripoint_om_omt origin = pos - piece_pos; + + if( std::optional result = can_place( + om, rule, origin, dir, unresolved ) ) { + if( best_result < *result ) { + pos_dir_options.clear(); + best_result = *result; + } + if( *result == best_result ) { + pos_dir_options.push_back( + satisfy_result{ origin, dir, &rule, result.value().supressed_joins, + {} } ); + } + } + } + + if( rule.parent->rotate ? !( *rule.parent->rotate ) : !rotatable ) { + break; + } + } + + if( auto chosen_result = random_entry_opt( pos_dir_options ) ) { + options.add( *chosen_result, rule.get_weight() ); + } + } + std::string joins_s = enumerate_as_string( unresolved.all_unresolved_at( pos ), + []( const joins_tracker::join * j ) { + return string_format( "%s: %s", io::enum_to_string( j->where.dir ), j->join->id ); + } ); + + if( satisfy_result *picked = options.pick() ) { + om_direction::type dir = picked->dir; + const mutable_overmap_placement_rule_remainder &rule = *picked->rule; + picked->description = + string_format( + "At %s chose '%s' rot %d with neighbours N:%s E:%s S:%s W:%s and constraints " + "%s", + pos.to_string(), rule.description(), static_cast( dir ), + om.ter( pos + point_north ).id().str(), om.ter( pos + point_east ).id().str(), + om.ter( pos + point_south ).id().str(), om.ter( pos + point_west ).id().str(), + joins_s ); + picked->rule->decrement(); + return *picked; + } else { + std::string rules_s = enumerate_as_string( rules, + []( const mutable_overmap_placement_rule_remainder & rule ) { + if( rule.is_exhausted() ) { + return string_format( "(%s)", rule.description() ); + } else { + return rule.description(); + } + } ); + std::string message = + string_format( + "At %s FAILED to match on terrain %s with neighbours N:%s E:%s S:%s W:%s and " + "constraints %s from amongst rules %s", + pos.to_string(), om.ter( pos ).id().str(), + om.ter( pos + point_north ).id().str(), om.ter( pos + point_east ).id().str(), + om.ter( pos + point_south ).id().str(), om.ter( pos + point_west ).id().str(), + joins_s, rules_s ); + return { {}, om_direction::type::invalid, nullptr, {}, std::move( message ) }; + } + } +}; + +struct mutable_overmap_phase { + std::vector rules; + + mutable_overmap_phase_remainder realise( overmap &om, + std::unordered_map scales ) const { + std::vector realised_rules; + for( const mutable_overmap_placement_rule &rule : rules ) { + if( rule.om_pos && *rule.om_pos != om.pos() ) { + continue; + } + int scale = !rule.scale.empty() ? scales[rule.scale] : 1; + realised_rules.push_back( rule.realise( scale ) ); + } + return { realised_rules }; + } + + void deserialize( JsonIn &jin ) { + jin.read( rules, true ); + } +}; + +template +pos_dir pos_dir::opposite() const +{ + switch( dir ) { + case cube_direction::north: + return { p + tripoint_north, cube_direction::south }; + case cube_direction::east: + return { p + tripoint_east, cube_direction::west }; + case cube_direction::south: + return { p + tripoint_south, cube_direction::north }; + case cube_direction::west: + return { p + tripoint_west, cube_direction::east }; + case cube_direction::above: + return { p + tripoint_above, cube_direction::below }; + case cube_direction::below: + return { p + tripoint_below, cube_direction::above }; + case cube_direction::last: + break; + } + debugmsg( "Invalid cube_direction" ); + abort(); +} + +template +void pos_dir::serialize( JsonOut &jsout ) const +{ + jsout.start_array(); + jsout.write( p ); + jsout.write( dir ); + jsout.end_array(); +} + +template +void pos_dir::deserialize( JsonIn &jsin ) +{ + JsonArray ja = jsin.get_array(); + if( ja.size() != 2 ) { + ja.throw_error( "Expected array of size 2" ); + } + ja.read( 0, p ); + ja.read( 1, dir ); +} + +template +bool pos_dir::operator==( const pos_dir &r ) const +{ + return p == r.p && dir == r.dir; +} + +template +bool pos_dir::operator<( const pos_dir &r ) const +{ + return std::tie( p, dir ) < std::tie( r.p, r.dir ); +} + +template struct pos_dir; +template struct pos_dir; + +struct mutable_overmap_special_data { + overmap_special_id parent_id; + std::vector check_for_locations; + std::vector joins_vec; + std::unordered_map shared_dist; + std::unordered_map joins; + std::unordered_map overmaps; + std::string root; + std::vector phases; + + explicit mutable_overmap_special_data( const overmap_special_id &p_id ) + : parent_id( p_id ) + {} + + void finalize( const std::string &context, + const cata::flat_set &default_locations ) { + if( check_for_locations.empty() ) { + check_for_locations.push_back( root_as_overmap_special_terrain() ); + } + joins.clear(); + for( size_t i = 0; i != joins_vec.size(); ++i ) { + mutable_overmap_join &join = joins_vec[i]; + if( join.into_locations.empty() ) { + join.into_locations = default_locations; + } + join.priority = i; + joins.emplace( join.id, &join ); + } + for( mutable_overmap_join &join : joins_vec ) { + if( join.opposite_id.empty() ) { + join.opposite_id = join.id; + join.opposite = &join; + continue; + } + auto opposite_it = joins.find( join.opposite_id ); + if( opposite_it == joins.end() ) { + // Error reported later in check() + continue; + } + join.opposite = opposite_it->second; + } + for( std::pair &p : overmaps ) { + mutable_overmap_terrain &ter = p.second; + ter.finalize( string_format( "overmap %s in %s", p.first, context ), joins, + default_locations ); + } + for( mutable_overmap_phase &phase : phases ) { + for( mutable_overmap_placement_rule &rule : phase.rules ) { + rule.finalize( context, overmaps ); + } + } + } + + void check( const std::string &context ) const { + if( joins_vec.size() != joins.size() ) { + debugmsg( "duplicate join id in %s", context ); + } + for( const mutable_overmap_join &join : joins_vec ) { + if( join.opposite ) { + if( join.opposite->opposite_id != join.id ) { + debugmsg( "in %1$s: join id %2$s specifies its opposite to be %3$s, but " + "the opposite of %3$s is %4$s, when it should match the " + "original id %2$s", + context, join.id, join.opposite_id, join.opposite->opposite_id ); + } + } else { + debugmsg( "in %s: join id '%s' specified as opposite of '%s' not valid", + context, join.opposite_id, join.id ); + } + } + for( const std::pair &p : overmaps ) { + const mutable_overmap_terrain &ter = p.second; + ter.check( string_format( "overmap %s in %s", p.first, context ) ); + } + if( !overmaps.count( root ) ) { + debugmsg( "root %s is not amongst the defined overmaps for %s", root, context ); + } + for( const mutable_overmap_phase &phase : phases ) { + for( const mutable_overmap_placement_rule &rule : phase.rules ) { + rule.check( context, joins, shared_dist ); + } + } + } + + overmap_special_terrain root_as_overmap_special_terrain() const { + auto it = overmaps.find( root ); + if( it == overmaps.end() ) { + debugmsg( "root '%s' is not an overmap in this special", root ); + return {}; + } + const mutable_overmap_terrain &root_om = it->second; + return { tripoint_zero, root_om.terrain, root_om.locations }; + } + + // Returns a list of the points placed and a list of the joins used + auto place( overmap &om, const tripoint_om_omt &origin, const bool rotatable ) const -> + std::tuple, std::vector, std::vector>> { + std::vector result; + std::vector connections; + + auto it = overmaps.find( root ); + if( it == overmaps.end() ) { + debugmsg( "Invalid root %s", root ); + return { result, {}, {} }; + } + const mutable_overmap_terrain &root_omt = it->second; + om.ter_set( origin, root_omt.terrain ); + result.push_back( origin ); + + // This is for debugging only, it tracks a human-readable description + // of what happened to be put in the debugmsg in the event of failure. + std::vector descriptions; + + std::unordered_map scales; + for( const auto &dist : shared_dist ) { + scales[dist.first] = dist.second.sample(); + } + + int z_min = INT_MAX; + int z_max = INT_MIN; + + joins_tracker unresolved; + unresolved.add_joins_for( root_omt, origin, om_direction::type::none, {} ); + + auto current_phase = phases.begin(); + mutable_overmap_phase_remainder phase_remaining = current_phase->realise( om, scales ); + + while( unresolved.any_unresolved() ) { + tripoint_om_omt next_pos = unresolved.pick_top_priority(); + mutable_overmap_phase_remainder::satisfy_result satisfy_result = + phase_remaining.satisfy( om, next_pos, unresolved, rotatable, z_min, z_max ); + descriptions.push_back( std::move( satisfy_result.description ) ); + const mutable_overmap_placement_rule_remainder *rule = satisfy_result.rule; + if( rule ) { + const tripoint_om_omt &origin = satisfy_result.origin; + om_direction::type rot = satisfy_result.dir; + std::vector pieces = rule->pieces( origin, rot ); + for( const mutable_overmap_piece_candidate &piece : pieces ) { + const mutable_overmap_terrain &ter = *piece.overmap; + const oter_id tid = ter.terrain->get_rotated( piece.rot ); + om.ter_set( piece.pos, tid ); + unresolved.add_joins_for( ter, piece.pos, piece.rot, + satisfy_result.suppressed_joins ); + z_min = std::min( z_min, piece.pos.z() ); + z_max = std::max( z_max, piece.pos.z() ); + result.push_back( piece.pos ); + } + for( const overmap_special_connection &con : rule->parent->connections ) { + connections.push_back( { origin, rot, &con } ); + } + } else { + unresolved.postpone( next_pos ); + } + if( !unresolved.any_unresolved() || phase_remaining.all_rules_exhausted() ) { + ++current_phase; + if( current_phase == phases.end() ) { + break; + } + descriptions.push_back( + string_format( "## Entering phase %td", current_phase - phases.begin() ) ); + phase_remaining = current_phase->realise( om, scales ); + unresolved.restore_postponed(); + } + } + + if( !unresolved.is_finished() ) { + // This is an error in the JSON; extract some useful info to help + // the user debug it + unresolved.restore_postponed(); + tripoint_om_omt p = unresolved.pick_top_priority(); + + const oter_id ¤t_terrain = om.ter( p ); + std::string joins = enumerate_as_string( unresolved.all_unresolved_at( p ), + []( const joins_tracker::join * dir_join ) { + return string_format( "%s: %s", io::enum_to_string( dir_join->where.dir ), + dir_join->join->id ); + } ); + + debugmsg( "Spawn of mutable special %s had unresolved joins. Existing terrain " + "at %s was %s; joins were %s\nComplete record of placement follows:\n%s", + parent_id.str(), p.to_string(), current_terrain.id().str(), joins, + join( descriptions, "\n" ) ); + + om.add_note( + p, string_format( + "U:R;DEBUG: unresolved joins %s at %s placing %s", + joins, p.to_string(), parent_id.str() ) ); + } + + return { result, connections, unresolved.all_used() }; + } +}; + +bool overmap_special::can_spawn() const +{ + if( get_constraints().occurrences.empty() ) { + return false; + } + + if( std::any_of( fixed_data_.terrains.begin(), fixed_data_.terrains.end(), + []( const overmap_special_terrain & t ) { + return t.locations.empty(); + } ) ) { + return false; + } + + const int city_size = get_option( "CITY_SIZE" ); + return city_size != 0 || get_constraints().city_size.min <= city_size; +} + +const overmap_special_terrain &overmap_special::get_terrain_at( const tripoint &p ) const +{ + const auto iter = std::find_if( fixed_data_.terrains.begin(), fixed_data_.terrains.end(), + [ &p ]( const overmap_special_terrain & elem ) { + return elem.p == p; + } ); + if( iter == fixed_data_.terrains.end() ) { + static const overmap_special_terrain null_terrain{}; + return null_terrain; + } + return *iter; +} + +bool overmap_special::requires_city() const +{ + return constraints_.city_size.min > 0 || + constraints_.city_distance.max < std::max( OMAPX, OMAPY ); +} + +bool overmap_special::can_belong_to_city( const tripoint_om_omt &p, const city &cit ) const +{ + return constraints_.city_distance.contains( cit.get_distance_from( p ) - ( cit.size ) ); +} + +int overmap_special::longest_side() const +{ + // Figure out the longest side of the special for purposes of determining our sector size + // when attempting placements. + const auto &locs = required_locations(); + auto min_max_x = std::minmax_element( locs.begin(), locs.end(), + []( const overmap_special_locations & lhs, const overmap_special_locations & rhs ) { + return lhs.p.x < rhs.p.x; + } ); + + auto min_max_y = std::minmax_element( locs.begin(), locs.end(), + []( const overmap_special_locations & lhs, const overmap_special_locations & rhs ) { + return lhs.p.y < rhs.p.y; + } ); + + const int width = min_max_x.second->p.x - min_max_x.first->p.x; + const int height = min_max_y.second->p.y - min_max_y.first->p.y; + return std::max( width, height ) + 1; +} + +std::vector overmap_special::all_terrains() const +{ + std::vector result; + switch( subtype_ ) { + case overmap_special_subtype::fixed: + for( const overmap_special_terrain &ter : fixed_data_.terrains ) { + result.push_back( ter.terrain ); + } + break; + case overmap_special_subtype::mutable_: + for( const auto &ter : mutable_data_->overmaps ) { + result.push_back( ter.second.terrain ); + } + break; + case overmap_special_subtype::last: + debugmsg( "invalid overmap_special_subtype" ); + abort(); + } + + for( const auto &nested : get_nested_specials() ) { + auto inner = nested.second->all_terrains(); + result.insert( result.end(), std::make_move_iterator( inner.begin() ), + std::make_move_iterator( inner.end() ) ); + } + return result; +} + +std::vector overmap_special::preview_terrains() const +{ + std::vector result; + switch( subtype_ ) { + case overmap_special_subtype::fixed: + for( const overmap_special_terrain &ter : fixed_data_.terrains ) { + if( ter.p.z == 0 ) { + result.push_back( ter ); + } + } + break; + case overmap_special_subtype::mutable_: + result.push_back( mutable_data_->root_as_overmap_special_terrain() ); + break; + case overmap_special_subtype::last: + debugmsg( "invalid overmap_special_subtype" ); + abort(); + } + + for( const auto &nested : get_nested_specials() ) { + for( const auto &ter : nested.second->preview_terrains() ) { + overmap_special_terrain rel_ter = ter; + rel_ter.p += nested.first.raw(); + if( rel_ter.p.z == 0 ) { + result.push_back( rel_ter ); + } + } + } + return result; +} + +std::vector overmap_special::required_locations() const +{ + std::vector result; + switch( subtype_ ) { + case overmap_special_subtype::fixed: { + std::copy( fixed_data_.terrains.begin(), fixed_data_.terrains.end(), + std::back_inserter( result ) ); + break; + } + case overmap_special_subtype::mutable_: { + std::copy( mutable_data_->check_for_locations.begin(), mutable_data_->check_for_locations.end(), + std::back_inserter( result ) ); + break; + } + case overmap_special_subtype::last: + debugmsg( "invalid overmap_special_subtype" ); + abort(); + } + + for( const auto &nested : get_nested_specials() ) { + for( const auto &loc : nested.second->required_locations() ) { + overmap_special_locations rel_loc = loc; + rel_loc.p += nested.first.raw(); + result.push_back( rel_loc ); + } + } + return result; +} + +void overmap_special::load( const JsonObject &jo, const std::string &src ) +{ + const bool strict = is_strict_enabled( src ); + // city_building is just an alias of overmap_special + // TODO: This comparison is a hack. Separate them properly. + const bool is_special = jo.get_string( "type", "" ) == "overmap_special"; + + optional( jo, was_loaded, "subtype", subtype_, overmap_special_subtype::fixed ); + optional( jo, was_loaded, "locations", default_locations_ ); + optional( jo, was_loaded, "connections", connections ); + + switch( subtype_ ) { + case overmap_special_subtype::fixed: + mandatory( jo, was_loaded, "overmaps", fixed_data_.terrains ); + break; + case overmap_special_subtype::mutable_: { + shared_ptr_fast mutable_data; + if( was_loaded ) { + mutable_data = make_shared_fast( *mutable_data_ ); + } else { + mutable_data = make_shared_fast( id ); + } + optional( jo, was_loaded, "check_for_locations", mutable_data->check_for_locations ); + for( JsonObject joc : jo.get_array( "check_for_locations_area" ) ) { + cata::flat_set type; + tripoint from; + tripoint to; + mandatory( joc, was_loaded, "type", type ); + mandatory( joc, was_loaded, "from", from ); + mandatory( joc, was_loaded, "to", to ); + if( from.x > to.x ) { + std::swap( from.x, to.x ); + } + if( from.y > to.y ) { + std::swap( from.y, to.y ); + } + if( from.z > to.z ) { + std::swap( from.z, to.z ); + } + for( int x = from.x; x <= to.x; x++ ) { + for( int y = from.y; y <= to.y; y++ ) { + for( int z = from.z; z <= to.z; z++ ) { + overmap_special_locations loc; + loc.p = tripoint( x, y, z ); + loc.locations = type; + mutable_data->check_for_locations.push_back( loc ); + } + } + } + } + optional( jo, was_loaded, "shared", mutable_data->shared_dist ); + mandatory( jo, was_loaded, "joins", mutable_data->joins_vec ); + mandatory( jo, was_loaded, "overmaps", mutable_data->overmaps ); + mandatory( jo, was_loaded, "root", mutable_data->root ); + mandatory( jo, was_loaded, "phases", mutable_data->phases ); + mutable_data->finalize( "overmap special " + id.str(), default_locations_ ); + mutable_data_ = std::move( mutable_data ); + break; + } + default: + jo.throw_error( string_format( "subtype %s not implemented", + io::enum_to_string( subtype_ ) ) ); + } + + if( jo.has_array( "place_nested" ) ) { + JsonArray jar = jo.get_array( "place_nested" ); + while( jar.has_more() ) { + JsonObject joc = jar.next_object(); + std::pair nested; + mandatory( joc, was_loaded, "point", nested.first ); + mandatory( joc, was_loaded, "special", nested.second ); + nested_.insert( nested ); + } + } + + if( is_special ) { + mandatory( jo, was_loaded, "occurrences", constraints_.occurrences ); + + assign( jo, "city_sizes", constraints_.city_size, strict ); + assign( jo, "city_distance", constraints_.city_distance, strict ); + } + + assign( jo, "spawns", monster_spawns_, strict ); + + assign( jo, "rotate", rotatable_, strict ); + assign( jo, "flags", flags_, strict ); + + // Another hack + if( !is_special ) { + flags_.insert( "ELECTRIC_GRID" ); + } +} + +void overmap_special::finalize() +{ + // If the special has default locations, then add those to the locations + // of each of the terrains IF the terrain has no locations already. + if( !default_locations_.empty() ) { + for( auto &t : fixed_data_.terrains ) { + if( t.locations.empty() ) { + t.locations = default_locations_; } } } + + // TODO: Check if piece and joins are uniform, to determine default "rotate" value + + for( auto &elem : connections ) { + elem.finalize(); + } } void overmap_special::check() const @@ -1056,11 +2598,11 @@ void overmap_special::check() const std::set invalid_terrains; std::set points; - for( const auto &elem : terrains ) { + for( const auto &elem : fixed_data_.terrains ) { const auto &oter = elem.terrain; if( !oter.is_valid() ) { - if( invalid_terrains.count( oter.id() ) == 0 ) { + if( !invalid_terrains.count( oter.id() ) ) { invalid_terrains.insert( oter.id() ); debugmsg( "In overmap special \"%s\", terrain \"%s\" is invalid.", id.c_str(), oter.c_str() ); @@ -1120,6 +2662,36 @@ void overmap_special::check() const } } } + + // Make sure nested specials doesn't loop back to parents + std::function( const overmap_special_id, std::unordered_set ) > + check_recursion = [&check_recursion]( const overmap_special_id special, + std::unordered_set parents ) -> std::optional { + for( const auto &nested : special->get_nested_specials() ) + { + if( parents.count( nested.second ) > 0 ) { + return nested.second; + } else { + std::unordered_set copy = parents; + copy.insert( nested.second ); + std::optional recures = check_recursion( nested.second, copy ); + if( recures ) { + return *recures; + } + } + } + return std::nullopt; + }; + std::unordered_set parents{ id }; + std::optional recurse = check_recursion( id, parents ); + if( recurse ) { + debugmsg( "In overmap special \"%s\", nested special \"%s\" places itself recursively.", + id, *recurse ); + } + + if( mutable_data_ ) { + mutable_data_->check( string_format( "overmap special %s", id.str() ) ); + } } // *** BEGIN overmap FUNCTIONS *** @@ -1163,13 +2735,8 @@ void overmap::populate() // Remove any items that have a flag that is present in the blacklist. if( should_blacklist ) { for( auto it = enabled_specials.begin(); it != enabled_specials.end(); ) { - std::vector common; - std::set_intersection( overmap_feature_flag.blacklist.begin(), - overmap_feature_flag.blacklist.end(), - it->special_details->flags.begin(), it->special_details->flags.end(), - std::back_inserter( common ) - ); - if( !common.empty() ) { + if( cata::sets_intersect( overmap_feature_flag.blacklist, + it->special_details->get_flags() ) ) { it = enabled_specials.erase( it ); } else { ++it; @@ -1180,16 +2747,11 @@ void overmap::populate() // Remove any items which do not have any of the flags from the whitelist. if( should_whitelist ) { for( auto it = enabled_specials.begin(); it != enabled_specials.end(); ) { - std::vector common; - std::set_intersection( overmap_feature_flag.whitelist.begin(), - overmap_feature_flag.whitelist.end(), - it->special_details->flags.begin(), it->special_details->flags.end(), - std::back_inserter( common ) - ); - if( common.empty() ) { - it = enabled_specials.erase( it ); - } else { + if( cata::sets_intersect( overmap_feature_flag.whitelist, + it->special_details->get_flags() ) ) { ++it; + } else { + it = enabled_specials.erase( it ); } } } @@ -1245,6 +2807,15 @@ const oter_id &overmap::ter( const tripoint_om_omt &p ) const return layer[p.z() + OVERMAP_DEPTH].terrain[p.x()][p.y()]; } +std::string *overmap::join_used_at( const om_pos_dir &p ) +{ + auto it = joins_used.find( p ); + if( it == joins_used.end() ) { + return nullptr; + } + return &it->second; +} + bool &overmap::seen( const tripoint_om_omt &p ) { if( !inbounds( p ) ) { @@ -1568,41 +3139,6 @@ void overmap::set_scent( const tripoint_abs_omt &loc, const scent_trace &new_sce scents[loc] = new_scent; } -static void fixup_labs( overmap &om ) -{ - if( om.pos() != point_abs_om( point_zero ) ) { - return; - } - - bool has_endgame = false; - for( lab &l : om.labs ) { - if( l.type != lab_type::central ) { - continue; - } - - auto lowest = std::min_element( l.finales.begin(), l.finales.end(), - []( const tripoint_om_omt & l, const tripoint_om_omt & r ) { - return l.z() < r.z(); - } ); - if( lowest == l.finales.end() ) { - auto lowest_regular = std::min_element( l.tiles.begin(), l.tiles.end(), - []( const tripoint_om_omt & l, const tripoint_om_omt & r ) { - return l.z() < r.z(); - } ); - debugmsg( "Endgame lab was generated with no finales. Lowest tile: (%s).", - lowest_regular->to_string() ); - continue; - } - - om.ter_set( *lowest, oter_id( "central_lab_endgame" ) ); - has_endgame = true; - } - - if( !has_endgame ) { - debugmsg( _( "No endgame lab was generated." ) ); - } -} - void overmap::generate( const overmap *north, const overmap *east, const overmap *south, const overmap *west, overmap_special_batch &enabled_specials ) @@ -1614,13 +3150,7 @@ void overmap::generate( const overmap *north, const overmap *east, dbg( DL::Info ) << "overmap::generate start"; - clear_labs(); - - bool needs_endgame = std::any_of( enabled_specials.begin(), - enabled_specials.end(), []( const overmap_special_placement & pl ) { - return pl.special_details->flags.count( "ENDGAME" ); - } ); - + connection_cache = overmap_connection_cache{}; populate_connections_out_from_neighbors( north, east, south, west ); place_rivers( north, east, south, west ); @@ -1645,11 +3175,6 @@ void overmap::generate( const overmap *north, const overmap *east, requires_sub = generate_sub( z ); } while( requires_sub && ( --z >= -OVERMAP_DEPTH ) ); - // We don't need it if we're in a test method or a mod that doesn't have endgame - if( needs_endgame ) { - fixup_labs( *this ); - } - // Always need at least one overlevel, but how many more z = 1; bool requires_over = false; @@ -1661,6 +3186,9 @@ void overmap::generate( const overmap *north, const overmap *east, // Place the monsters, now that the terrain is laid out place_mongroups(); place_radios(); + + connection_cache.reset(); + dbg( DL::Info ) << "overmap::generate done"; } @@ -1668,87 +3196,25 @@ bool overmap::generate_sub( const int z ) { // We need to generate at least 3 z-levels for labs bool requires_sub = z > -4; - std::vector subway_points; std::vector sewer_points; - std::vector ant_points; std::vector goo_points; - std::vector lab_points; - std::vector ice_lab_points; - std::vector central_lab_points; - std::vector lab_train_points; - std::vector central_lab_train_points; std::vector mine_points; - // Connect subways of cities - const overmap_connection_id subway_tunnel( "subway_tunnel" ); - for( const auto &elem : cities ) { - if( subway_tunnel->has( ter( tripoint_om_omt( elem.pos, z ) ) ) ) { - subway_points.emplace_back( elem.pos ); - } - } - // Connect outer subways - for( const auto &p : connections_out[subway_tunnel] ) { - if( p.z() == z ) { - subway_points.emplace_back( p.xy() ); - } - } - // These are so common that it's worth checking first as int. - const oter_id skip_above[5] = { + const std::unordered_set skip_above = { oter_id( "empty_rock" ), oter_id( "forest" ), oter_id( "field" ), oter_id( "forest_thick" ), oter_id( "forest_water" ) }; - // TODO: Clean up - const auto find_lab_for = [this]( const tripoint_om_omt & p ) { - for( lab &l : labs ) { - if( l.tiles.count( p ) > 0 ) { - return &l; - } - } - - return static_cast( nullptr ); - }; - - const auto handle_lab_core = [&find_lab_for]( const tripoint_om_omt & p, - std::vector &points, lab_type type ) { - points.push_back( p.xy() ); - lab *l = find_lab_for( p + tripoint_above ); - if( l == nullptr ) { - // FIXME: figure out why this happens - // debugmsg( "Couldn't find lab for point %s", p.to_string() ); - return; - } - if( l->type != type ) { - // FIXME: figure out why this happens - // debugmsg( "Lab type mismatch for lab at %s", p.to_string() ); - return; - } - l->tiles.insert( p ); - }; - for( int i = 0; i < OMAPX; i++ ) { for( int j = 0; j < OMAPY; j++ ) { tripoint_om_omt p( i, j, z ); const oter_id oter_above = ter( p + tripoint_above ); const oter_id oter_ground = ter( tripoint_om_omt( p.xy(), 0 ) ); - if( is_ot_match( "microlab_sub_connector", ter( p ), ot_match_type::type ) ) { - om_direction::type rotation = ter( p )->get_dir(); - ter_set( p, oter_id( "subway_end_north" )->get_rotated( rotation ) ); - subway_points.emplace_back( p.xy() ); - } - // implicitly skip skip_above oter_ids - bool skipme = false; - for( auto &elem : skip_above ) { - if( oter_above == elem ) { - skipme = true; - break; - } - } - if( skipme ) { + if( skip_above.count( oter_above ) > 0 ) { continue; } @@ -1757,31 +3223,12 @@ bool overmap::generate_sub( const int z ) sewer_points.emplace_back( i, j ); } else if( oter_above == "sewage_treatment" ) { sewer_points.emplace_back( i, j ); - } else if( oter_above == "anthill" || oter_above == "acid_anthill" ) { - const int size = rng( MIN_ANT_SIZE, MAX_ANT_SIZE ); - ant_points.emplace_back( p.xy(), size ); } else if( oter_above == "slimepit_down" ) { const int size = rng( MIN_GOO_SIZE, MAX_GOO_SIZE ); goo_points.emplace_back( p.xy(), size ); } else if( oter_above == "forest_water" ) { ter_set( p, oter_id( "cavern" ) ); chip_rock( p ); - } else if( oter_above == "lab_core" || - ( z == -1 && oter_above == "lab_stairs" ) ) { - handle_lab_core( p, lab_points, lab_type::standard ); - } else if( oter_above == "lab_stairs" ) { - ter_set( p, oter_id( "lab" ) ); - } else if( oter_above == "ice_lab_core" || - ( z == -1 && oter_above == "ice_lab_stairs" ) ) { - handle_lab_core( p, ice_lab_points, lab_type::ice ); - } else if( oter_above == "ice_lab_stairs" ) { - ter_set( p, oter_id( "ice_lab" ) ); - } else if( oter_above == "central_lab_core" ) { - handle_lab_core( p, central_lab_points, lab_type::central ); - } else if( oter_above == "central_lab_stairs" ) { - ter_set( p, oter_id( "central_lab" ) ); - } else if( is_ot_match( "hidden_lab_stairs", oter_above, ot_match_type::contains ) ) { - handle_lab_core( p, lab_points, lab_type::standard ); } else if( is_ot_match( "mine_entrance", oter_ground, ot_match_type::prefix ) && z == -2 ) { mine_points.emplace_back( ( p + tripoint_west ).xy(), rng( 6 + z, 10 + z ) ); requires_sub = true; @@ -1798,120 +3245,10 @@ bool overmap::generate_sub( const int z ) for( auto &i : goo_points ) { requires_sub |= build_slimepit( tripoint_om_omt( i.pos, z ), i.size ); } + const overmap_connection_id sewer_tunnel( "sewer_tunnel" ); connect_closest_points( sewer_points, z, *sewer_tunnel ); - // A third of overmaps have labs with a 1-in-2 chance of being subway connected. - // If a central (but not truly central) lab exists, all labs which go down to z=4 will have a subway to central. - int lab_train_odds = 0; - if( z == -2 && one_in( 3 ) ) { - lab_train_odds = 2; - } - if( z == -4 && !central_lab_points.empty() && pos() != point_abs_om() ) { - lab_train_odds = 1; - } - - const auto handle_lab_type = [&]( const std::string & prefix, - const std::vector &pts ) { - for( auto &i : pts ) { - const tripoint_om_omt p( i, z ); - lab *l = find_lab_for( p ); - if( l == nullptr ) { - // FIXME: figure out why this happens - // debugmsg( "Couldn't find lab for point %s on overmap %s", p.to_string(), - // pos().to_string() ); - continue; - } - int size = l->type == lab_type::central ? rng( std::max( 1, 7 + z ), 9 + z ) : rng( 1, 5 + z ); - bool goes_lower = build_lab( p, *l, size, lab_train_points, prefix, lab_train_odds ); - requires_sub |= goes_lower; - if( !goes_lower && ter( p ) == oter_id( prefix + "lab_core" ) ) { - ter_set( p, oter_id( prefix + "lab" ) ); - } - } - }; - - handle_lab_type( "", lab_points ); - handle_lab_type( "ice_", ice_lab_points ); - handle_lab_type( "central_", central_lab_points ); - - const auto create_real_train_lab_points = [this, z]( - const std::vector &train_points, - std::vector &real_train_points ) { - bool is_first_in_pair = true; - for( auto &p : train_points ) { - tripoint_om_omt i( p, z ); - const std::vector nearby_locations { - i + point_north, - i + point_south, - i + point_east, - i + point_west }; - if( is_first_in_pair ) { - ter_set( i, oter_id( "open_air" ) ); // mark tile to prevent subway gen - - for( auto &nearby_loc : nearby_locations ) { - if( is_ot_match( "empty_rock", ter( nearby_loc ), ot_match_type::contains ) ) { - // mark tile to prevent subway gen - ter_set( nearby_loc, oter_id( "open_air" ) ); - } - } - } else { - // change train connection point back to rock to allow gen - if( is_ot_match( "open_air", ter( i ), ot_match_type::contains ) ) { - ter_set( i, oter_id( "empty_rock" ) ); - } - real_train_points.push_back( i.xy() ); - } - is_first_in_pair = !is_first_in_pair; - } - }; - std::vector - subway_lab_train_points; // real points for subway, excluding train depot points - create_real_train_lab_points( lab_train_points, subway_lab_train_points ); - create_real_train_lab_points( central_lab_train_points, subway_lab_train_points ); - - subway_points.insert( subway_points.end(), subway_lab_train_points.begin(), - subway_lab_train_points.end() ); - connect_closest_points( subway_points, z, *subway_tunnel ); - - // The first lab point is adjacent to a lab, set it a depot (as long as track was actually laid). - const auto create_train_depots = [this, z, &subway_tunnel]( const oter_id & train_type, - const std::vector &train_points ) { - bool is_first_in_pair = true; - std::vector extra_route; - for( auto &p : train_points ) { - tripoint_om_omt i( p, z ); - if( is_first_in_pair ) { - const std::vector subway_possible_loc { - i + point_north, - i + point_south, - i + point_east, - i + point_west }; - extra_route.clear(); - ter_set( i, oter_id( "empty_rock" ) ); // this clears marked tiles - bool is_depot_generated = false; - for( auto &subway_loc : subway_possible_loc ) { - if( !is_depot_generated && - is_ot_match( "subway", ter( subway_loc ), ot_match_type::contains ) ) { - extra_route.push_back( i.xy() ); - extra_route.push_back( subway_loc.xy() ); - connect_closest_points( extra_route, z, *subway_tunnel ); - - ter_set( i, train_type ); - is_depot_generated = true; // only one connection to depot - } else if( is_ot_match( "open_air", ter( subway_loc ), - ot_match_type::contains ) ) { - // clear marked - ter_set( subway_loc, oter_id( "empty_rock" ) ); - } - } - } - is_first_in_pair = !is_first_in_pair; - } - }; - create_train_depots( oter_id( "lab_train_depot" ), lab_train_points ); - create_train_depots( oter_id( "central_lab_train_depot" ), central_lab_train_points ); - for( auto &i : cities ) { tripoint_om_omt omt_pos( i.pos, z ); tripoint_om_sm sm_pos = project_to( omt_pos ); @@ -1931,19 +3268,6 @@ bool overmap::generate_sub( const int z ) build_mine( tripoint_om_omt( i.pos, z ), i.size ); } - for( auto &i : ant_points ) { - const tripoint_om_omt p_loc( i.pos, z ); - if( ter( p_loc ) != "empty_rock" ) { - continue; - } - mongroup_id ant_group( ter( p_loc + tripoint_above ) == "anthill" ? - "GROUP_ANT" : "GROUP_ANT_ACID" ); - add_mon_group( - mongroup( ant_group, tripoint_om_sm( project_to( i.pos ), z ), - ( i.size * 3 ) / 2, rng( 6000, 8000 ) ) ); - build_anthill( p_loc, i.size, ter( p_loc + tripoint_above ) == "anthill" ); - } - return requires_sub; } @@ -2139,10 +3463,6 @@ void overmap::clear_cities() { cities.clear(); } -void overmap::clear_labs() -{ - labs.clear(); -} void overmap::clear_connections_out() { connections_out.clear(); @@ -3230,422 +4550,102 @@ overmap_special_id overmap::pick_random_building_to_place( int town_dist ) const } else { return city_spec.pick_house(); } -} - -void overmap::place_building( const tripoint_om_omt &p, om_direction::type dir, const city &town ) -{ - const tripoint_om_omt building_pos = p + om_direction::displace( dir ); - const om_direction::type building_dir = om_direction::opposite( dir ); - - const int town_dist = ( trig_dist( building_pos.xy(), town.pos ) * 100 ) / std::max( town.size, 1 ); - - for( size_t retries = 10; retries > 0; --retries ) { - const overmap_special_id building_tid = pick_random_building_to_place( town_dist ); - - if( can_place_special( *building_tid, building_pos, building_dir, false ) ) { - place_special( *building_tid, building_pos, building_dir, town, false, false ); - break; - } - } -} - -void overmap::build_city_street( - const overmap_connection &connection, const point_om_omt &p, int cs, - om_direction::type dir, const city &town, int block_width ) -{ - int c = cs; - int croad = cs; - - if( dir == om_direction::type::invalid ) { - debugmsg( "Invalid road direction." ); - return; - } - - const pf::directed_path street_path = lay_out_street( connection, p, dir, cs + 1 ); - - if( street_path.nodes.size() <= 1 ) { - return; // Don't bother. - } - // Build the actual street. - build_connection( connection, street_path, 0 ); - // Grow in the stated direction, sprouting off sub-roads and placing buildings as we go. - const auto from = std::next( street_path.nodes.begin() ); - const auto to = street_path.nodes.end(); - - //Alternate wide and thin blocks - int new_width = block_width == 2 ? rng( 3, 5 ) : 2; - - for( auto iter = from; iter != to; ++iter ) { - --c; - - const tripoint_om_omt rp( iter->pos, 0 ); - if( c >= 2 && c < croad - block_width ) { - croad = c; - int left = cs - rng( 1, 3 ); - int right = cs - rng( 1, 3 ); - - //Remove 1 length road nubs - if( left == 1 ) { - left++; - } - if( right == 1 ) { - right++; - } - - build_city_street( connection, iter->pos, left, om_direction::turn_left( dir ), - town, new_width ); - - build_city_street( connection, iter->pos, right, om_direction::turn_right( dir ), - town, new_width ); - - const oter_id &oter = ter( rp ); - // TODO: Get rid of the hardcoded terrain ids. - if( one_in( 2 ) && oter->get_line() == 15 && oter->type_is( oter_type_id( "road" ) ) ) { - ter_set( rp, oter_id( "road_nesw_manhole" ) ); - } - } - - if( !one_in( BUILDINGCHANCE ) ) { - place_building( rp, om_direction::turn_left( dir ), town ); - } - if( !one_in( BUILDINGCHANCE ) ) { - place_building( rp, om_direction::turn_right( dir ), town ); - } - } - - // If we're big, make a right turn at the edge of town. - // Seems to make little neighborhoods. - cs -= rng( 1, 3 ); - - if( cs >= 2 && c == 0 ) { - const auto &last_node = street_path.nodes.back(); - const auto rnd_dir = om_direction::turn_random( dir ); - build_city_street( connection, last_node.pos, cs, rnd_dir, town ); - if( one_in( 5 ) ) { - build_city_street( connection, last_node.pos, cs, om_direction::opposite( rnd_dir ), - town, new_width ); - } - } -} - -bool overmap::build_lab( const tripoint_om_omt &p, lab &l, int s, - std::vector &lab_train_points, - const std::string &prefix, int train_odds ) -{ - std::vector generated_lab; - const oter_id labt( prefix + "lab" ); - const oter_id labt_stairs( labt.id().str() + "_stairs" ); - const oter_id labt_core( labt.id().str() + "_core" ); - const oter_id labt_finale( labt.id().str() + "_finale" ); - const oter_id labt_ants( "ants_lab" ); - const oter_id labt_ants_stairs( "ants_lab_stairs" ); - - bool is_true_center = pos() == point_abs_om() && l.type == lab_type::central; - - ter_set( p, labt ); - generated_lab.push_back( p ); - - const auto is_same_lab = [&]( const tripoint_om_omt & p ) { - const std::set types = {{ - labt, labt_stairs, labt_core, labt_finale, labt_ants, labt_ants_stairs - } - }; - return types.count( ter( p ) ) > 0; - }; - - // maintain a list of potential new lab tiles - // grows outwards from previously placed lab tiles - std::set closed_candidates; - std::set candidates; - candidates.insert( { p + point_north, p + point_east, p + point_south, p + point_west } ); - while( !candidates.empty() ) { - const tripoint_om_omt cand = *candidates.begin(); - candidates.erase( candidates.begin() ); - closed_candidates.insert( cand ); - if( !inbounds( cand ) || is_same_lab( cand ) ) { - continue; - } - const int dist = manhattan_dist( p.xy(), cand.xy() ); - if( dist <= s * 2 ) { // increase radius to compensate for sparser new algorithm - int dist_increment = s > 3 ? 3 : 2; // Determines at what distance the odds of placement decreases - if( one_in( dist / dist_increment + 1 ) ) { // odds diminish farther away from the stairs - // make an ants lab if it's a basic lab and ants were there before. - if( prefix.empty() && check_ot( "ants", ot_match_type::type, cand ) ) { - if( ter( cand ) != "ants_queen" ) { // skip over a queen's chamber. - ter_set( cand, labt_ants ); - } - } else { - ter_set( cand, labt ); - } - generated_lab.push_back( cand ); - // add new candidates, don't backtrack - for( point offset : four_adjacent_offsets ) { - const tripoint_om_omt new_cand = cand + offset; - const int new_dist = manhattan_dist( p.xy(), new_cand.xy() ); - if( closed_candidates.count( new_cand ) == 0 && new_dist > dist ) { - candidates.insert( new_cand ); - } - } - } - } - } - - bool generate_stairs = true; - for( tripoint_om_omt &elem : generated_lab ) { - // Use a check for "_stairs" to catch the hidden_lab_stairs tiles. - if( is_ot_match( "_stairs", ter( elem + tripoint_above ), ot_match_type::contains ) ) { - generate_stairs = false; - } - } - if( generate_stairs && !generated_lab.empty() ) { - std::shuffle( generated_lab.begin(), generated_lab.end(), rng_get_engine() ); - - // we want a spot where labs are above, but we'll settle for the last element if necessary. - tripoint_om_omt lab_pos; - for( tripoint_om_omt elem : generated_lab ) { - lab_pos = elem; - if( ter( lab_pos + tripoint_above ) == labt ) { - break; - } - } - ter_set( lab_pos + tripoint_above, labt_stairs ); - } - - ter_set( p, labt_core ); - int numstairs = 0; - if( s > 0 && p.z() != -OVERMAP_DEPTH ) { // Build stairs going down - while( !one_in( 6 ) ) { - tripoint_om_omt stair; - int tries = 0; - do { - stair = p + point( rng( -s, s ), rng( -s, s ) ); - tries++; - } while( ( ter( stair ) != labt && ter( stair ) != labt_ants ) && tries < 15 ); - if( tries < 15 ) { - if( ter( stair ) == labt_ants ) { - ter_set( stair, labt_ants_stairs ); - } else { - ter_set( stair, labt_stairs ); - } - numstairs++; - } - } - } - - bool endgame_finale = is_true_center && numstairs == 0; - // We need a finale on the bottom of labs. Central labs have a chance of additional finales. - if( numstairs == 0 || ( l.type == lab_type::central && one_in( -p.z() - 1 ) ) ) { - std::set finale_candidates; - // This is for when we can't find any proper candidates - std::set secondary_candidates; - for( const tripoint_om_omt &c : closest_points_first( p, s ) ) { - if( ter( c ) == labt || ( !endgame_finale && ter( c ) == labt_core ) ) { - finale_candidates.insert( c ); - } - } - - if( endgame_finale ) { - for( const tripoint_om_omt &c : closest_points_first( p, 2 * s + 1 ) ) { - if( is_ot_match( labt.id().str(), ter( c ), ot_match_type::contains ) ) { - for( point offset : four_adjacent_offsets ) { - if( inbounds( c + offset ) && ter( c + offset ) != labt_stairs ) { - secondary_candidates.insert( c + offset ); - } - } - } - } - } - - tripoint_om_omt finale_pos = p; - if( !finale_candidates.empty() ) { - finale_pos = random_entry( finale_candidates ); - } else if( !secondary_candidates.empty() ) { - finale_pos = random_entry( secondary_candidates ); - } else if( endgame_finale ) { - debugmsg( "Endgame finale could not be generated for lab at %s.", p.to_string() ); - } - - ter_set( finale_pos, labt_finale ); - l.finales.insert( finale_pos ); - if( std::count( generated_lab.begin(), generated_lab.end(), finale_pos ) == 0 ) { - generated_lab.push_back( finale_pos ); - } - } - - for( const tripoint_om_omt &p : generated_lab ) { - l.tiles.insert( p ); - } - - if( !is_true_center && train_odds > 0 && one_in( train_odds ) ) { - tripoint_om_omt train; - int tries = 0; - int adjacent_labs; - - do { - train = p + point( rng( -s * 1.5 - 1, s * 1.5 + 1 ), rng( -s * 1.5 - 1, s * 1.5 + 1 ) ); - tries++; - - adjacent_labs = 0; - for( point offset : four_adjacent_offsets ) { - if( is_ot_match( "lab", ter( train + offset ), ot_match_type::contains ) ) { - ++adjacent_labs; - } - } - } while( tries < 50 && ( - ter( train ) == labt || - ter( train ) == labt_stairs || - ter( train ) == labt_finale || - adjacent_labs != 1 ) ); - if( tries < 50 ) { - lab_train_points.push_back( train.xy() ); // possible train depot - // next is rail connection - for( point offset : four_adjacent_offsets ) { - if( is_ot_match( "lab", ter( train + offset ), ot_match_type::contains ) ) { - lab_train_points.push_back( train.xy() - offset ); - break; - } - } - } - } +} - // 4th story of labs and down are candidates for lab escape, as long as there's no train or finale. - if( prefix.empty() && p.z() <= -4 && train_odds == 0 && numstairs > 0 ) { - tripoint_om_omt cell; - int tries = 0; - int adjacent_labs = 0; +void overmap::place_building( const tripoint_om_omt &p, om_direction::type dir, const city &town ) +{ + const tripoint_om_omt building_pos = p + om_direction::displace( dir ); + const om_direction::type building_dir = om_direction::opposite( dir ); - // Find a space bordering just one lab to the south. - do { - cell = p + point( rng( -s * 1.5 - 1, s * 1.5 + 1 ), rng( -s * 1.5 - 1, s * 1.5 + 1 ) ); - tries++; + const int town_dist = ( trig_dist( building_pos.xy(), town.pos ) * 100 ) / std::max( town.size, 1 ); - adjacent_labs = 0; - for( point offset : four_adjacent_offsets ) { - if( is_ot_match( "lab", ter( cell + offset ), ot_match_type::contains ) ) { - ++adjacent_labs; - } - } - } while( tries < 50 && ( - ter( cell ) == labt_stairs || - ter( cell ) == labt_finale || - ter( cell + point_south ) != labt || - adjacent_labs != 1 ) ); - if( tries < 50 ) { - ter_set( cell, oter_id( "lab_escape_cells" ) ); - ter_set( cell + point_south, oter_id( "lab_escape_entrance" ) ); + for( size_t retries = 10; retries > 0; --retries ) { + const overmap_special_id building_tid = pick_random_building_to_place( town_dist ); + + if( can_place_special( *building_tid, building_pos, building_dir, false ) ) { + place_special( *building_tid, building_pos, building_dir, town, false, false ); + break; } } - - return numstairs > 0; } -void overmap::build_anthill( const tripoint_om_omt &p, int s, bool ordinary_ants ) +void overmap::build_city_street( + const overmap_connection &connection, const point_om_omt &p, int cs, + om_direction::type dir, const city &town, int block_width ) { - for( om_direction::type dir : om_direction::all ) { - build_tunnel( p, s - rng( 0, 3 ), dir, ordinary_ants ); - } + int c = cs; + int croad = cs; - // TODO: This should follow the tunnel network, - // as of now it can pick a tile from an adjacent ant network. - std::vector queenpoints; - for( int i = -s; i <= s; i++ ) { - for( int j = -s; j <= s; j++ ) { - const tripoint_om_omt qp = p + point( i, j ); - if( check_ot( "ants", ot_match_type::type, qp ) ) { - queenpoints.push_back( qp ); - } - } + if( dir == om_direction::type::invalid ) { + debugmsg( "Invalid road direction." ); + return; } - if( queenpoints.empty() ) { - debugmsg( "No queenpoints when building anthill, anthill over %s", ter( p ).id().str() ); + + const pf::directed_path street_path = lay_out_street( connection, p, dir, cs + 1 ); + + if( street_path.nodes.size() <= 1 ) { + return; // Don't bother. } - const tripoint_om_omt target = random_entry( queenpoints ); - ter_set( target, ordinary_ants ? oter_id( "ants_queen" ) : oter_id( "ants_queen_acid" ) ); + // Build the actual street. + build_connection( connection, street_path, 0 ); + // Grow in the stated direction, sprouting off sub-roads and placing buildings as we go. + const auto from = std::next( street_path.nodes.begin() ); + const auto to = street_path.nodes.end(); - const oter_id root_id( "ants_isolated" ); + //Alternate wide and thin blocks + int new_width = block_width == 2 ? rng( 3, 5 ) : 2; - for( int i = -s; i <= s; i++ ) { - for( int j = -s; j <= s; j++ ) { - const tripoint_om_omt root = p + point( i, j ); - if( !inbounds( root ) ) { - continue; + for( auto iter = from; iter != to; ++iter ) { + --c; + + const tripoint_om_omt rp( iter->pos, 0 ); + if( c >= 2 && c < croad - block_width ) { + croad = c; + int left = cs - rng( 1, 3 ); + int right = cs - rng( 1, 3 ); + + //Remove 1 length road nubs + if( left == 1 ) { + left++; } - if( root_id == ter( root )->id ) { - const oter_id &oter = ter( root ); - for( om_direction::type dir : om_direction::all ) { - const tripoint_om_omt neighbor = root + om_direction::displace( dir ); - if( check_ot( "ants", ot_match_type::prefix, neighbor ) ) { - size_t line = oter->get_line(); - line = om_lines::set_segment( line, dir ); - if( line != oter->get_line() ) { - ter_set( root, oter->get_type_id()->get_linear( line ) ); - } - } - } + if( right == 1 ) { + right++; } - } - } -} -void overmap::build_tunnel( const tripoint_om_omt &p, int s, om_direction::type dir, - bool ordinary_ants ) -{ - if( s <= 0 ) { - return; - } - if( !inbounds( p ) ) { - return; - } + build_city_street( connection, iter->pos, left, om_direction::turn_left( dir ), + town, new_width ); - const oter_id root_id( "ants_isolated" ); - if( root_id != ter( p )->id ) { - if( check_ot( "ants", ot_match_type::type, p ) ) { - return; - } - if( !is_ot_match( "empty_rock", ter( p ), ot_match_type::type ) ) { - return; - } - } + build_city_street( connection, iter->pos, right, om_direction::turn_right( dir ), + town, new_width ); - ter_set( p, oter_id( root_id ) ); + const oter_id &oter = ter( rp ); + // TODO: Get rid of the hardcoded terrain ids. + if( one_in( 2 ) && oter->get_line() == 15 && oter->type_is( oter_type_id( "road" ) ) ) { + ter_set( rp, oter_id( "road_nesw_manhole" ) ); + } + } - std::vector valid; - valid.reserve( om_direction::size ); - for( om_direction::type r : om_direction::all ) { - const tripoint_om_omt cand = p + om_direction::displace( r ); - if( !check_ot( "ants", ot_match_type::type, cand ) && - is_ot_match( "empty_rock", ter( cand ), ot_match_type::type ) ) { - valid.push_back( r ); + if( !one_in( BUILDINGCHANCE ) ) { + place_building( rp, om_direction::turn_left( dir ), town ); + } + if( !one_in( BUILDINGCHANCE ) ) { + place_building( rp, om_direction::turn_right( dir ), town ); } } - const oter_id ants_food( "ants_food" ); - const oter_id ants_larvae( "ants_larvae" ); - const oter_id ants_larvae_acid( "ants_larvae_acid" ); - const tripoint_om_omt next = - s != 1 ? p + om_direction::displace( dir ) : tripoint_om_omt( -1, -1, -1 ); - - for( auto r : valid ) { - const tripoint_om_omt cand = p + om_direction::displace( r ); - if( !inbounds( cand ) ) { - continue; - } + // If we're big, make a right turn at the edge of town. + // Seems to make little neighborhoods. + cs -= rng( 1, 3 ); - if( cand.xy() != next.xy() ) { - if( one_in( s * 2 ) ) { - // Spawn a special chamber - if( one_in( 2 ) ) { - ter_set( cand, ants_food ); - } else { - ter_set( cand, ordinary_ants ? ants_larvae : ants_larvae_acid ); - } - } else if( one_in( 5 ) ) { - // Branch off a side tunnel - build_tunnel( cand, s - rng( 1, 3 ), r, ordinary_ants ); - } + if( cs >= 2 && c == 0 ) { + const auto &last_node = street_path.nodes.back(); + const auto rnd_dir = om_direction::turn_random( dir ); + build_city_street( connection, last_node.pos, cs, rnd_dir, town ); + if( one_in( 5 ) ) { + build_city_street( connection, last_node.pos, cs, om_direction::opposite( rnd_dir ), + town, new_width ); } } - build_tunnel( next, s - 1, dir, ordinary_ants ); } bool overmap::build_slimepit( const tripoint_om_omt &origin, int s ) @@ -3864,12 +4864,52 @@ pf::directed_path overmap::lay_out_street( const overmap_connectio return straight_path( source, dir, actual_len ); } -void overmap::build_connection( +const std::vector &overmap_connection_cache::get_all( const overmap_connection_id &id, + const int z ) +{ + auto outer = cache.find( id ); + if( outer != cache.end() ) { + auto inner = outer->second.find( z ); + if( inner != outer->second.end() ) { + return inner->second; + } + } + // Return an empty vector if the keys do not exist + static const std::vector empty; + return empty; +} + +std::vector overmap_connection_cache::get_closests( + const overmap_connection_id &id, const int z, const point_om_omt &pos ) +{ + std::vector sorted = get_all( id, z ); + std::sort( sorted.begin(), sorted.end(), [&pos]( point_om_omt & a, point_om_omt & b ) { + return rl_dist( pos, a ) < rl_dist( pos, b ); + } ); + + return sorted; +} + +void overmap_connection_cache::add( const overmap_connection_id &id, const int z, + const point_om_omt &pos ) +{ + auto outer = cache.find( id ); + if( outer == cache.end() ) { + outer = cache.emplace( id, std::map> {} ).first; + } + auto inner = outer->second.find( z ); + if( inner == outer->second.end() ) { + inner = outer->second.emplace( z, std::vector {} ).first; + } + inner->second.push_back( pos ); +} + +bool overmap::build_connection( const overmap_connection &connection, const pf::directed_path &path, int z, const om_direction::type &initial_dir ) { if( path.nodes.empty() ) { - return; + return false; } om_direction::type prev_dir = initial_dir; @@ -3886,7 +4926,7 @@ void overmap::build_connection( if( !subtype ) { debugmsg( "No suitable subtype of connection \"%s\" found for \"%s\".", connection.id.c_str(), ter_id.id().c_str() ); - return; + return false; } if( subtype->terrain->is_linear() ) { @@ -3938,7 +4978,7 @@ void overmap::build_connection( if( new_line == om_lines::invalid ) { debugmsg( "Invalid path for connection \"%s\".", connection.id.c_str() ); - return; + return false; } ter_set( pos, subtype->terrain->get_linear( new_line ) ); @@ -3950,15 +4990,20 @@ void overmap::build_connection( prev_dir = new_dir; } + + if( connection_cache ) { + connection_cache->add( connection.id, z, start.pos ); + } + return true; } -void overmap::build_connection( const point_om_omt &source, const point_om_omt &dest, int z, +bool overmap::build_connection( const point_om_omt &source, const point_om_omt &dest, int z, const overmap_connection &connection, const bool must_be_unexplored, const om_direction::type &initial_dir ) { - build_connection( - connection, lay_out_connection( connection, source, dest, z, must_be_unexplored ), - z, initial_dir ); + return build_connection( + connection, lay_out_connection( connection, source, dest, z, must_be_unexplored ), + z, initial_dir ); } void overmap::connect_closest_points( const std::vector &points, int z, @@ -4136,18 +5181,6 @@ void overmap::polish_rivers( const overmap *north, const overmap *east, const ov } } -const std::string &om_direction::id( type dir ) -{ - static const std::array < std::string, size + 1 > ids = {{ - "", "north", "east", "south", "west" - } - }; - if( dir == om_direction::type::invalid ) { - debugmsg( "Invalid direction cannot have an id." ); - } - return ids[static_cast( dir ) + 1]; -} - std::string om_direction::name( type dir ) { static const std::array < std::string, size + 1 > names = {{ @@ -4175,6 +5208,7 @@ point om_direction::rotate( point p, type dir ) { switch( dir ) { case om_direction::type::invalid: + case om_direction::type::last: debugmsg( "Invalid overmap rotation (%d).", static_cast( dir ) ); // Intentional fallthrough. case om_direction::type::north: @@ -4287,10 +5321,11 @@ om_direction::type overmap::random_special_rotation( const overmap_special &spec ++last; } - if( !special.rotatable ) { + if( !special.is_rotatable() ) { break; } } + // Pick first valid rotation at random. std::shuffle( first, last, rng_get_engine() ); const auto rotation = std::find_if( first, last, [&]( om_direction::type elem ) { @@ -4309,8 +5344,10 @@ bool overmap::can_place_special( const overmap_special &special, const tripoint_ return false; } - return std::all_of( special.terrains.begin(), - special.terrains.end(), [&]( const overmap_special_terrain & elem ) { + const std::vector fixed_terrains = special.required_locations(); + + return std::all_of( fixed_terrains.begin(), fixed_terrains.end(), + [&]( const overmap_special_locations & elem ) { const tripoint_om_omt rp = p + om_direction::rotate( elem.p, dir ); if( !inbounds( rp, 1 ) ) { @@ -4338,133 +5375,305 @@ bool overmap::can_place_special( const overmap_special &special, const tripoint_ } ); } -// checks around the selected point to see if the special can be placed there -void overmap::place_special( - const overmap_special &special, const tripoint_om_omt &p, om_direction::type dir, - const city &cit, const bool must_be_unexplored, const bool force ) +static std::vector place_fixed_overmap_special( + overmap &om, const fixed_overmap_special_data &fixed_data, const tripoint_om_omt &p, + om_direction::type dir, bool blob ) { - assert( dir != om_direction::type::invalid ); - if( !force ) { - assert( can_place_special( special, p, dir, must_be_unexplored ) ); - } - - const bool blob = special.flags.count( "BLOB" ) > 0; - - { - bool is_lab = false; - lab_type type; - std::set all_points; - for( const auto &elem : special.terrains ) { - const tripoint_om_omt location = p + om_direction::rotate( elem.p, dir ); - all_points.insert( location ); - - - if( is_ot_match( "lab_stairs", elem.terrain, ot_match_type::contains ) ) { - is_lab = true; - if( is_ot_match( "central_lab", elem.terrain, ot_match_type::contains ) ) { - type = lab_type::central; - } else if( is_ot_match( "ice_lab", elem.terrain, ot_match_type::contains ) ) { - type = lab_type::ice; - } else { - type = lab_type::standard; - } - } - } + std::vector result; - if( is_lab ) { - labs.push_back( lab{type, all_points, {}} ); - } - } - - for( const auto &elem : special.terrains ) { + for( const auto &elem : fixed_data.terrains ) { const tripoint_om_omt location = p + om_direction::rotate( elem.p, dir ); + result.push_back( location ); const oter_id tid = elem.terrain->get_rotated( dir ); - overmap_special_placements[location] = special.id; - ter_set( location, tid ); + om.ter_set( location, tid ); if( blob ) { for( int x = -2; x <= 2; x++ ) { for( int y = -2; y <= 2; y++ ) { const tripoint_om_omt nearby_pos = location + point( x, y ); - if( !inbounds( nearby_pos ) ) { + if( !om.inbounds( nearby_pos ) ) { continue; } - if( one_in( 1 + std::abs( x ) + std::abs( y ) ) && elem.can_be_placed_on( ter( nearby_pos ) ) ) { - ter_set( nearby_pos, tid ); + if( one_in( 1 + std::abs( x ) + std::abs( y ) ) && elem.can_be_placed_on( om.ter( nearby_pos ) ) ) { + om.ter_set( nearby_pos, tid ); } } } } } - if( special.flags.count( "ELECTRIC_GRID" ) > 0 ) { - std::set special_points; - for( const auto &elem : special.terrains ) { - const tripoint_om_omt location = p + om_direction::rotate( elem.p, dir ); - special_points.insert( location ); + return result; +} + +// checks around the selected point to see if the special can be placed there +std::vector overmap::place_special( + const overmap_special &special, const tripoint_om_omt &p, om_direction::type dir, + const city &cit, const bool must_be_unexplored, const bool force ) +{ + assert( dir != om_direction::type::invalid ); + if( !force ) { + assert( can_place_special( special, p, dir, must_be_unexplored ) ); + } + + const bool blob = special.has_flag( "BLOB" ); + const bool grid = special.has_flag( "ELECTRIC_GRID" ); + + std::vector result; + std::vector connections; + switch( special.get_subtype() ) { + case overmap_special_subtype::fixed: { + const fixed_overmap_special_data &fixed_data = special.get_fixed_data(); + result = place_fixed_overmap_special( *this, fixed_data, p, dir, blob ); + break; + } + case overmap_special_subtype::mutable_: { + std::vector> joins; + auto placement = special.get_mutable_data().place( *this, p, special.is_rotatable() ); + result = std::get<0>( placement ); + connections = std::get<1>( placement ); + joins = std::get<2>( placement ); + for( const std::pair &join : joins ) { + joins_used[join.first] = join.second; + } + break; } + case overmap_special_subtype::last: + debugmsg( "Invalid overmap_special_subtype" ); + abort(); + } + // Combine fixed connections with mutable + for( const overmap_special_connection &elem : special.connections ) { + connections.push_back( { p, dir, &elem } ); + } + // Place connection + for( const connection_node &node : connections ) { + const overmap_special_connection &elem = *node.connection; + if( elem.connection ) { + const tripoint_om_omt rp = node.origin + om_direction::rotate( elem.p, node.rot ); + if( !elem.connection->can_start_at( ter( rp ) ) ) { + continue; + } - for( const tripoint_om_omt &p : special_points ) { - for( size_t i = 0; i < six_cardinal_directions.size(); i++ ) { - const tripoint_om_omt other = p + six_cardinal_directions[i]; - if( special_points.count( other ) > 0 ) { - electric_grid_connections[p].set( i, true ); + om_direction::type initial_dir = elem.initial_dir; + if( initial_dir != om_direction::type::invalid ) { + initial_dir = om_direction::add( initial_dir, node.rot ); + } + + bool linked = false; + // First, try to link to city, if layout allows that + if( elem.connection->get_layout() == overmap_connection_layout::city && cit ) { + linked = build_connection( cit.pos, rp.xy(), rp.z(), *elem.connection, + must_be_unexplored, initial_dir ); + } + if( !linked && connection_cache ) { + // If no city present, try to link to closest connection + auto points = connection_cache->get_closests( elem.connection->id, rp.z(), rp.xy() ); + for( const point_om_omt &pos : points ) { + if( ( linked = build_connection( pos, rp.xy(), rp.z(), *elem.connection, + must_be_unexplored, initial_dir ) ) ) { + break; + } + } + } + if( !linked && !connection_cache ) { + // Cache is cleared once generation is done, if special is spawned via debug or for + // mission we'll need to perform search. It is slow, but that's a rare case + for( const tripoint_om_omt &p : closest_points_first( rp, OMAPX ) ) { + if( !inbounds( p ) || std::find( result.begin(), result.end(), p ) != result.end() || + !check_ot( elem.connection->id->default_terrain.str(), ot_match_type::type, p ) ) { + continue; + } + if( ( linked = build_connection( p.xy(), rp.xy(), rp.z(), *elem.connection, + must_be_unexplored, initial_dir ) ) ) { + break; + } } } + if( !linked && initial_dir != om_direction::type::invalid ) { + // If nothing found, make a stub for a clean break, and also to connect other specials here later on + pf::directed_path stub; + stub.nodes.emplace_back( rp.xy(), om_direction::opposite( initial_dir ) ); + linked = build_connection( *elem.connection, stub, rp.z() ); + } } } - // Make connections. - if( cit ) { - for( const auto &elem : special.connections ) { - if( elem.connection ) { - const tripoint_om_omt rp = p + om_direction::rotate( elem.p, dir ); - om_direction::type initial_dir = elem.initial_dir; - - if( initial_dir != om_direction::type::invalid ) { - initial_dir = om_direction::add( initial_dir, dir ); + // Link grid + for( const tripoint_om_omt &location : result ) { + overmap_special_placements[location] = special.id; + if( grid ) { + for( size_t i = 0; i < six_cardinal_directions.size(); i++ ) { + const tripoint_om_omt other = location + six_cardinal_directions[i]; + if( std::find( result.begin(), result.end(), other ) != result.end() ) { + electric_grid_connections[location].set( i, true ); } - - build_connection( cit.pos, rp.xy(), elem.p.z, *elem.connection, must_be_unexplored, - initial_dir ); } } } // Place spawns. - if( special.spawns.group ) { - const overmap_special_spawns &spawns = special.spawns; + const overmap_special_spawns &spawns = special.get_monster_spawns(); + if( spawns.group ) { const int pop = rng( spawns.population.min, spawns.population.max ); const int rad = rng( spawns.radius.min, spawns.radius.max ); add_mon_group( mongroup( spawns.group, project_to( p ), rad, pop ) ); } + + for( const auto &nested : special.get_nested_specials() ) { + const tripoint_rel_omt shift = om_direction::rotate( nested.first, dir ); + const tripoint_om_omt rp = p + shift; + if( can_place_special( *nested.second, rp, dir, false ) ) { + std::vector nested_result = place_special( *nested.second, rp, dir, + get_nearest_city( rp ), false, false ); + for( const auto &nested_point : nested_result ) { + result.push_back( nested_point + shift ); + } + } + } + + return result; +} + +// Points list maintaining custom order, and allowing fast removal by coordinates +struct specials_overlay { + std::list order; + std::list::iterator lookup[OMAPX][OMAPY]; + + specials_overlay( std::vector &points ) { + // Shuffle all points to randomly distribute specials across the overmap + std::shuffle( points.begin(), points.end(), rng_get_engine() ); + + order = { std::make_move_iterator( points.begin() ), + std::make_move_iterator( points.end() ) + }; + + for( int x = 0; x < OMAPX; x++ ) { + for( int y = 0; y < OMAPY; y++ ) { + lookup[x][y] = order.end(); + } + } + for( auto it = order.begin(); it != order.end(); it++ ) { + lookup[it->x()][it->y()] = it; + } + } + + std::list::iterator begin() { + return order.begin(); + } + std::list::iterator end() { + return order.end(); + } + + void erase_other( const std::list::iterator &i, const tripoint_om_omt &pos ) { + if( !overmap::inbounds( pos ) || i->raw() == pos.raw() ) { + return; + } + auto it = lookup[pos.x()][pos.y()]; + if( it != order.end() ) { + lookup[pos.x()][pos.y()] = order.end(); + order.erase( it ); + } + } + std::list::iterator erase_this( const std::list::iterator &i ) { + auto it = lookup[i->x()][i->y()]; + if( it != order.end() ) { + lookup[i->x()][i->y()] = order.end(); + return order.erase( it ); + } + return i; + } +}; + +int overmap::place_special_custom( const overmap_special &special, + std::vector &points ) +{ + specials_overlay target( points ); + return place_special_attempt( special, 1, target, true ); } int overmap::place_special_attempt( const overmap_special &special, const int max, - std::vector &points, const bool must_be_unexplored ) + specials_overlay &points, const bool must_be_unexplored ) { if( max < 1 ) { return 0; } + + const int RANGE = get_option( "SPECIALS_SPACING" ); + + // Check how many cities are suitable for this specials + // and try to distribute specials between then + bool need_city = special.requires_city(); + int max_per_city = INT_MAX; + std::unordered_map valid_city; + if( need_city ) { + int valid_cities = 0; + for( const auto &city : cities ) { + if( special.get_constraints().city_size.contains( city.size ) ) { + valid_cities++; + valid_city[&city] = 0; + } + } + if( valid_cities < 1 ) { + return 0; + } + max_per_city = std::ceil( static_cast( max ) / valid_cities ); + } + int placed = 0; for( auto p = points.begin(); p != points.end(); ) { const city &nearest_city = get_nearest_city( *p ); // City check is the fastest => it goes first. - if( !special.can_belong_to_city( *p, nearest_city ) ) { - p++; - continue; + if( need_city ) { + if( valid_city.count( &nearest_city ) < 1 || + valid_city[&nearest_city] >= max_per_city || + !special.can_belong_to_city( *p, nearest_city ) ) { + p++; + continue; + } } + // See if we can actually place the special there. const auto rotation = random_special_rotation( special, *p, must_be_unexplored ); if( rotation == om_direction::type::invalid ) { p++; continue; } - place_special( special, *p, rotation, nearest_city, false, must_be_unexplored ); + std::vector result = place_special( special, *p, rotation, nearest_city, false, + must_be_unexplored ); + if( need_city ) { + valid_city[&nearest_city]++; + } + + // Remove all used points from our candidates list + if( RANGE == 0 ) { + for( const auto &dest : result ) { + if( dest.z() == 0 ) { + points.erase_other( p, dest ); + } + } + } else if( RANGE > 0 ) { + tripoint_om_omt p_min = { INT_MAX, INT_MAX, 0 }; + tripoint_om_omt p_max = { INT_MIN, INT_MIN, 0 }; + for( const auto &dest : result ) { + if( dest.z() == 0 ) { + p_min.raw().x = std::min( p_min.x(), dest.x() ); + p_max.raw().x = std::max( p_max.x(), dest.x() ); + p_min.raw().y = std::min( p_min.y(), dest.y() ); + p_max.raw().y = std::max( p_max.y(), dest.y() ); + } + } + const point_rel_omt shift( RANGE, RANGE ); + for( const tripoint_om_omt &dest : tripoint_range( p_min - shift, + p_max + shift ) ) { + points.erase_other( p, dest ); + } + } + + // Sometimes points can be reused with different rotation, wa want to avoid + // that for better dispertion - make sure current origin is removed even if + // this special doesn't place any surface omt + p = points.erase_this( p ); - // Sometimes points can be reused with different rotation - // wa want to avoid that for better dispertion - p = points.erase( p ); if( ++placed >= max ) { break; } @@ -4475,12 +5684,54 @@ int overmap::place_special_attempt( const overmap_special &special, const int ma // Iterate over overmap searching for valid locations, and placing specials void overmap::place_specials( overmap_special_batch &enabled_specials ) { + const int RANGE = std::max( 0, get_option( "SPECIALS_SPACING" ) ); + const float DENSITY = get_option( "SPECIALS_DENSITY" ); + + // Rough estimate how many space our specials about to take with + // our range, density, and available specials, if we can't possibly + // have that much - tune density down + float land_needed = 0; + float lake_needed = 0; + + std::unordered_map special_area; + for( auto &iter : enabled_specials ) { + const overmap_special &special = *iter.special_details; + + // preview_terrains() would give more accurate area, but + // since we don't taking in account cities, and assumes + // tight packig of terrains - some overestimation won't hurt + special_area[special.id] = special.required_locations().size(); + const numeric_interval &o = special.get_constraints().occurrences; + + float average_amount = special.has_flag( "UNIQUE" ) ? + static_cast( o.min ) / o.max : + static_cast( o.min + o.max ) / 2; + + float total_area = std::pow( std::sqrt( special_area[special.id] ) + RANGE, 2.0 ) * + average_amount * DENSITY; + if( special.has_flag( "LAKE" ) ) { + lake_needed += total_area; + } else { + land_needed += total_area; + } + } + + // Most of the setups should end with x1 multiplier, but some dire combinations + // of settings like maxed spacing and density may actually need adjusting to + // give a things on bottom of the list a chance to spawn + float lake_crowd_ratio = std::min( 1.0f, OMAPX * OMAPY / lake_needed ); + float land_crowd_ratio = std::min( 1.0f, OMAPX * OMAPY / land_needed ); + // Sort specials be they sizes - placing big things is faster // and easier while we have most of map still empty, and also // that central lab will have top priority bool is_true_center = pos() == point_abs_om(); - const auto special_weight = [&is_true_center]( const overmap_special * s ) { - return s->terrains.size() * ( is_true_center && s->flags.count( "ENDGAME" ) ? 1000 : 1 ); + const auto special_weight = [&]( const overmap_special * s ) { + int weight = special_area[s->id]; + if( is_true_center && s->has_flag( "ENDGAME" ) ) { + weight *= 1000; + } + return weight; }; std::sort( enabled_specials.begin(), enabled_specials.end(), [&special_weight]( const overmap_special_placement & a, const overmap_special_placement & b ) { @@ -4504,26 +5755,27 @@ void overmap::place_specials( overmap_special_batch &enabled_specials ) // Calculate water to land ratio to normalize specials occurencies // we don't want to dump all specials on overmap covered by water float lake_rate = lake_points.size() / static_cast( OMAPX * OMAPY ) * - get_option( "SPECIALS_DENSITY" ); + lake_crowd_ratio * DENSITY; float land_rate = land_points.size() / static_cast( OMAPX * OMAPY ) * - get_option( "SPECIALS_DENSITY" ); + land_crowd_ratio * DENSITY; + + specials_overlay lake( lake_points ); + specials_overlay land( land_points ); - // Shuffle all points to distribute specials across the overmap - std::shuffle( lake_points.begin(), lake_points.end(), rng_get_engine() ); - std::shuffle( land_points.begin(), land_points.end(), rng_get_engine() ); // And here we go for( auto &iter : enabled_specials ) { const overmap_special &special = *iter.special_details; + const overmap_special_placement_constraints &constraints = special.get_constraints(); - const int min = special.occurrences.min; - const int max = special.occurrences.max; + const int min = constraints.occurrences.min; + const int max = constraints.occurrences.max; - const bool is_lake = special.flags.count( "LAKE" ); - const float rate = is_true_center && special.flags.count( "ENDGAME" ) ? 1 : + const bool is_lake = special.has_flag( "LAKE" ); + const float rate = is_true_center && special.has_flag( "ENDGAME" ) ? 1 : ( is_lake ? lake_rate : land_rate ); int amount_to_place; - if( special.flags.count( "UNIQUE" ) ) { + if( special.has_flag( "UNIQUE" ) ) { int chance = roll_remainder( min * rate ); amount_to_place = x_in_y( chance, max ) ? 1 : 0; } else { @@ -4533,7 +5785,7 @@ void overmap::place_specials( overmap_special_batch &enabled_specials ) } iter.instances_placed += place_special_attempt( special, - amount_to_place, ( is_lake ? lake_points : land_points ), false ); + amount_to_place, ( is_lake ? lake : land ), false ); } } @@ -4840,17 +6092,15 @@ overmap_special_id overmap_specials::create_building_from( const oter_type_str_i static const overmap_location_id land( "land" ); static const overmap_location_id swamp( "swamp" ); - overmap_special new_special; - - new_special.id = overmap_special_id( "FakeSpecial_" + base.str() ); - overmap_special_terrain ter; ter.terrain = base.obj().get_first().id(); ter.locations.insert( land ); ter.locations.insert( swamp ); - new_special.terrains.push_back( ter ); - new_special.flags.insert( "ELECTRIC_GRID" ); + overmap_special_id new_id( "FakeSpecial_" + base.str() ); + overmap_special new_special( new_id, ter ); + + new_special.set_flag( "ELECTRIC_GRID" ); return specials.insert( new_special ).id; } diff --git a/src/overmap.h b/src/overmap.h index 024cc2c76be6..5a368d9a1ab5 100644 --- a/src/overmap.h +++ b/src/overmap.h @@ -19,6 +19,7 @@ #include #include "coordinates.h" +#include "cube_direction.h" #include "enums.h" #include "enum_conversions.h" #include "game_constants.h" @@ -43,6 +44,7 @@ class overmap_connection; class overmap_special; class overmap_special_batch; struct regional_settings; +struct specials_overlay; template struct enum_traits; namespace pf @@ -65,32 +67,6 @@ struct city { int get_distance_from( const tripoint_om_omt &p ) const; }; -enum class lab_type : int { - standard = 0, - ice, - central, - invalid -}; - -namespace io -{ - -template<> -std::string enum_to_string( lab_type data ); - -} // namespace io - -template<> -struct enum_traits { - static constexpr auto last = lab_type::invalid; -}; - -struct lab { - lab_type type; - std::set tiles; - std::set finales; -}; - struct om_note { std::string text; point_om_omt p; @@ -143,6 +119,7 @@ static const std::map oter_flags_map = { { "RIVER", river_tile }, { "SIDEWALK", has_sidewalk }, { "NO_ROTATE", no_rotate }, + { "IGNORE_ROTATION_FOR_ADJACENCY", ignore_rotation_for_adjacency }, { "LINEAR", line_drawing }, { "SUBWAY", subway_connection }, { "LAKE", lake }, @@ -174,6 +151,46 @@ static const std::map oter_flags_map = { { "SOURCE_WEAPON", source_weapon } }; +template +struct pos_dir { + Tripoint p; + cube_direction dir; + + pos_dir opposite() const; + + void serialize( JsonOut &jsout ) const; + void deserialize( JsonIn &jsin ); + + bool operator==( const pos_dir &r ) const; + bool operator<( const pos_dir &r ) const; +}; + +extern template struct pos_dir; +extern template struct pos_dir; + +using om_pos_dir = pos_dir; +using rel_pos_dir = pos_dir; + +namespace std +{ +template +struct hash> { + size_t operator()( const pos_dir &p ) const { + cata::tuple_hash h; + return h( std::make_tuple( p.p, p.dir ) ); + } +}; +} // namespace std + +struct overmap_connection_cache { + std::map>> cache; + + const std::vector &get_all( const overmap_connection_id &id, const int z ); + std::vector get_closests( const overmap_connection_id &id, const int z, + const point_om_omt &pos ); + void add( const overmap_connection_id &id, const int z, const point_om_omt &pos ); +}; + class overmap { public: @@ -213,6 +230,7 @@ class overmap void ter_set( const tripoint_om_omt &p, const oter_id &id ); const oter_id &ter( const tripoint_om_omt &p ) const; + std::string *join_used_at( const om_pos_dir & ); bool &seen( const tripoint_om_omt &p ); bool seen( const tripoint_om_omt &p ) const; bool &explored( const tripoint_om_omt &p ); @@ -287,7 +305,6 @@ class overmap void clear_mon_groups(); void clear_overmap_special_placements(); void clear_cities(); - void clear_labs(); void clear_connections_out(); void place_special_forced( const overmap_special_id &special_id, const tripoint_om_omt &p, om_direction::type dir ); @@ -311,8 +328,8 @@ class overmap std::map vehicles; std::vector camps; std::vector cities; - std::vector labs; std::map> connections_out; + std::optional connection_cache; std::optional find_camp( const point_abs_omt &p ); /// Adds the npc to the contained list of npcs ( @ref npcs ). void insert_npc( const shared_ptr_fast &who ); @@ -350,6 +367,10 @@ class overmap const regional_settings *settings; + // Records the joins that were chosen during placement of a mutable + // special, so that it can be queried later by mapgen + std::unordered_map joins_used; + oter_id get_default_terrain( int z ) const; // Initialize @@ -414,12 +435,6 @@ class overmap void build_city_street( const overmap_connection &connection, const point_om_omt &p, int cs, om_direction::type dir, const city &town, int block_width = 2 ); - bool build_lab( const tripoint_om_omt &p, lab &l, int size, - std::vector &lab_train_points, - const std::string &prefix, int train_odds ); - void build_anthill( const tripoint_om_omt &p, int s, bool ordinary_ants = true ); - void build_tunnel( const tripoint_om_omt &p, int s, om_direction::type dir, - bool ordinary_ants = true ); bool build_slimepit( const tripoint_om_omt &origin, int s ); void build_mine( const tripoint_om_omt &origin, int s ); @@ -430,11 +445,11 @@ class overmap pf::directed_path lay_out_street( const overmap_connection &connection, const point_om_omt &source, om_direction::type dir, size_t len ) const; - - void build_connection( + public: + bool build_connection( const overmap_connection &connection, const pf::directed_path &path, int z, const om_direction::type &initial_dir = om_direction::type::invalid ); - void build_connection( const point_om_omt &source, const point_om_omt &dest, int z, + bool build_connection( const point_om_omt &source, const point_om_omt &dest, int z, const overmap_connection &connection, bool must_be_unexplored, const om_direction::type &initial_dir = om_direction::type::invalid ); void connect_closest_points( const std::vector &points, int z, @@ -455,9 +470,10 @@ class overmap bool can_place_special( const overmap_special &special, const tripoint_om_omt &p, om_direction::type dir, bool must_be_unexplored ) const; - void place_special( + std::vector place_special( const overmap_special &special, const tripoint_om_omt &p, om_direction::type dir, const city &cit, bool must_be_unexplored, bool force ); + private: /** * Iterate over the overmap and place the quota of specials. * If the stated minimums are not reached, it will spawn a new nearby overmap @@ -470,13 +486,15 @@ class overmap * Iterate over given points, placing specials if possible. * @param special The overmap special to place. * @param max Maximum amount of specials to place. - * @param points Vector of allowed origins for new specials, used points will be erased. + * @param points Struct tracking points allowed to spawn special. * @param must_be_unexplored If true, will require that all of the * terrains where the special would be placed are unexplored. * @returns Actual amount of placed specials. **/ int place_special_attempt( const overmap_special &special, const int max, - std::vector &points, const bool must_be_unexplored ); + specials_overlay &points, const bool must_be_unexplored ); + + int place_special_custom( const overmap_special &special, std::vector &points ); void place_mongroups(); void place_radios(); diff --git a/src/overmap_connection.cpp b/src/overmap_connection.cpp index 175b62d6a906..a709462525c3 100644 --- a/src/overmap_connection.cpp +++ b/src/overmap_connection.cpp @@ -18,6 +18,26 @@ generic_factory connections( "overmap connection" ); } // namespace +namespace io +{ + +template<> +std::string enum_to_string( overmap_connection_layout data ) +{ + switch( data ) { + // *INDENT-OFF* + case overmap_connection_layout::city: return "city"; + case overmap_connection_layout::p2p: return "p2p"; + // *INDENT-ON* + case overmap_connection_layout::last: + break; + } + debugmsg( "Invalid overmap_connection_layout" ); + abort(); +} + +} // namespace io + static const std::map connection_subtype_flag_map = { { "ORTHOGONAL", overmap_connection::subtype::flag::orthogonal }, @@ -108,6 +128,7 @@ void overmap_connection::load( const JsonObject &jo, const std::string & ) { mandatory( jo, was_loaded, "default_terrain", default_terrain ); mandatory( jo, was_loaded, "subtypes", subtypes ); + optional( jo, was_loaded, "layout", layout, overmap_connection_layout::city ); } void overmap_connection::check() const diff --git a/src/overmap_connection.h b/src/overmap_connection.h index 4e00bcd7f379..d20bc08576cd 100644 --- a/src/overmap_connection.h +++ b/src/overmap_connection.h @@ -14,6 +14,17 @@ class JsonObject; class JsonIn; struct overmap_location; +enum class overmap_connection_layout { + city, + p2p, + last +}; + +template<> +struct enum_traits { + static constexpr overmap_connection_layout last = overmap_connection_layout::last; +}; + class overmap_connection { public: @@ -51,6 +62,10 @@ class overmap_connection bool can_start_at( const oter_id &ground ) const; bool has( const oter_id &oter ) const; + const overmap_connection_layout &get_layout() const { + return layout; + } + void load( const JsonObject &jo, const std::string &src ); void check() const; void finalize(); @@ -70,6 +85,7 @@ class overmap_connection } }; + overmap_connection_layout layout; std::list subtypes; mutable std::vector cached_subtypes; }; diff --git a/src/overmap_special.h b/src/overmap_special.h index 7a0b69b2d8af..8c02f5f971de 100644 --- a/src/overmap_special.h +++ b/src/overmap_special.h @@ -12,6 +12,8 @@ #include #include +#include "flat_set.h" +#include "memory_fast.h" #include "omdata.h" #include "point.h" #include "type_id.h" @@ -40,19 +42,36 @@ struct overmap_special_spawns : public overmap_spawns { void deserialize( const JsonObject &jo ); }; -struct overmap_special_terrain { - overmap_special_terrain() = default; +// This is the information needed to know whether you can place a particular +// piece of an overmap_special at a particular location +struct overmap_special_locations { + overmap_special_locations() = default; + overmap_special_locations( const tripoint &p, + const cata::flat_set &l ) + : p( p ) + , locations( l ) + {}; tripoint p; - oter_str_id terrain; - std::set locations; - - void deserialize( const JsonObject &jo ); + cata::flat_set locations; /** * Returns whether this terrain of the special can be placed on the specified terrain. * It's true if oter meets any of locations. */ bool can_be_placed_on( const oter_id &oter ) const; + void deserialize( JsonIn &jsin ); +}; + +struct overmap_special_terrain : overmap_special_locations { + overmap_special_terrain() = default; + overmap_special_terrain( const tripoint &p, const oter_str_id &t, + const cata::flat_set &l ) + : overmap_special_locations{ p, l } + , terrain( t ) + {}; + oter_str_id terrain; + + void deserialize( JsonIn &jsin ); }; struct overmap_special_connection { @@ -63,11 +82,52 @@ struct overmap_special_connection { bool existing = false; void deserialize( const JsonObject &jo ); + void finalize(); }; +struct overmap_special_placement_constraints { + numeric_interval city_size{ 0, INT_MAX }; + numeric_interval city_distance{ 0, INT_MAX }; + numeric_interval occurrences; +}; + +enum class overmap_special_subtype { + fixed, + mutable_, + last +}; + +template<> +struct enum_traits { + static constexpr overmap_special_subtype last = overmap_special_subtype::last; +}; + +struct fixed_overmap_special_data { + std::vector terrains; +}; + +struct mutable_overmap_special_data; + class overmap_special { public: + overmap_special() = default; + overmap_special( const overmap_special_id &i, const overmap_special_terrain &ter ) + : id( i ) + , subtype_( overmap_special_subtype::fixed ) + , fixed_data_{ { overmap_special_terrain{ ter } } } + {}; + overmap_special_subtype get_subtype() const { + return subtype_; + } + + const overmap_special_placement_constraints &get_constraints() const { + return constraints_; + } + bool is_rotatable() const { + return rotatable_; + } + bool can_spawn() const; /** Returns terrain at the given point. */ const overmap_special_terrain &get_terrain_at( const tripoint &p ) const; /** @returns true if this special requires a city */ @@ -75,26 +135,56 @@ class overmap_special /** @returns whether the special at specified tripoint can belong to the specified city. */ bool can_belong_to_city( const tripoint_om_omt &p, const city &cit ) const; - overmap_special_id id; - std::list terrains; - std::vector connections; - - numeric_interval city_size{ 0, INT_MAX }; - numeric_interval city_distance{ 0, INT_MAX }; - numeric_interval occurrences; + const cata::flat_set &get_flags() const { + return flags_; + } + bool has_flag( const std::string &flag ) const { + return flags_.count( flag ); + } + void set_flag( const std::string &flag ) { + flags_.insert( flag ); + } + int longest_side() const; + std::vector all_terrains() const; + std::vector preview_terrains() const; + std::vector required_locations() const; + + const fixed_overmap_special_data &get_fixed_data() const { + assert( subtype_ == overmap_special_subtype::fixed ); + return fixed_data_; + } + const mutable_overmap_special_data &get_mutable_data() const { + assert( subtype_ == overmap_special_subtype::mutable_ ); + return *mutable_data_; + } + const overmap_special_spawns &get_monster_spawns() const { + return monster_spawns_; + } + const std::unordered_map &get_nested_specials() const { + return nested_; + } - bool rotatable = true; - overmap_special_spawns spawns; - std::set flags; + overmap_special_id id; // Used by generic_factory bool was_loaded = false; void load( const JsonObject &jo, const std::string &src ); void finalize(); void check() const; + std::vector connections; private: + overmap_special_subtype subtype_; + overmap_special_placement_constraints constraints_; + fixed_overmap_special_data fixed_data_; + shared_ptr_fast mutable_data_; + + bool rotatable_ = true; + overmap_special_spawns monster_spawns_; + cata::flat_set flags_; + // These locations are the default values if ones are not specified for the individual OMTs. - std::set default_locations; + cata::flat_set default_locations_; + std::unordered_map nested_; }; namespace overmap_specials diff --git a/src/overmap_ui.cpp b/src/overmap_ui.cpp index 6e078c3a12d2..79ef3b069dc3 100644 --- a/src/overmap_ui.cpp +++ b/src/overmap_ui.cpp @@ -18,6 +18,7 @@ #include #include "activity_actor_definitions.h" +#include "all_enum_values.h" #include "avatar.h" #include "basecamp.h" #include "cached_options.h" @@ -875,20 +876,21 @@ static void draw_ascii( const catacurses::window &w, point_rel_omt s_begin; point_rel_omt s_end; if( blink && uistate.place_special ) { - for( const auto &s_ter : uistate.place_special->terrains ) { - if( s_ter.p.z == 0 ) { - // TODO: fix point types - const point_rel_omt rp( om_direction::rotate( s_ter.p.xy(), uistate.omedit_rotation ) ); - const oter_id oter = s_ter.terrain->get_rotated( uistate.omedit_rotation ); + for( const overmap_special_terrain &s_ter : uistate.place_special->preview_terrains() ) { + // Preview should only yield the terrains on the zero z-level + assert( s_ter.p.z == 0 ); + + // TODO: fix point types + const point_rel_omt rp( om_direction::rotate( s_ter.p.xy(), uistate.omedit_rotation ) ); + const oter_id oter = s_ter.terrain->get_rotated( uistate.omedit_rotation ); - special_cache.insert( std::make_pair( - rp, std::make_pair( oter->get_symbol(), oter->get_color() ) ) ); + special_cache.insert( std::make_pair( + rp, std::make_pair( oter->get_symbol(), oter->get_color() ) ) ); - s_begin.x() = std::min( s_begin.x(), rp.x() ); - s_begin.y() = std::min( s_begin.y(), rp.y() ); - s_end.x() = std::max( s_end.x(), rp.x() ); - s_end.y() = std::max( s_end.y(), rp.y() ); - } + s_begin.x() = std::min( s_begin.x(), rp.x() ); + s_begin.y() = std::min( s_begin.y(), rp.y() ); + s_end.x() = std::max( s_end.x(), rp.x() ); + s_end.y() = std::max( s_end.y(), rp.y() ); } } @@ -1364,9 +1366,17 @@ static void draw_om_sidebar( if( data.debug_editor && center_seen ) { const oter_t &oter = overmap_buffer.ter( center ).obj(); - mvwprintz( wbar, point( 1, ++lines ), c_white, _( "oter: %s" ), oter.id.str() ); + mvwprintz( wbar, point( 1, ++lines ), c_white, _( "oter: %s (rot %d)" ), oter.id.str(), + oter.get_rotation() ); mvwprintz( wbar, point( 1, ++lines ), c_white, _( "oter_type: %s" ), oter.get_type_id().str() ); + + for( cube_direction dir : all_enum_values() ) { + if( std::string *join = overmap_buffer.join_used_at( { center, dir } ) ) { + mvwprintz( wbar, point( 1, ++lines ), c_white, _( "join %s: %s" ), + io::enum_to_string( dir ), *join ); + } + } } if( has_target ) { @@ -1745,7 +1755,7 @@ static void place_ter_or_special( const ui_adaptor &om_ui, tripoint_abs_omt &cur } // TODO: Unify these things. const bool can_rotate = terrain ? uistate.place_terrain->is_rotatable() : - uistate.place_special->rotatable; + uistate.place_special->is_rotatable(); uistate.omedit_rotation = om_direction::type::none; // If user chose an already rotated submap, figure out its direction @@ -1782,7 +1792,7 @@ static void place_ter_or_special( const ui_adaptor &om_ui, tripoint_abs_omt &cur mvwprintz( w_editor, point( 1, 8 ), c_red, _( "id will change, but not" ) ); mvwprintz( w_editor, point( 1, 9 ), c_red, _( "their contents." ) ); if( ( terrain && uistate.place_terrain->is_rotatable() ) || - ( !terrain && uistate.place_special->rotatable ) ) { + ( !terrain && uistate.place_special->is_rotatable() ) ) { mvwprintz( w_editor, point( 1, 11 ), c_white, _( "[%s] Rotate" ), ctxt.get_desc( "ROTATE" ) ); } @@ -1806,11 +1816,12 @@ static void place_ter_or_special( const ui_adaptor &om_ui, tripoint_abs_omt &cur overmap_buffer.ter_set( curs, uistate.place_terrain->id.id() ); overmap_buffer.set_seen( curs, true ); } else { - overmap_buffer.place_special( *uistate.place_special, curs, uistate.omedit_rotation, false, true ); - for( const overmap_special_terrain &s_ter : uistate.place_special->terrains ) { - const tripoint_abs_omt pos = - curs + om_direction::rotate( s_ter.p, uistate.omedit_rotation ); - overmap_buffer.set_seen( pos, true ); + if( std::optional> used_points = + overmap_buffer.place_special( *uistate.place_special, curs, + uistate.omedit_rotation, false, true ) ) { + for( const tripoint_abs_omt &pos : *used_points ) { + overmap_buffer.set_seen( pos, true ); + } } } break; diff --git a/src/overmapbuffer.cpp b/src/overmapbuffer.cpp index 42a3760c37c0..e47d13471500 100644 --- a/src/overmapbuffer.cpp +++ b/src/overmapbuffer.cpp @@ -672,6 +672,12 @@ void overmapbuffer::ter_set( const tripoint_abs_omt &p, const oter_id &id ) return om_loc.om->ter_set( om_loc.local, id ); } +std::string *overmapbuffer::join_used_at( const std::pair &p ) +{ + const overmap_with_local_coords om_loc = get_om_global( p.first ); + return om_loc.om->join_used_at( { om_loc.local, p.second } ); +} + bool overmapbuffer::reveal( const point_abs_omt ¢er, int radius, int z ) { return reveal( tripoint_abs_omt( center, z ), radius ); @@ -1561,17 +1567,15 @@ bool overmapbuffer::is_safe( const tripoint_abs_omt &p ) return true; } -bool overmapbuffer::place_special( - const overmap_special &special, const tripoint_abs_omt &p, om_direction::type dir, - const bool must_be_unexplored, const bool force ) +std::optional> overmapbuffer::place_special( + const overmap_special &special, const tripoint_abs_omt &origin, om_direction::type dir, + const bool must_be_unexplored, const bool force ) { - const overmap_with_local_coords om_loc = get_om_global( p ); + const overmap_with_local_coords om_loc = get_om_global( origin ); - bool placed = false; // Only place this special if we can actually place it per its criteria, or we're forcing // the placement, which is mostly a debug behavior, since a forced placement may not function // correctly (e.g. won't check correct underlying terrain). - if( om_loc.om->can_place_special( special, om_loc.local, dir, must_be_unexplored ) || force ) { // Get the closest city that is within the overmap because @@ -1579,11 +1583,14 @@ bool overmapbuffer::place_special( // the single overmap. If future generation is hoisted up to the // buffer to spawn overmaps, then this can also be changed accordingly. const city c = om_loc.om->get_nearest_city( om_loc.local ); - om_loc.om->place_special( special, om_loc.local, dir, c, - must_be_unexplored, force ); - placed = true; + std::vector result; + for( const tripoint_om_omt &p : om_loc.om->place_special( + special, om_loc.local, dir, c, must_be_unexplored, force ) ) { + result.push_back( project_combine( om_loc.om->pos(), p ) ); + } + return result; } - return placed; + return std::nullopt; } bool overmapbuffer::place_special( const overmap_special_id &special_id, @@ -1603,28 +1610,7 @@ bool overmapbuffer::place_special( const overmap_special_id &special_id, return false; } - // Figure out the longest side of the special for purposes of determining our sector size - // when attempting placements. - const auto calculate_longest_side = [&special]() { - auto min_max_x = std::minmax_element( special.terrains.begin(), - special.terrains.end(), []( const overmap_special_terrain & lhs, - const overmap_special_terrain & rhs ) { - return lhs.p.x < rhs.p.x; - } ); - - auto min_max_y = std::minmax_element( special.terrains.begin(), - special.terrains.end(), []( const overmap_special_terrain & lhs, - const overmap_special_terrain & rhs ) { - return lhs.p.y < rhs.p.y; - } ); - - const int special_longest_side = std::max( min_max_x.second->p.x - min_max_x.first->p.x, - min_max_y.second->p.y - min_max_y.first->p.y ) + 1; - - return special_longest_side; - }; - - const int longest_side = calculate_longest_side(); + const int longest_side = special.longest_side(); // Get all of the overmaps within the defined radius of the center. for( const auto &om : get_overmaps_near( @@ -1637,7 +1623,8 @@ bool overmapbuffer::place_special( const overmap_special_id &special_id, // then result in us placing the special but then not finding it later if we // search using the same radius value we used in placing it. std::vector points_in_range; - for( const tripoint_abs_omt &p : points_in_radius( center, radius - longest_side ) ) { + for( const tripoint_abs_omt &p : points_in_radius( center, std::max( 1, + radius - longest_side ) ) ) { point_abs_om overmap; tripoint_om_omt omt_within_overmap; std::tie( overmap, omt_within_overmap ) = project_remain( p ); @@ -1646,12 +1633,9 @@ bool overmapbuffer::place_special( const overmap_special_id &special_id, } } - // First points will have a priority, let's randomize placement - std::shuffle( points_in_range.begin(), points_in_range.end(), rng_get_engine() ); - // Attempt to place the specials using filtered points. We // require they be placed in unexplored terrain right now. - if( om->place_special_attempt( special, 1, points_in_range, true ) > 0 ) { + if( om->place_special_custom( special, points_in_range ) > 0 ) { return true; } } diff --git a/src/overmapbuffer.h b/src/overmapbuffer.h index 98c7a43ec1fb..e1f75dd1fb15 100644 --- a/src/overmapbuffer.h +++ b/src/overmapbuffer.h @@ -22,6 +22,7 @@ class basecamp; class character_id; +enum class cube_direction : int; class map_extra; class monster; class npc; @@ -189,6 +190,7 @@ class overmapbuffer */ const oter_id &ter_existing( const tripoint_abs_omt &p ); void ter_set( const tripoint_abs_omt &p, const oter_id &id ); + std::string *join_used_at( const std::pair & ); /** * Uses global overmap terrain coordinates. */ @@ -502,11 +504,11 @@ class overmapbuffer * @param must_be_unexplored If true, will require that all of the terrains where the special would be * placed are unexplored. * @param force If true, placement will bypass the checks for valid placement. - * @returns True if the special was placed, else false. + * @returns If the special was placed, a vector of the points used, else nullopt. */ - bool place_special( const overmap_special &special, const tripoint_abs_omt &p, - om_direction::type dir, - bool must_be_unexplored, bool force ); + std::optional> place_special( + const overmap_special &special, const tripoint_abs_omt &origin, + om_direction::type dir, bool must_be_unexplored, bool force ); /** * Place the specified overmap special using the overmap's placement algorithm. Intended to be used * when you have a special that you want placed but it should be placed similarly to as if it were diff --git a/src/pickup.cpp b/src/pickup.cpp index c7d9203e4bb3..0dbb34579c69 100644 --- a/src/pickup.cpp +++ b/src/pickup.cpp @@ -639,7 +639,7 @@ void pickup::pick_up( const tripoint &p, int min, from_where get_items_from ) direction adjacentDir[8] = {direction::NORTH, direction::NORTHEAST, direction::EAST, direction::SOUTHEAST, direction::SOUTH, direction::SOUTHWEST, direction::WEST, direction::NORTHWEST}; for( auto &elem : adjacentDir ) { - tripoint apos = tripoint( direction_XY( elem ), 0 ); + tripoint apos = tripoint( displace_XY( elem ), 0 ); apos += p; pick_up( apos, min ); diff --git a/src/point.h b/src/point.h index c8c4fefdf33b..baf5b43d9b53 100644 --- a/src/point.h +++ b/src/point.h @@ -207,6 +207,13 @@ struct tripoint { return point( x, y ); } + /** + * Rotates just the x,y component of the tripoint. See point::rotate() + * NOLINTNEXTLINE(cata-use-named-point-constants) */ + tripoint rotate( int turns, const point &dim = { 1, 1 } ) const { + return tripoint( xy().rotate( turns, dim ), z ); + } + std::string to_string() const; /** diff --git a/src/savegame.cpp b/src/savegame.cpp index a961bcf3d223..036e24908a74 100644 --- a/src/savegame.cpp +++ b/src/savegame.cpp @@ -388,7 +388,9 @@ void overmap::convert_terrain( old == "fema_2_1" || old == "fema_2_2" || old == "fema_2_3" || old == "fema_3_1" || old == "fema_3_2" || old == "fema_3_3" || old == "mine_entrance" || old == "underground_sub_station" || - old == "sewer_sub_station" ) { + old == "sewer_sub_station" || old == "anthill" || old == "acid_anthill" || + old == "ants_larvae" || old == "ants_larvae_acid" || old == "ants_queen" || + old == "ants_queen_acid" || old == "ants_food" ) { ter_set( pos, oter_id( old + "_north" ) ); } else if( old.compare( 0, 10, "mass_grave" ) == 0 ) { ter_set( pos, oter_id( "field" ) ); @@ -520,26 +522,6 @@ void overmap::unserialize( std::istream &fin, const std::string &file_path ) } cities.push_back( new_city ); } - } else if( name == "labs" ) { - jsin.start_array(); - while( !jsin.end_array() ) { - jsin.start_object(); - lab new_lab; - while( !jsin.end_object() ) { - std::string lab_member_name = jsin.get_member_name(); - if( lab_member_name == "type" ) { - std::string string_type; - jsin.read( string_type ); - new_lab.type = io::string_to_enum( string_type ); - } else if( lab_member_name == "tiles" ) { - jsin.read( new_lab.tiles ); - } else if( lab_member_name == "finales" ) { - jsin.read( new_lab.finales ); - } - } - - labs.push_back( new_lab ); - } } else if( name == "connections_out" ) { jsin.read( connections_out ); } else if( name == "electric_grid_connections" ) { @@ -690,6 +672,14 @@ void overmap::unserialize( std::istream &fin, const std::string &file_path ) } } } + } else if( name == "joins_used" ) { + std::vector> flat_index; + jsin.read( flat_index, true ); + for( const std::pair &p : flat_index ) { + joins_used.insert( p ); + } + } else { + jsin.skip_value(); } } } @@ -989,18 +979,6 @@ void overmap::serialize( std::ostream &fout ) const json.end_array(); fout << std::endl; - json.member( "labs" ); - json.start_array(); - for( auto &l : labs ) { - json.start_object(); - json.member_as_string( "type", l.type ); - json.member( "tiles", l.tiles ); - json.member( "finales", l.finales ); - json.end_object(); - } - json.end_array(); - fout << std::endl; - json.member( "connections_out", connections_out ); fout << std::endl; @@ -1117,6 +1095,11 @@ void overmap::serialize( std::ostream &fout ) const } json.end_array(); + std::vector> flattened_joins_used( + joins_used.begin(), joins_used.end() ); + json.member( "joins_used", flattened_joins_used ); + fout << std::endl; + json.end_object(); fout << std::endl; } diff --git a/src/sdltiles.cpp b/src/sdltiles.cpp index 57d69d4a2da9..3ebb0b1413fc 100644 --- a/src/sdltiles.cpp +++ b/src/sdltiles.cpp @@ -1012,7 +1012,7 @@ void cata_tiles::draw_om( point dest, const tripoint_abs_omt ¢er_abs_omt, bo lit_level::LOW, true, 0 ); } if( uistate.place_special ) { - for( const overmap_special_terrain &s_ter : uistate.place_special->terrains ) { + for( const overmap_special_terrain &s_ter : uistate.place_special->preview_terrains() ) { if( s_ter.p.z == 0 ) { // TODO: fix point types const point_rel_omt rp( om_direction::rotate( s_ter.p.xy(), uistate.omedit_rotation ) ); diff --git a/src/sets_intersect.h b/src/sets_intersect.h new file mode 100644 index 000000000000..d453c03638c9 --- /dev/null +++ b/src/sets_intersect.h @@ -0,0 +1,29 @@ +#ifndef CATA_SRC_SETS_INTERSECT_H +#define CATA_SRC_SETS_INTERSECT_H + +namespace cata +{ + +template +bool sets_intersect( const SetL &l, const SetR &r ) +{ + // There are two reasonable implementation strategies for ordered sets, but + // only one for unordered sets. We use the approach that works for both, + // but if both sets were ordered you can instead step though them in + // parallel looking for a dupe. + if( l.size() > r.size() ) { + return sets_intersect( r, l ); + } + + for( const auto &elem : l ) { + if( r.count( elem ) ) { + return true; + } + } + + return false; +} + +} // namespace cata + +#endif // CATA_SRC_SETS_INTERSECT_H \ No newline at end of file diff --git a/src/start_location.cpp b/src/start_location.cpp index 3bfdf5066039..eb6a369849b9 100644 --- a/src/start_location.cpp +++ b/src/start_location.cpp @@ -231,24 +231,26 @@ tripoint_abs_omt start_location::find_player_initial_location() const // Look for special having that terrain for( const auto &special : overmap_specials::get_all() ) { - if( std::none_of( special.terrains.begin(), - special.terrains.end(), [&loc]( const overmap_special_terrain & t ) { - return is_ot_match( loc.first, t.terrain, loc.second ); + const auto &terrains = special.all_terrains(); + if( std::none_of( terrains.begin(), terrains.end(), + [&loc]( const oter_str_id & t ) { + return is_ot_match( loc.first, t, loc.second ); } ) ) { continue; } // Look for place where it can be spawned - for( const point_abs_om &omp : overmaps ) { - const tripoint_abs_omt abs_mid = project_combine( omp, om_mid ); - if( overmap_buffer.place_special( special.id, abs_mid, OMAPX / 2 ) ) { - - // Now try to find what we spawned - const tripoint_abs_omt start = overmap_buffer.find_closest( abs_mid, loc.first, OMAPX / 2, false, - loc.second ); - if( start != overmap::invalid_tripoint ) { - return start; - } + // If there's not a single matching spot in whole overmap - most likely + // that special is bad, no need to check all other overmaps for same thing + const point_abs_om &omp = random_entry( overmaps ); + const tripoint_abs_omt abs_mid = project_combine( omp, om_mid ); + if( overmap_buffer.place_special( special.id, abs_mid, OMAPX / 2 ) ) { + + // Now try to find what we spawned + const tripoint_abs_omt start = overmap_buffer.find_closest( abs_mid, loc.first, + OMAPX / 2, false, loc.second ); + if( start != overmap::invalid_tripoint ) { + return start; } } } diff --git a/tests/cube_direction_test.cpp b/tests/cube_direction_test.cpp new file mode 100644 index 000000000000..2d33f6d54c73 --- /dev/null +++ b/tests/cube_direction_test.cpp @@ -0,0 +1,39 @@ +#include "catch/catch.hpp" + +#include "cube_direction.h" +#include "omdata.h" + +TEST_CASE( "cube_direction_add_om_direction", "[cube_direction]" ) +{ + CHECK( cube_direction::north + om_direction::type::north == cube_direction::north ); + CHECK( cube_direction::north + om_direction::type::east == cube_direction::east ); + CHECK( cube_direction::north + om_direction::type::south == cube_direction::south ); + CHECK( cube_direction::north + om_direction::type::west == cube_direction::west ); + CHECK( cube_direction::east + om_direction::type::south == cube_direction::west ); + CHECK( cube_direction::south + om_direction::type::south == cube_direction::north ); + CHECK( cube_direction::west + om_direction::type::south == cube_direction::east ); +} + +TEST_CASE( "cube_direction_add_int", "[cube_direction]" ) +{ + CHECK( cube_direction::north + 0 == cube_direction::north ); + CHECK( cube_direction::north + 1 == cube_direction::east ); + CHECK( cube_direction::north + 2 == cube_direction::south ); + CHECK( cube_direction::north + 3 == cube_direction::west ); + CHECK( cube_direction::north + 4 == cube_direction::north ); + CHECK( cube_direction::east + 2 == cube_direction::west ); + CHECK( cube_direction::south + 2 == cube_direction::north ); + CHECK( cube_direction::west + 2 == cube_direction::east ); +} + +TEST_CASE( "cube_direction_subtract_int", "[cube_direction]" ) +{ + CHECK( cube_direction::north - 0 == cube_direction::north ); + CHECK( cube_direction::north - 1 == cube_direction::west ); + CHECK( cube_direction::north - 2 == cube_direction::south ); + CHECK( cube_direction::north - 3 == cube_direction::east ); + CHECK( cube_direction::north - 4 == cube_direction::north ); + CHECK( cube_direction::east - 2 == cube_direction::west ); + CHECK( cube_direction::south - 2 == cube_direction::north ); + CHECK( cube_direction::west - 2 == cube_direction::east ); +} diff --git a/tests/distribution_test.cpp b/tests/distribution_test.cpp new file mode 100644 index 000000000000..c68acbe41598 --- /dev/null +++ b/tests/distribution_test.cpp @@ -0,0 +1,18 @@ +#include "catch/catch.hpp" + +#include + +#include "distribution.h" +#include "json.h" + +TEST_CASE( "poisson_distribution", "[distribution]" ) +{ + std::string s = R"({ "poisson": 10 })"; + std::istringstream is( s ); + JsonIn jin( is ); + int_distribution d; + d.deserialize( jin ); + + CHECK( d.description() == "Poisson(10)" ); + CHECK( d.minimum() == 0 ); +} diff --git a/tests/overmap_test.cpp b/tests/overmap_test.cpp index 9f0350a6d183..9a95e9d41dbd 100644 --- a/tests/overmap_test.cpp +++ b/tests/overmap_test.cpp @@ -14,10 +14,11 @@ #include "overmap_types.h" #include "overmapbuffer.h" #include "point.h" +#include "rng.h" #include "state_helpers.h" #include "type_id.h" -TEST_CASE( "set_and_get_overmap_scents" ) +TEST_CASE( "set_and_get_overmap_scents", "[overmap]" ) { clear_all_state(); std::unique_ptr test_overmap = std::make_unique( point_abs_om() ); @@ -38,7 +39,7 @@ TEST_CASE( "set_and_get_overmap_scents" ) REQUIRE( test_overmap->scent_at( { 75, 85, 0} ).initial_strength == 90 ); } -TEST_CASE( "default_overmap_generation_always_succeeds", "[slow]" ) +TEST_CASE( "default_overmap_generation_always_succeeds", "[overmap][slow]" ) { clear_all_state(); int overmaps_to_construct = 10; @@ -51,12 +52,13 @@ TEST_CASE( "default_overmap_generation_always_succeeds", "[slow]" ) overmap_buffer.create_custom_overmap( candidate_addr, test_specials ); for( const auto &special_placement : test_specials ) { auto special = special_placement.special_details; - if( special->flags.count( "UNIQUE" ) > 0 ) { + if( special->has_flag( "UNIQUE" ) ) { continue; } INFO( "In attempt #" << overmaps_to_construct << " failed to place " << special->id.str() ); - CHECK( special->occurrences.min <= special_placement.instances_placed ); + int min_occur = special->get_constraints().occurrences.min; + CHECK( min_occur <= special_placement.instances_placed ); } if( --overmaps_to_construct <= 0 ) { break; @@ -173,3 +175,41 @@ TEST_CASE( "is_ot_match", "[overmap][terrain]" ) CHECK_FALSE( is_ot_match( "forestry", oter_id( "forest" ), ot_match_type::contains ) ); } } + +TEST_CASE( "mutable_overmap_placement", "[overmap][slow]" ) +{ + const overmap_special &special = + *overmap_special_id( GENERATE( "test_anthill", "test_crater" ) ); + const city cit; + + constexpr int num_overmaps = 100; + constexpr int num_trials_per_overmap = 100; + + for( int j = 0; j < num_overmaps; ++j ) { + // overmap objects are really large, so we don't want them on the + // stack. Use unique_ptr and put it on the heap + std::unique_ptr om = std::make_unique( point_abs_om( point_zero ) ); + om_direction::type dir = om_direction::type::north; + + int successes = 0; + + for( int i = 0; i < num_trials_per_overmap; ++i ) { + tripoint_om_omt try_pos( rng( 0, OMAPX - 1 ), rng( 0, OMAPY - 1 ), 0 ); + + // This test can get very spammy, so abort once an error is + // observed + if( debug_has_error_been_observed() ) { + return; + } + + if( om->can_place_special( special, try_pos, dir, false ) ) { + std::vector placed_points = + om->place_special( special, try_pos, dir, cit, false, false ); + CHECK( !placed_points.empty() ); + ++successes; + } + } + + CHECK( successes > num_trials_per_overmap / 2 ); + } +}