From 6ffdc50db8ea9a31f695dfd6f13730a30aa4492f Mon Sep 17 00:00:00 2001 From: Vollch Date: Sat, 2 Dec 2023 13:46:50 +0300 Subject: [PATCH] feat(port)!: parametric mapgen (#3780) * Constify mapgendata A lot of mapgen code passes around references to mapgendata. These were mostly non-const. Make them const where possible to clarify intent. I think they should be const everywhere, but in some places they are genuinely used in a non-const manner. It is unclear how necessary this is. For now I wanted to PR what I could. This is inspired by some new features I'm adding to the mapgen code. * Simplify mapgen logic Previously there were separate dedicated components for terrain and furniture mapgen. But these are also supported in the more general format_placings so it simplifies the code to get rid of the special cases for terrain and furniture. (There's some performance cost here, but I don't think it's significant). * Don't overwrite format_placings from new palette When multiple palettes were listed for a particular map, the later palette entry of the format_placings map would overwrite the earlier one. This had gone unnoticed until recently when furniture and terrain started to be implemented via format_placings, which caused many more entries to exist. Maps using multiple palettes now lacked furniture because the other palette added items. This affected the LMOE shelter, for example. After this chance, the palettes' format_placings entries are concatenated, rather than replaced. This should mean that all the palettes' entries take effect. Tested in the LMOE shelter and it seems to work fine. * Add cata_variant::is_valid When JSON is reading cata_variant values we need to be able to verify their correctness at JSON load time (rather than waiting until the value is used for something). To support this, add an is_valid method to cata_variant. This needs to be implemented for each type supported. For now it's not implemented for all of them, just the ones most likely to suffer from typo issues. * Rewrite is_valid_helper for older compilers Older compilers can't cope with expanding a lambda expression. Tweak implementation accordingly. * Add ter_str_id alternative to cata_variant * Make cata_variant_type_for a public API It is useful in other places in the code to be able to determine which cata_variant_type corresponds to a particular C++ type. Previously the function that did that was an implementation detail of cata_variant. Instead make it public. * Allow making cata_variant from type and string This is slightly unsafe, so it's a static factory function rather than direct access to the constructor. * Tests for new cata_variant features Test cata_variant::from_string and cata_variant_type_for. * Parametric mapgen part 1: global random terrain selection (#48529) * Allow access to JsonArray members as JsonValue * Create load_weighted_list function This is a generic function for loading a weighted_list from json that can be used to reduce code duplication and ensure json consistency. * Remove undefined declaration This function had no definition and no callers. * Implement "parameters" for mapgen This is an initial implementation of parameters for mapgen, which currently has those parameters act more like variables than parameters. Essentially all it currently permits is to choose a terrain at random and use that in a palette. Thus choosing a thing at random which applies to an entire map, rather than choosing at random on a per-tile basis. This is the first step towards true parameters for mapgen; hence the currently-misleading name. * Add docs for new parameters feature. * Minor grammar/style fixes Co-authored-by: actual-nh <74678550+actual-nh@users.noreply.github.com> Co-authored-by: actual-nh <74678550+actual-nh@users.noreply.github.com> * Extend cddatags to support multi-id entries Some json object have a list of ids they are defining, rather than just one. In particular, this is common for overmap_terrain. When that happens, index all of them. * Mapgen params to map special scope and palettes Previously mapgen parameters only applied to a single OMT, and could not be in palettes. Remove both those limitations. Now a parameter can have map_special scope (indeed, that's the default) so that the same value is chosen consistently across any specific overmap_special. Moreover, parameters can be placed in palettes, and are automatically extracted from all the palettes relevant to a particular piece of mapgen. * Randomize house interior wall colours Demonstrate the new mapgen parameter support by randomizing the colour of walls in standard_domestic_palette. Make white about as common as all the other colours put together. * Document the new mapgen parameter features * Apply suggestions from code review Three typos, and one arbitrary constant changed Co-authored-by: actual-nh <74678550+actual-nh@users.noreply.github.com> * Fix a couple of serialization bugs Two issues with serialization: - If the entire stream was just a single integer then it would cause an infinite loop when reading. - When reading a sequence of optionals, if one optional was empty, it would cause the sequence to grow until memory was exhausted without ever consuming that input. Fix both these issues, and add unit tests. * Serialize mapgen args as stored in the overmap For mapgen to be consistent across reloads, we need to store the arg choices. * Ensure mapgen order is sensible (#50597) When terrain and furniture were merged into the same mapgen system as everything else it changed the order in which mapgen happened. In particular, some terrain was now placed after nested mapgen, which caused the terrain from the nest to be overridden. Add the ability to define an ordering in which the jmapgen_pieces should be applied, and ensure that terrain and furniture go first. This should more closely match what happened before the changes in #48498. In particular, this fixes #49788, the issue with missing walls in basements. * Vector can invalidate pointer during resize, store index instead * weighted_list::to_debug_string Allow dumping a weighted list to a string for debug purposes. * Overwrite mapgen args when placing special Previously when placing an overmap special on top of an existing overmap special (e.g. via the debug menu) the parameters for the old one would persist and the new one would lack its parameters. Instead, overwrite the old with the new. * Support mapgen parameters in nested mapgen We want mapgen parameters to work inside nested mapgen, at all scopes. To make this work we lift all parameters from nests to overmap terrains before lifting again from overmap terrains to overmap specials. At mapgen time, the arguments chosen for these parameters cascade down in the opposite direction. * Pass nest-scoped arguments to nest mapgen A simple bugfix. Previously we were generating these arguments but not passing them. * Better debug output for inconsistent distributions State the two distributions so that it is easier to figure out where each is coming from and why they are conflicting. * Randomize nested carpets in house_w_palette Use the new functionality of mapgen parameters in nests to choose a random colour of carpet for each carpet used in a nested mapgen using house_w_nest_palette. Previously there was a separate symbol for each carpet colour but only 6 (red carpet) was ever used. Remove the unused symbols and repurpose 6 to mean "random carpet". Make the parameter of nest scope so each nest can choose a carpet colour independently. * Document nest scoped parameters * Fixed invalid id * Validate item group ids in mapgen (#50684) Placing item groups using the "items" key was not validating that the given ids were valid until they came to be used. Validate them at data validation time. Fix the issues thereby detected. * Removed ter_str_id << redefinition in tests * Add furn_id, furn_str_id to cata_variant * Add a bunch of types to cata_variant We need lots more types so that they can be used as mapgen_value types for parametric mapgen. * Migrate most mapgen constants to mapgen_value Previously, parametric mapgen could only be used for terrain. Extend that to all other json mapgen pieces for which is is straightforward to do so. A few of trickier cases remain without support: * jmapgen_gaspump's fuel type * jmapgen_item_group's item group * jmapgen_loot's result_group * jmapgen_monster's monster ids * jmapgen_sealed_item's furniture id Postponing those to the future for now. * Migrate some zone definitions to JSON For these zone types' ids to be deemed valid we must have JSON definitions of them so that they are defined within the zone_factory. * Check mapgen parameter definitions There was previously no way to detect an invalid id in a mapgen parameter definition. Check for those. * Make palette_id a string_id Previously palette_id was simply a string. Make it instead a string_id. * Add palette_id to cata_variant * Make mapgen_palette::parameters private * Add recursive palette support Now palettes can use other palettes. This can reduce repetitiveness in mapgen somewhat, but should be more useful in the future with the addition of variant palette support. * Check for loops in palette references If we had a loop of palettes, all referencing the next, that would lead to a stack overflow. Detect that case, report an error, and avoid crashing. * Show overmap_special args in overmap editor To assist with debugging, it's helpful to be able to see the chosen values for any given overmap_special in the overmap editor. * Add support for weighted lists of palettes Allow a reference to a palette in mapgen to be a mapgen_value. That means it can be a distribution. That mapgen value is injected as the default value of a new (internal) mapgen_parameter, and the value of that parameter dictates the palette used. When the palettes are added to the chunk of mapgen, every possible palette's pieces of mapgen are added, but those for which the palette might not be chosen are constrained by the mapgen value using a new jmapgen_constrained feature. * Use new weighted palette feature for cabin Create a new cabin_abandoned palette and change one of the cabin variants to use it half the time using the new palette choice feature. The abandoned cabin has mostly trash as items, is overgrown, and might have broken doors, windows, etc. * Document mapgen palettes and how to use them I wrote this documentation to explain how to use the new feature of choosing from a weighted list of palettes. However, it turns out that palettes themselves had never been documented (at least, not in MAPGEN.md), so I had to document them first. Also added a note about using the overmap editor to see mapgen arguments. * Add missing overrides to jmapgen_alternatively This wrapper mapgen helper wasn't forwarding most of the overridable functions to its alternatives as it ought to have been. Remedy that. In particular, this fixes the phase() for such mapgen, which means that top-level mapgen can override palette mapgen as it is supposed to. * Convert mapgen phases to enum Previously we were using an int for this, but an enum is more self-documenting, and was suggested in the review of #50597. * Debugging option to set mapgen args To aid with testing and debugging mapgen, add a new option to the overmap UI (in debug mode) that allows you to manually set the arguments for an overmap special. Currently, only the overmap special-scoped arguments may be set. * Add support for switch mapgen_value Sometimes you want to choose one value in mapgen based on another. For example, to make a fence gate match its respective fence. This switch form of a mapgen value permits that. To make this work, we had to allow any mapgen argument to be treated as a string even when it was a more specific type (like a terrain id). * Implement matching fences and gates for farm_dairy Previously the dairy farm had to use only splitrail fence. Now it can use one of a number of different fence types, and gates to match. * Document mapgen_value switch feature * Migrate jmapgen_gaspump to mapgen_id This entailed migrating the fuel type from a std::string to an itype_id. * Migrate jmapgen_monster to mapgen_values * Migrate jmapgen_sealed_item to mapgen_value Switch the furniture specified in jmapgen_sealed_item to be a mapgen_value. * Check for member, not string, on monster group The monster group in jmapgen_monster was theoretically supposed to be a mapgen_value but the code only parsed it when it was a string. Fix that so other values can be used. * Fix duplicate terrain/furniture mapgen All terrain and furniture was accidentally being loaded into palettes twice. This is probably the result of a bad merge conflict resolution at some point which duplicated some lines of code. Remove the duplicates. * Support more neighbours in jmapgen_nested (#51804) Previously only the cardinal directions and above were supported; now all eight horizontal neighbours, above, and below are. * Styling * Remove unnecessary plural from ammo satchel to fix tests * Delete unused update mapgen bandits_rv * Remove nonexistent gun_cases items group * size_t doesn't always serialize well, change index to int --------- Co-authored-by: John Bytheway Co-authored-by: John Bytheway <52664+jbytheway@users.noreply.github.com> Co-authored-by: actual-nh <74678550+actual-nh@users.noreply.github.com> --- data/json/loot_zones.json | 30 + .../modular_workshop_metal.json | 2 +- data/json/mapgen/cabin.json | 2 +- data/json/mapgen/farm_dairy.json | 12 +- data/json/mapgen/house/crack_house.json | 9 +- data/json/mapgen/mapgen-test.json | 7 +- data/json/mapgen_palettes/cabin.json | 95 +- data/json/mapgen_palettes/farm_dairy.json | 28 + .../house_general_palette.json | 18 +- .../json/mapgen_palettes/house_w_palette.json | 14 +- data/mods/No_Hope/Mapgen/mall_ground.json | 6 +- .../Mapgen/map_extras/mapgen_updates.json | 6 - data/raw/keybindings/keybindings.json | 7 + .../docs/en/mod/json/reference/map/mapgen.md | 142 +- src/cata_variant.cpp | 43 + src/cata_variant.h | 119 +- src/clzones.cpp | 16 - src/enum_conversions.h | 18 +- src/faction.cpp | 2 +- src/game.cpp | 3 + src/init.cpp | 1 + src/json.cpp | 60 +- src/json.h | 4 +- src/line.cpp | 54 + src/line.h | 18 + src/magic_ter_fur_transform.cpp | 9 +- src/map.h | 12 +- src/mapgen.cpp | 1944 ++++++++++++----- src/mapgen.h | 140 +- src/mapgen_functions.h | 2 + src/mapgen_parameter.h | 71 + src/mapgendata.cpp | 78 +- src/mapgendata.h | 59 +- src/omdata.h | 2 + src/overmap.cpp | 48 + src/overmap.h | 8 + src/overmap_special.h | 8 + src/overmap_ui.cpp | 55 + src/overmapbuffer.cpp | 13 + src/overmapbuffer.h | 3 + src/savegame.cpp | 19 + src/string_id.h | 5 + src/string_id_null_ids.cpp | 4 + src/to_string_id.h | 24 + src/type_id.h | 6 + src/weighted_list.h | 42 +- tests/cata_variant_test.cpp | 32 + tests/json_test.cpp | 20 + tests/map_test.cpp | 6 - tools/json_tools/cddatags.py | 54 +- 50 files changed, 2705 insertions(+), 675 deletions(-) create mode 100644 data/json/mapgen_palettes/farm_dairy.json create mode 100644 src/mapgen_parameter.h create mode 100644 src/to_string_id.h diff --git a/data/json/loot_zones.json b/data/json/loot_zones.json index 113b7e86d76a..b2ddaccb9811 100644 --- a/data/json/loot_zones.json +++ b/data/json/loot_zones.json @@ -196,5 +196,35 @@ "type": "LOOT_ZONE", "name": "Loot: Ignore Favorites", "description": "Favorite items inside of this zone are ignored by \"sort out loot\" zone-action." + }, + { + "id": "NO_AUTO_PICKUP", + "type": "LOOT_ZONE", + "name": "No Auto Pickup", + "description": "You won't auto-pickup items inside the zone." + }, + { + "id": "NO_NPC_PICKUP", + "type": "LOOT_ZONE", + "name": "No NPC Pickup", + "description": "Friendly NPCs don't pickup items inside the zone." + }, + { + "id": "NPC_RETREAT", + "type": "LOOT_ZONE", + "name": "NPC Retreat", + "description": "When fleeing, friendly NPCs will attempt to retreat toward this zone if it is within 60 tiles." + }, + { + "id": "NPC_NO_INVESTIGATE", + "type": "LOOT_ZONE", + "name": "NPC Ignore Sounds", + "description": "Friendly NPCs won't investigate unseen sounds coming from this zone." + }, + { + "id": "NPC_INVESTIGATE_ONLY", + "type": "LOOT_ZONE", + "name": "NPC Investigation Area", + "description": "Friendly NPCs will investigate unseen sounds only if they come from inside this area." } ] diff --git a/data/json/mapgen/basecamps/modular_workshop/modular_workshop_metal.json b/data/json/mapgen/basecamps/modular_workshop/modular_workshop_metal.json index 6e6e1c074969..8e4d6c892881 100644 --- a/data/json/mapgen/basecamps/modular_workshop/modular_workshop_metal.json +++ b/data/json/mapgen/basecamps/modular_workshop/modular_workshop_metal.json @@ -65,7 +65,7 @@ "update_mapgen_id": "fbmw_room2_metal_northeast", "method": "json", "object": { - "place_nested": [ { "chunks": [ "fbmw_room2_metal" ], "x": 15, "y": 3 } ], + "place_nested": [ { "chunks": [ "fbmw_room2_common" ], "x": 15, "y": 3 } ], "place_loot": [ { "item": "55gal_drum", "x": 17, "y": 7, "chance": 100 }, { "item": "30gal_drum", "x": 17, "y": 8, "chance": 100 } ] } }, diff --git a/data/json/mapgen/cabin.json b/data/json/mapgen/cabin.json index 9ec7a48f242f..fc5d55c2dede 100644 --- a/data/json/mapgen/cabin.json +++ b/data/json/mapgen/cabin.json @@ -32,7 +32,7 @@ "-********..~~..********-", "-----------GG-----------" ], - "palettes": [ "cabin_palette" ], + "palettes": [ { "distribution": [ [ "cabin_palette", 1 ], [ "cabin_palette_abandoned", 1 ] ] } ], "place_monsters": [ { "monster": "GROUP_ZOMBIE", "x": 7, "y": 4 } ] } }, diff --git a/data/json/mapgen/farm_dairy.json b/data/json/mapgen/farm_dairy.json index 05cda2dccc81..333c5c177a14 100644 --- a/data/json/mapgen/farm_dairy.json +++ b/data/json/mapgen/farm_dairy.json @@ -32,7 +32,7 @@ ".$............................................$.", ".$............................................$." ], - "terrain": { "$": "t_splitrail_fence", ".": [ "t_grass", "t_grass", "t_dirt", "t_dirt", "t_dirt" ] }, + "palettes": [ "farm_dairy_palette" ], "place_monsters": [ { "monster": "GROUP_COWS", "x": [ 1, 10 ], "y": [ 1, 9 ], "density": 0.5 }, { "monster": "GROUP_COWS", "x": [ 1, 12 ], "y": [ 1, 12 ], "density": 0.5 }, @@ -80,11 +80,7 @@ ",$............................................$,", ",$............................................$," ], - "terrain": { - "$": "t_splitrail_fence", - ".": [ "t_grass", "t_grass", "t_dirt", "t_dirt", "t_dirt" ], - ",": [ "t_grass", "t_grass", "t_dirt", "t_dirt", "t_dirt" ] - } + "palettes": [ "farm_dairy_palette" ] } }, { @@ -120,15 +116,14 @@ ".$$$$$$$$$$$$$$$$$$fffff$$$$$$$$$$$$$$$$$$$$$$$.", "................................................" ], + "palettes": [ "farm_dairy_palette" ], "terrain": { - "$": "t_splitrail_fence", "'": "t_door_o", "%": "t_milking_machine", "!": "t_window_bars", "*": "t_window_domestic", "=": "t_door_o", "+": "t_door_locked_interior", - ".": [ "t_grass", "t_grass", "t_dirt", "t_dirt", "t_dirt" ], "N": [ "t_grass", "t_grass", "t_dirt", "t_dirt", "t_dirt" ], "X": [ "t_grass", "t_grass", "t_dirt", "t_dirt", "t_dirt" ], "O": "t_bulk_tank", @@ -136,7 +131,6 @@ "Y": "t_floor", "_": "t_floor", "a": [ "t_grass", "t_grass", "t_dirt", "t_dirt", "t_dirt" ], - "f": "t_splitrail_fencegate_c", "|": "t_wall_w", "4": "t_gutter_downspout" }, diff --git a/data/json/mapgen/house/crack_house.json b/data/json/mapgen/house/crack_house.json index e68ebdc45a20..eb00eac28e65 100644 --- a/data/json/mapgen/house/crack_house.json +++ b/data/json/mapgen/house/crack_house.json @@ -106,8 +106,15 @@ " ", " " ], + "parameters": { + "roof_type": { + "type": "ter_str_id", + "scope": "omt", + "default": { "distribution": [ [ "t_flat_roof", 2 ], [ "t_tar_flat_roof", 1 ], [ "t_shingle_flat_roof", 1 ] ] } + } + }, "palettes": [ "roof_palette" ], - "terrain": { ".": "t_shingle_flat_roof" } + "terrain": { ".": { "param": "roof_type" } } } }, { diff --git a/data/json/mapgen/mapgen-test.json b/data/json/mapgen/mapgen-test.json index b377961e0fe8..4429ee1212db 100644 --- a/data/json/mapgen/mapgen-test.json +++ b/data/json/mapgen/mapgen-test.json @@ -91,11 +91,8 @@ "toilets": { "T": { "amount": [ 44, 46 ] } }, "gaspumps": { "O": { "amount": [ 110, 112 ] } }, "place_gaspumps": [ { "x": 1, "y": 1, "amount": [ 200, 222 ] } ], - "items": { "o": { "item": "clothing_work_set", "chance": 20 }, "s": { "item": "bionics_tech", "chance": 70 } }, - "place_items": [ - { "item": "bionics_tech", "x": 1, "y": 3, "chance": 70 }, - { "item": "clothing_work_set", "x": 2, "y": 3, "chance": 20 } - ], + "items": { "o": { "item": "clothing_work_set", "chance": 20 }, "s": { "item": "bionics", "chance": 70 } }, + "place_items": [ { "item": "bionics", "x": 1, "y": 3, "chance": 70 }, { "item": "clothing_work_set", "x": 2, "y": 3, "chance": 20 } ], "place_item": [ { "item": "frame", "x": [ 0, 7 ], "y": [ 4, 5 ], "chance": 4, "repeat": 10 } ], "item": { "t": { "item": "television", "chance": 4 }, diff --git a/data/json/mapgen_palettes/cabin.json b/data/json/mapgen_palettes/cabin.json index a796dda59418..528b4b746e9d 100644 --- a/data/json/mapgen_palettes/cabin.json +++ b/data/json/mapgen_palettes/cabin.json @@ -1,23 +1,15 @@ [ { "type": "palette", - "id": "cabin_palette", + "id": "cabin_palette_common", "terrain": { "#": "t_wall_log", - "+": [ [ "t_door_c", 3 ], "t_door_locked" ], - "=": "t_door_c", - "-": "t_fence", - ".": [ [ "t_region_groundcover", 4 ], "t_region_groundcover_forest" ], - "~": [ [ "t_region_groundcover_barren", 3 ], "t_region_groundcover" ], - "*": [ [ "t_region_groundcover", 15 ], "t_region_shrub" ], "G": "t_fencegate_c", "o": "t_column", "P": "t_water_pump", "R": "t_floor", "r": "t_rock_floor", "S": "t_rock_floor", - "w": "t_window_domestic", - "W": [ [ "t_window_boarded", 2 ], "t_window" ], "%": "t_gutter_downspout", "q": "t_swater_sh", "Q": "t_water_dp", @@ -27,36 +19,99 @@ "furniture": { "a": "f_armchair", "A": "f_bookcase", - "b": "f_bench", - "B": "f_bed", "C": "f_counter", - "c": "f_chair", - "d": "f_desk", - "D": "f_dresser", "F": "f_fridge", "h": "f_bathtub", - "l": "f_stool", "L": "f_locker", - "O": "f_sofa", "R": [ "f_rack", "f_utility_shelf" ], "S": "f_woodstove", "s": "f_sink", - "t": "f_table", "T": "f_toilet", "U": "f_utility_shelf", "V": "f_oven", "u": "f_cupboard", - "Y": "f_rack_coat", "z": "f_brazier", "Z": "f_fireplace", - "y": [ "f_indoor_plant_y", "f_indoor_plant" ], "1": "f_cupboard", "2": "f_cupboard", "3": "f_cupboard", "4": "f_cupboard", "5": "f_sink", - "&": "f_trashcan", "0": "f_standing_tank" + } + }, + { + "type": "palette", + "id": "cabin_palette_abandoned", + "palettes": [ "cabin_palette_common" ], + "parameters": { + "cabin_window_type": { + "type": "ter_str_id", + "default": { "distribution": [ [ "t_window_boarded", 3 ], [ "t_window_domestic", 1 ], [ "t_window_no_curtains", 2 ] ] } + }, + "table_or_not": { "type": "furn_str_id", "default": { "distribution": [ [ "f_table", 1 ], [ "f_null", 2 ] ] } }, + "sofa_or_not": { "type": "furn_str_id", "default": { "distribution": [ [ "f_sofa", 1 ], [ "f_null", 4 ] ] } }, + "bed_or_not": { "type": "furn_str_id", "default": { "distribution": [ [ "f_bed", 1 ], [ "f_null", 2 ] ] } } + }, + "terrain": { + ".": [ [ "t_region_groundcover", 4 ], [ "t_region_groundcover_forest", 3 ], "t_region_shrub" ], + "~": [ [ "t_region_groundcover_barren", 3 ], [ "t_region_groundcover", 3 ], "t_region_shrub" ], + "*": [ "t_region_groundcover", "t_region_shrub" ], + "-": [ [ "t_fence", 15 ], "t_region_groundcover" ], + "w": [ [ { "param": "cabin_window_type", "fallback": "t_window_boarded" }, 6 ], "t_window_frame" ], + "W": [ [ "t_window_boarded", 3 ], "t_window", "t_window_frame" ], + "+": [ [ "t_door_c", 1 ], [ "t_door_locked", 3 ], [ "t_door_b", 2 ] ], + "=": [ [ "t_door_c", 1 ], [ "t_door_o", 1 ], [ "t_door_b", 2 ] ] + }, + "furniture": { + "B": { "param": "bed_or_not", "fallback": "f_null" }, + "b": [ [ "f_bench", 1 ], [ "f_null", 5 ] ], + "c": [ [ "f_chair", 1 ], [ "f_null", 5 ] ], + "D": [ [ "f_dresser", 1 ], [ "f_null", 3 ] ], + "d": [ [ "f_desk", 1 ], [ "f_null", 3 ] ], + "l": [ [ "f_stool", 1 ], [ "f_null", 5 ] ], + "t": { "param": "table_or_not", "fallback": "f_null" }, + "O": { "param": "sofa_or_not", "fallback": "f_null" }, + "y": [ "f_indoor_plant_y", "f_indoor_plant" ], + "&": "f_trashcan" + }, + "items": { + " ": { "item": "trash", "chance": 2 }, + ".": { "item": "trash_forest", "chance": 1 }, + "C": { "item": "trash", "chance": 20 }, + "1": { "item": "SUS_junk_drawer", "chance": 20 }, + "2": { "item": "SUS_junk_drawer", "chance": 20 }, + "3": { "item": "SUS_junk_drawer", "chance": 20 }, + "4": { "item": "SUS_junk_drawer", "chance": 20 }, + "5": { "item": "SUS_kitchen_sink", "chance": 10 } + } + }, + { + "type": "palette", + "id": "cabin_palette", + "palettes": [ "cabin_palette_common" ], + "terrain": { + ".": [ [ "t_region_groundcover", 4 ], "t_region_groundcover_forest" ], + "~": [ [ "t_region_groundcover_barren", 3 ], "t_region_groundcover" ], + "*": [ [ "t_region_groundcover", 15 ], "t_region_shrub" ], + "-": "t_fence", + "w": "t_window_domestic", + "W": [ [ "t_window_boarded", 2 ], "t_window" ], + "+": [ [ "t_door_c", 3 ], "t_door_locked" ], + "=": "t_door_c" + }, + "furniture": { + "B": "f_bed", + "b": "f_bench", + "c": "f_chair", + "D": "f_dresser", + "d": "f_desk", + "l": "f_stool", + "t": "f_table", + "O": "f_sofa", + "Y": "f_rack_coat", + "y": [ "f_indoor_plant_y", "f_indoor_plant" ], + "&": "f_trashcan" }, "liquids": { "0": { "liquid": "water_clean", "amount": [ 10, 900 ] } }, "items": { diff --git a/data/json/mapgen_palettes/farm_dairy.json b/data/json/mapgen_palettes/farm_dairy.json new file mode 100644 index 000000000000..b0928f265928 --- /dev/null +++ b/data/json/mapgen_palettes/farm_dairy.json @@ -0,0 +1,28 @@ +[ + { + "type": "palette", + "id": "farm_dairy_palette", + "parameters": { + "fence_type": { + "type": "ter_str_id", + "default": { + "distribution": [ [ "t_splitrail_fence", 3 ], [ "t_chainfence", 2 ], [ "t_fence_barbed", 2 ], [ "t_privacy_fence", 1 ] ] + } + } + }, + "terrain": { + "$": { "param": "fence_type", "fallback": "t_splitrail_fence" }, + "f": { + "switch": { "param": "fence_type", "fallback": "t_splitrail_fence" }, + "cases": { + "t_splitrail_fence": "t_splitrail_fencegate_c", + "t_chainfence": "t_chaingate_c", + "t_fence_barbed": "t_gate_metal_c", + "t_privacy_fence": "t_privacy_fencegate_c" + } + }, + ".": [ [ "t_grass", 2 ], [ "t_dirt", 3 ] ], + ",": [ [ "t_grass", 2 ], [ "t_dirt", 3 ] ] + } + } +] diff --git a/data/json/mapgen_palettes/house_general_palette.json b/data/json/mapgen_palettes/house_general_palette.json index ca9e41ae278a..b2f331652d2f 100644 --- a/data/json/mapgen_palettes/house_general_palette.json +++ b/data/json/mapgen_palettes/house_general_palette.json @@ -3,6 +3,22 @@ "type": "palette", "id": "standard_domestic_palette", "//": "Intended as a palette for non-nested houses. Symbols still open for use: 0 ! $ % & _ = ~ ? / , ` and some symbols that conflict with json (like brackets).", + "parameters": { + "interior_wall_type": { + "type": "ter_str_id", + "default": { + "distribution": [ + [ "t_wall_b", 1 ], + [ "t_wall_g", 1 ], + [ "t_wall_p", 1 ], + [ "t_wall_P", 1 ], + [ "t_wall_r", 1 ], + [ "t_wall_w", 6 ], + [ "t_wall_y", 1 ] + ] + } + } + }, "toilets": { "t": { } }, "furniture": { "a": "f_fireplace", @@ -81,7 +97,7 @@ "+": [ [ "t_door_c", 5 ], [ "t_door_o", 5 ], [ "t_door_locked_interior", 1 ] ], "*": [ [ "t_door_locked_peep", 2 ], "t_door_locked_alarm", [ "t_door_locked", 10 ], "t_door_c" ], "^": "t_gutter_downspout", - "|": "t_wall_w", + "|": { "param": "interior_wall_type", "fallback": "t_wall_w" }, "#": "t_brick_wall", "¶": "t_door_glass_c", ":": "t_wall_glass", diff --git a/data/json/mapgen_palettes/house_w_palette.json b/data/json/mapgen_palettes/house_w_palette.json index 273493c59836..fa95b4cd17e5 100644 --- a/data/json/mapgen_palettes/house_w_palette.json +++ b/data/json/mapgen_palettes/house_w_palette.json @@ -2,6 +2,15 @@ { "type": "palette", "id": "house_w_nest_palette", + "parameters": { + "house_w_nest_carpet_type": { + "type": "ter_str_id", + "scope": "nest", + "default": { + "distribution": [ [ "t_carpet_yellow", 2 ], [ "t_carpet_green", 2 ], [ "t_carpet_purple", 1 ], [ "t_carpet_red", 1 ], [ "t_floor", 2 ] ] + } + } + }, "furniture": { "A": "f_stool", "B": "f_chair", @@ -66,10 +75,7 @@ "=": "t_region_groundcover_barren", "2": "t_region_groundcover_barren", "5": "t_region_groundcover_barren", - "6": "t_carpet_red", - "7": "t_carpet_green", - "8": "t_carpet_purple", - "9": "t_carpet_yellow", + "6": { "param": "house_w_nest_carpet_type" }, "?": "t_console_broken" }, "toilets": { "t": { } }, diff --git a/data/mods/No_Hope/Mapgen/mall_ground.json b/data/mods/No_Hope/Mapgen/mall_ground.json index 02c74f36489b..e06e235b48e5 100644 --- a/data/mods/No_Hope/Mapgen/mall_ground.json +++ b/data/mods/No_Hope/Mapgen/mall_ground.json @@ -572,7 +572,6 @@ "z": [ { "item": "clothing_hunting", "chance": 40, "repeat": [ 1, 2 ] }, { "item": "tools_hunting", "chance": 20, "repeat": [ 1, 2 ] }, - { "item": "gun_cases", "chance": 30, "repeat": [ 1, 2 ] }, { "item": "camping", "chance": 5, "repeat": [ 1, 2 ] } ], "[": [ @@ -583,7 +582,6 @@ ":": [ { "item": "clothing_hunting", "chance": 40 }, { "item": "tools_hunting", "chance": 20 }, - { "item": "gun_cases", "chance": 30 }, { "item": "camping", "chance": 5 } ], "±": { "item": "clothing_hunting", "chance": 20, "repeat": [ 1, 2 ] }, @@ -915,7 +913,6 @@ "z": [ { "item": "clothing_hunting", "chance": 40, "repeat": [ 1, 2 ] }, { "item": "tools_hunting", "chance": 20, "repeat": [ 1, 2 ] }, - { "item": "gun_cases", "chance": 20, "repeat": [ 1, 2 ] }, { "item": "camping", "chance": 5, "repeat": [ 1, 2 ] } ], "[": [ @@ -926,7 +923,6 @@ ":": [ { "item": "clothing_hunting", "chance": 40 }, { "item": "tools_hunting", "chance": 20 }, - { "item": "gun_cases", "chance": 40 }, { "item": "camping", "chance": 5 } ], "±": { "item": "clothing_hunting", "chance": 30, "repeat": [ 1, 2 ] }, @@ -934,7 +930,7 @@ "T": { "item": "wedding_suits", "chance": 40, "repeat": [ 1, 2 ] }, "Ʌ": { "item": "SUS_tailoring_materials", "chance": 30, "repeat": [ 1, 2 ] }, "N": { "item": "SUS_tailoring_tool_drawer", "chance": 80, "repeat": [ 1, 2 ] }, - "Ŧ": [ { "item": "camping", "chance": 20, "repeat": [ 1, 2 ] }, { "item": "gun_cases", "chance": 20, "repeat": [ 1, 2 ] } ], + "Ŧ": { "item": "camping", "chance": 20, "repeat": [ 1, 2 ] }, "U": { "item": "cleaning", "chance": 30, "repeat": [ 1, 2 ] }, "I": { "item": "cubical_office", "chance": 70, "repeat": [ 2, 4 ] }, "P": [ { "item": "jackets", "chance": 10 }, { "item": "bags", "chance": 10 } ], diff --git a/data/mods/No_Hope/Mapgen/map_extras/mapgen_updates.json b/data/mods/No_Hope/Mapgen/map_extras/mapgen_updates.json index fcf1c51f15b6..3915abb7684a 100644 --- a/data/mods/No_Hope/Mapgen/map_extras/mapgen_updates.json +++ b/data/mods/No_Hope/Mapgen/map_extras/mapgen_updates.json @@ -470,11 +470,5 @@ }, "npcs": { "@": { "class": "raider" } } } - }, - { - "type": "mapgen", - "method": "json", - "update_mapgen_id": "bandits_rv", - "object": { "place_nested": [ { "chunks": [ "bandits_rv" ], "x": 0, "y": 0 } ] } } ] diff --git a/data/raw/keybindings/keybindings.json b/data/raw/keybindings/keybindings.json index 2dea6a0d65b4..437444479c2e 100644 --- a/data/raw/keybindings/keybindings.json +++ b/data/raw/keybindings/keybindings.json @@ -878,6 +878,13 @@ "name": "Place Overmap Special", "bindings": [ { "input_method": "keyboard", "key": "s" } ] }, + { + "type": "keybinding", + "id": "SET_SPECIAL_ARGS", + "category": "OVERMAP", + "name": "Set Overmap Special Arguments", + "bindings": [ { "input_method": "keyboard", "key": "a" } ] + }, { "type": "keybinding", "name": "View Missions", 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 c9b8abec6b26..ad3756912bb2 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 @@ -1015,6 +1015,98 @@ The code excerpt above will place chunks as follows: - `"concrete_wall_ns"`if the north west neighbor is neither a field nor any of the microlab overmaps. +## Mapgen values + +A _mapgen value_ can be used in various places where a specific id is expected. For example, the +default value of a parameter, or a terrain id in the `"terrain"` object. A mapgen value can take one +of three forms: + +- A simple string, which should be a literal id. For example, `"t_flat_roof"`. +- A JSON object containing the key `"distribution"`, whose corresponding value is a list of lists, + each a pair of a string id and an integer weight. For example: + +``` +{ "distribution": [ [ "t_flat_roof", 2 ], [ "t_tar_flat_roof", 1 ], [ "t_shingle_flat_roof", 1 ] ] } +``` + +- A JSON object containing the key `"param"`, whose corresponding value is the string name of a + parameter as discussed in [Mapgen parameters](#mapgen-parameters). For example, + `{ "param": "roof_type" }`. + + You may be required to also supply a fallback value, such as + `{ "param": "roof_type", "fallback": "t_flat_roof" }`. The fallback is necessary to allow mapgen + definitions to change without breaking an ongoing game. Different parts of the same overmap + special can be generated at different times, and if a new parameter is added to the definition + part way through the generation then the value of that parameter will be missing and the fallback + will be used. +- A switch statement to select different values depending on the value of some other mapgen value. + This would most often be used to switch on the value of a mapgen parameter, so as to allow two + parts of the mapgen to be consistent. For example, the following switch would match a fence gate + type to a fence type chosen by a mapgen parameter `fence_type`: + +```json +{ + "switch": { "param": "fence_type", "fallback": "t_splitrail_fence" }, + "cases": { + "t_splitrail_fence": "t_splitrail_fencegate_c", + "t_chainfence": "t_chaingate_c", + "t_fence_barbed": "t_gate_metal_c", + "t_privacy_fence": "t_privacy_fencegate_c" + } +} +``` + +## Mapgen parameters + +(Note that this feature is under development and functionality may not line up exactly with the +documentation.) + +Another entry within a mapgen definition or palette can be a `"parameters"` key. For example: + +``` +"parameters": { + "roof_type": { + "type": "ter_str_id", + "default": { "distribution": [ [ "t_flat_roof", 2 ], [ "t_tar_flat_roof", 1 ], [ "t_shingle_flat_roof", 1 ] ] } + } +}, +``` + +Each entry in the `"parameters"` JSON object defines a parameter. The key is the parameter name. +Each such key should have an associated JSON object. That object must provide its type (which should +be a type string as for a `cata_variant`) and may optionally provide a default value. The default +value should be a [mapgen value](#mapgen-value) as defined above. + +At time of writing, the only way for a parameter to get a value is via the `"default"`, so you +probably want to always have one. + +The primary application of parameters is that you can use a `"distribution"` mapgen value to select +a value at random, and then apply that value to every use of that parameter. In the above example, a +random roof terrain is picked. By using the parameter with some `"terrain"` key, via a `"param"` +mapgen value, you can use a random but consistent choice of roof terrain across your map. In +contrast, placing the `"distribution"` directly in the `"terrain"` object would cause mapgen to +choose a terrain at random for each roof tile, leading to a mishmash of roof terrains. + +By default, the scope of a parameter is the `overmap_special` being generated. That is, the +parameter will have the same value across the `overmap_special`. When a default value is needed, it +will be chosen when the first chunk of that special is generated, and that value will be saved to be +reused for later chunks. + +If you wish, you may specify `"scope": "omt"` to limit the scope to just a single overmap tile. Then +a default value will be chosen independently for each OMT. This has the advantage that you are no +longer forced to select a `"fallback"` value when using that parameter in mapgen. + +The third option for scope is `"scope": "nest"`. This only makes sense when used in nested mapgen +(although it is not an error to use it elsewhere, so that the same palette may be used for nested +and non-nested mapgen). When the scope is `nest`, the value of the parameter is chosen for a +particular nested chunk. For example, suppose a nest defines a carpet across several tiles, you can +use a parameter to ensure that the carpet is the same colour for all the tiles within that nest, but +another instance of the same `nested_mapgen_id` elsewhere in the same OMT might choose a different +colour. + +To help you debug mapgen parameters and their effect on mapgen, you can see the chosen values for +`overmap_special`-scoped parameters in the overmap editor (accessible via the debug menu). + ## Rotate the map with "rotation" Rotates the generated map after all the other mapgen stuff has been done. The value can be a single @@ -1035,7 +1127,55 @@ type. Example: `"predecessor_mapgen": "forest"` -# Using update_mapgen +# Palettes + +A **palette** provides a way to use the same symbol definitions for different pieces of mapgen. For +example, most of the houses defined in CDDA us the `standard_domestic_palette`. That palette, for +example, defines `h` as meaning `f_chair`, so all the house mapgen can use `h` in its `"rows"` array +without needing to repeat this definition everywhere. It simply requires a reference to the palette, +achieved by adding + +```json +"palettes": [ "standard_domestic_palette" ] +``` + +to the definition of each house. + +Each piece of mapgen can refer to multiple palettes. When two palettes both define meanings for the +same symbol, both are applied. In some cases (such as spawning items) you can see the results of +both in the final output. In other cases (such as setting terrain or furniture) one result must +override the others. The rule is that the last palette listed overrides earlier ones, and +definitions in the outer mapgen override anything in the palettes within. + +Palette definitions can contain any of the JSON described above for the +[JSON object definition](#json-object-definition) where it is defining a meaning for a symbol. They +cannot specify anything for a particular location (using `"x"` and `"y"` coordinates. + +Palettes can themselves include other palettes via a `"palettes"` key. So if two or more palettes +would have many of the same symbols with the same meanings that common part can be pulled out into a +new palette which each of them includes, so that the definitions need not be repeated. + +## Palette ids as mapgen values + +The values in the `"palettes"` list need not be simple strings. They can be any +[mapgen value](#mapgen-values) as described above. Most importantly, this means that they can use a +`"distribution"` to select from a set of palettes at random. + +This selection works as if it were an overmap special-scoped [mapgen parameter](#mapgen-parameters). +So, all OMTs within a special will use the same palette. Moreover, you can see which palette was +chosen by looking at the overmap special arguments displayed in the overmap editor (accessible via +the debug menu). + +For example, the following JSON used in a cabin mapgen definition + +```json +"palettes": [ { "distribution": [ [ "cabin_palette", 1 ], [ "cabin_palette_abandoned", 1 ] ] } ], +``` + +causes half the cabins generated to use the regular `cabin_palette` and the other half to use +`cabin_palette_abandoned`. + +# Using `update_mapgen` **update_mapgen** is a variant of normal JSON mapgen. Instead of creating a new overmap tile, it updates an existing overmap tile with a specific set of changes. Currently, it only works within the diff --git a/src/cata_variant.cpp b/src/cata_variant.cpp index b411a4944260..be57f6cff390 100644 --- a/src/cata_variant.cpp +++ b/src/cata_variant.cpp @@ -1,5 +1,34 @@ #include "cata_variant.h" +#include "debug_menu.h" +#include "mutation.h" +#include "character.h" + +template +static bool is_valid_impl_2( const std::string &s ) +{ + constexpr cata_variant_type T = static_cast( I ); + return cata_variant_detail::convert::is_valid( s ); +} + +template +constexpr bool is_valid_impl( const cata_variant &v, std::index_sequence ) +{ + constexpr size_t num_types = static_cast( cata_variant_type::num_types ); + constexpr std::array is_valid_helpers = {{ + is_valid_impl_2... + } + }; + // No match + return is_valid_helpers[static_cast( v.type() )]( v.get_string() ); +} + +bool cata_variant::is_valid() const +{ + constexpr size_t num_types = static_cast( cata_variant_type::num_types ); + return is_valid_impl( *this, std::make_index_sequence {} ); +} + namespace io { @@ -18,20 +47,34 @@ std::string enum_to_string( cata_variant_type type ) case cata_variant_type::efftype_id: return "efftype_id"; case cata_variant_type::flag_id: return "flag_id"; case cata_variant_type::hp_part: return "hp_part"; + case cata_variant_type::faction_id: return "faction_id"; + case cata_variant_type::field_type_id: return "field_type_id"; + case cata_variant_type::field_type_str_id: return "field_type_str_id"; + case cata_variant_type::furn_id: return "furn_id"; + case cata_variant_type::furn_str_id: return "furn_str_id"; case cata_variant_type::int_: return "int"; + case cata_variant_type::item_group_id: return "item_group_id"; case cata_variant_type::itype_id: return "itype_id"; case cata_variant_type::matype_id: return "matype_id"; + case cata_variant_type::mongroup_id: return "mongroup_id"; case cata_variant_type::mtype_id: return "mtype_id"; case cata_variant_type::mutagen_technique: return "mutagen_technique"; case cata_variant_type::mutation_category_id: return "mutation_category_id"; + case cata_variant_type::npc_template_id: return "npc_template_id"; case cata_variant_type::oter_id: return "oter_id"; + case cata_variant_type::palette_id: return "palette_id"; case cata_variant_type::skill_id: return "skill_id"; case cata_variant_type::species_id: return "species_id"; case cata_variant_type::spell_id: return "spell_id"; case cata_variant_type::string: return "string"; case cata_variant_type::ter_id: return "ter_id"; + case cata_variant_type::ter_furn_transform_id: return "ter_furn_transform_id"; + case cata_variant_type::ter_str_id: return "ter_str_id"; case cata_variant_type::trait_id: return "trait_id"; + case cata_variant_type::trap_id: return "trap_id"; case cata_variant_type::trap_str_id: return "trap_str_id"; + case cata_variant_type::vgroup_id: return "vgroup_id"; + case cata_variant_type::zone_type_id: return "zone_type_id"; // *INDENT-ON* case cata_variant_type::num_types: break; diff --git a/src/cata_variant.h b/src/cata_variant.h index b2a88be9d98d..2b6ad61d5ff0 100644 --- a/src/cata_variant.h +++ b/src/cata_variant.h @@ -14,6 +14,7 @@ #include "enum_conversions.h" #include "hash_utils.h" #include "pldata.h" +#include "to_string_id.h" #include "type_id.h" class JsonIn; @@ -39,20 +40,34 @@ enum class cata_variant_type : int { efftype_id, flag_id, hp_part, + faction_id, + field_type_id, + field_type_str_id, + furn_id, + furn_str_id, int_, + item_group_id, itype_id, matype_id, mtype_id, + mongroup_id, mutagen_technique, mutation_category_id, + npc_template_id, oter_id, + palette_id, skill_id, species_id, spell_id, string, ter_id, + ter_furn_transform_id, + ter_str_id, trait_id, + trap_id, trap_str_id, + vgroup_id, + zone_type_id, num_types, // last }; @@ -92,14 +107,6 @@ constexpr cata_variant_type type_for_impl( std::index_sequence ) return cata_variant_type::num_types; } -template -constexpr cata_variant_type type_for() -{ - constexpr size_t num_types = static_cast( cata_variant_type::num_types ); - using SimpleT = std::remove_cv_t>; - return type_for_impl( std::make_index_sequence {} ); -} - // Inherit from this struct to easily implement convert specializations for any // string type template @@ -113,6 +120,9 @@ struct convert_string { static T from_string( const std::string &v ) { return v; } + static bool is_valid( const std::string & ) { + return true; + } }; // Inherit from this struct to easily implement convert specializations for any @@ -126,6 +136,9 @@ struct convert_string_id { static T from_string( const std::string &v ) { return T( v ); } + static bool is_valid( const std::string &v ) { + return from_string( v ).is_valid(); + } }; // Inherit from this struct to easily implement convert specializations for any @@ -139,6 +152,9 @@ struct convert_int_id { static T from_string( const std::string &v ) { return T( v ); } + static bool is_valid( const std::string &v ) { + return to_string_id_t( v ).is_valid(); + } }; // Inherit from this struct to easily implement convert specializations for any @@ -152,16 +168,22 @@ struct convert_enum { static T from_string( const std::string &v ) { return io::string_to_enum( v ); } + static bool is_valid( const std::string &v ) { + return io::enum_is_valid( v ); + } }; // These are the specializations of convert for each value type. -static_assert( static_cast( cata_variant_type::num_types ) == 24, +static_assert( static_cast( cata_variant_type::num_types ) == 38, "This assert is a reminder to add conversion support for any new types to the " "below specializations" ); template<> struct convert { using type = void; + static bool is_valid( const std::string &s ) { + return s.empty(); + } }; template<> @@ -182,6 +204,11 @@ struct convert { static bool from_string( const std::string &v ) { return std::stoi( v ); } + static bool is_valid( const std::string &v ) { + // TODO: check for int-ness + int i = std::stoi( v ); + return i >= 0 && i <= 1; + } }; template<> @@ -193,6 +220,10 @@ struct convert { static character_id from_string( const std::string &v ) { return character_id( std::stoi( v ) ); } + static bool is_valid( const std::string & ) { + // TODO: check for int-ness + return true; + } }; template<> @@ -207,6 +238,21 @@ struct convert : convert_string_id {}; template<> struct convert : convert_enum {}; +template<> +struct convert : convert_string_id {}; + +template<> +struct convert : convert_int_id {}; + +template<> +struct convert : convert_string_id {}; + +template<> +struct convert : convert_int_id {}; + +template<> +struct convert : convert_string_id {}; + template<> struct convert { using type = int; @@ -216,8 +262,15 @@ struct convert { static int from_string( const std::string &v ) { return std::stoi( v ); } + static bool is_valid( const std::string & ) { + // TODO: check for int-ness + return true; + } }; +template<> +struct convert : convert_string_id {}; + template<> struct convert : convert_string_id {}; @@ -227,15 +280,24 @@ struct convert : convert_string_id {}; template<> struct convert : convert_string_id {}; +template<> +struct convert : convert_string_id {}; + template<> struct convert : convert_enum {}; template<> struct convert : convert_string {}; +template<> +struct convert : convert_string_id {}; + template<> struct convert : convert_int_id {}; +template<> +struct convert : convert_string_id {}; + template<> struct convert : convert_string_id {}; @@ -251,14 +313,38 @@ struct convert : convert_string {}; template<> struct convert : convert_int_id {}; +template<> +struct convert : + convert_string_id {}; + +template<> +struct convert : convert_string_id {}; + template<> struct convert : convert_string_id {}; +template<> +struct convert : convert_int_id {}; + template<> struct convert : convert_string_id {}; +template<> +struct convert : convert_string_id {}; + +template<> +struct convert : convert_string_id {}; + } // namespace cata_variant_detail +template +constexpr cata_variant_type cata_variant_type_for() +{ + constexpr size_t num_types = static_cast( cata_variant_type::num_types ); + using SimpleT = std::remove_cv_t>; + return cata_variant_detail::type_for_impl( std::make_index_sequence {} ); +} + class cata_variant { public: @@ -270,9 +356,9 @@ class cata_variant // value passed. template < typename Value, typename = std::enable_if_t <( - cata_variant_detail::type_for() < cata_variant_type::num_types )>> + cata_variant_type_for() < cata_variant_type::num_types )>> explicit cata_variant( Value && value ) { - constexpr cata_variant_type Type = cata_variant_detail::type_for(); + constexpr cata_variant_type Type = cata_variant_type_for(); type_ = Type; value_ = cata_variant_detail::convert::to_string( std::forward( value ) ); @@ -289,6 +375,13 @@ class cata_variant std::forward( value ) ) ); } + // Call this to construct from a type + string. This should rarely be + // necessary, so think twice before using it (that's why the equivalent + // constructor is private). + static cata_variant from_string( cata_variant_type t, std::string &&v ) { + return cata_variant( t, std::move( v ) ); + } + cata_variant_type type() const { return type_; } @@ -306,13 +399,15 @@ class cata_variant template T get() const { - return get()>(); + return get()>(); } const std::string &get_string() const { return value_; } + bool is_valid() const; + std::pair as_pair() const { return std::make_pair( type_, value_ ); } diff --git a/src/clzones.cpp b/src/clzones.cpp index b7f224844a12..a748bacbe4ab 100644 --- a/src/clzones.cpp +++ b/src/clzones.cpp @@ -61,22 +61,6 @@ static const zone_type_id zone_NO_NPC_PICKUP( "NO_NPC_PICKUP" ); zone_manager::zone_manager() { - types.emplace( zone_NO_AUTO_PICKUP, - zone_type( translate_marker( "No Auto Pickup" ), - translate_marker( "You won't auto-pickup items inside the zone." ) ) ); - types.emplace( zone_NO_NPC_PICKUP, - zone_type( translate_marker( "No NPC Pickup" ), - translate_marker( "Friendly NPCs don't pickup items inside the zone." ) ) ); - types.emplace( zone_type_id( "NPC_RETREAT" ), - zone_type( translate_marker( "NPC Retreat" ), - translate_marker( "When fleeing, friendly NPCs will attempt to retreat toward this zone if it is within 60 tiles." ) ) ); - types.emplace( zone_type_id( "NPC_NO_INVESTIGATE" ), - zone_type( translate_marker( "NPC Ignore Sounds" ), - translate_marker( "Friendly NPCs won't investigate unseen sounds coming from this zone." ) ) ); - types.emplace( zone_type_id( "NPC_INVESTIGATE_ONLY" ), - zone_type( translate_marker( "NPC Investigation Area" ), - translate_marker( "Friendly NPCs will investigate unseen sounds only if they come from inside this area." ) ) ); - for( const zone_type &zone : zone_type::get_all() ) { types.emplace( zone.id, zone ); } diff --git a/src/enum_conversions.h b/src/enum_conversions.h index 7e94a6d8c865..3e16d5dde4bc 100644 --- a/src/enum_conversions.h +++ b/src/enum_conversions.h @@ -61,6 +61,14 @@ std::unordered_map build_enum_lookup_map() return result; } +template +const std::unordered_map &get_enum_lookup_map() +{ + static const std::unordered_map string_to_enum_map = + build_enum_lookup_map(); + return string_to_enum_map; +} + // Helper function to do the lookup in an associative container template inline E string_to_enum_look_up( const C &container, const std::string &data ) @@ -76,9 +84,13 @@ inline E string_to_enum_look_up( const C &container, const std::string &data ) template E string_to_enum( const std::string &data ) { - static const std::unordered_map string_to_enum_map = - build_enum_lookup_map(); - return string_to_enum_look_up( string_to_enum_map, data ); + return string_to_enum_look_up( get_enum_lookup_map(), data ); +} + +template +bool enum_is_valid( const std::string &data ) +{ + return get_enum_lookup_map().count( data ); } /*@}*/ diff --git a/src/faction.cpp b/src/faction.cpp index 6819ae2561e7..4baf6c122201 100644 --- a/src/faction.cpp +++ b/src/faction.cpp @@ -474,7 +474,7 @@ const faction &string_id::obj() const template<> bool string_id::is_valid() const { - return g->faction_manager_ptr->get( *this, false ); + return g->faction_manager_ptr->get( *this, false ) != nullptr; } void basecamp::faction_display( const catacurses::window &fac_w, const int width ) const diff --git a/src/game.cpp b/src/game.cpp index f63bb3bb867f..bec606cc8789 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -3939,6 +3939,9 @@ void game::mon_info_update( ) case direction::BELOWSOUTHEAST: index = 3; break; + case direction::last: + debugmsg( "invalid direction" ); + abort(); } } diff --git a/src/init.cpp b/src/init.cpp index 7f08f4c30af9..458925204e0d 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -693,6 +693,7 @@ void DynamicDataLoader::finalize_loaded_data( loading_ui &ui ) { _( "Zone manager" ), &zone_manager::reset_manager }, { _( "Vehicle prototypes" ), &vehicle_prototype::finalize }, { _( "Mapgen weights" ), &calculate_mapgen_weights }, + { _( "Mapgen parameters" ), &overmap_specials::finalize_mapgen_parameters }, { _( "Monster types" ), []() { diff --git a/src/json.cpp b/src/json.cpp index d9651ce12716..23da4ac94181 100644 --- a/src/json.cpp +++ b/src/json.cpp @@ -585,6 +585,12 @@ void JsonArray::verify_index( const size_t i ) const /* iterative access */ +JsonValue JsonArray::next() +{ + verify_index( index ); + return JsonValue( *jsin, positions[index++] ); +} + bool JsonArray::next_bool() { verify_index( index ); @@ -1342,9 +1348,13 @@ number_sci_notation JsonIn::get_any_number() number_sci_notation ret; int mod_e = 0; eat_whitespace(); - stream->get( ch ); + if( !stream->get( ch ) ) { + error( "unexpected end of input", 0 ); + } if( ( ret.negative = ch == '-' ) ) { - stream->get( ch ); + if( !stream->get( ch ) ) { + error( "unexpected end of input", 0 ); + } } else if( ch != '.' && ( ch < '0' || ch > '9' ) ) { // not a valid float std::stringstream err; @@ -1361,36 +1371,42 @@ number_sci_notation JsonIn::get_any_number() while( ch >= '0' && ch <= '9' ) { ret.number *= 10; ret.number += ( ch - '0' ); - stream->get( ch ); + if( !stream->get( ch ) ) { + break; + } } if( ch == '.' ) { - stream->get( ch ); - while( ch >= '0' && ch <= '9' ) { + while( stream->get( ch ) && ch >= '0' && ch <= '9' ) { ret.number *= 10; ret.number += ( ch - '0' ); mod_e -= 1; - stream->get( ch ); } } - if( ch == 'e' || ch == 'E' ) { - stream->get( ch ); + if( stream && ( ch == 'e' || ch == 'E' ) ) { + if( !stream->get( ch ) ) { + error( "unexpected end of input", 0 ); + } bool neg; - if( ( neg = ch == '-' ) ) { - stream->get( ch ); - } else if( ch == '+' ) { - stream->get( ch ); + if( ( neg = ch == '-' ) || ch == '+' ) { + if( !stream->get( ch ) ) { + error( "unexpected end of input", 0 ); + } } while( ch >= '0' && ch <= '9' ) { ret.exp *= 10; ret.exp += ( ch - '0' ); - stream->get( ch ); + if( !stream->get( ch ) ) { + break; + } } if( neg ) { ret.exp *= -1; } } // unget the final non-number character (probably a separator) - stream->unget(); + if( stream ) { + stream->unget(); + } end_value(); ret.exp += mod_e; return ret; @@ -1550,6 +1566,22 @@ bool JsonIn::test_object() /* non-fatal value setting by reference */ +bool JsonIn::read_null( bool throw_on_error ) +{ + if( !test_null() ) { + return error_or_false( throw_on_error, "Expected null" ); + } + char text[5]; + if( !stream->get( text, 5 ) ) { + error( "Unexpected end of stream reading null", 0 ); + } + if( 0 != strcmp( text, "null" ) ) { + error( std::string( "Expected 'null', got '" ) + text + "'", -4 ); + } + end_value(); + return true; +} + bool JsonIn::read( bool &b, bool throw_on_error ) { if( !test_bool() ) { diff --git a/src/json.h b/src/json.h index 9d90b6a85140..ac4317b082aa 100644 --- a/src/json.h +++ b/src/json.h @@ -266,6 +266,7 @@ class JsonIn // optionally-fatal reading into values by reference // returns true if the data was read successfully, false otherwise // if throw_on_error then throws JsonError rather than returning false. + bool read_null( bool throw_on_error = false ); bool read( bool &b, bool throw_on_error = false ); bool read( char &c, bool throw_on_error = false ); bool read( signed char &c, bool throw_on_error = false ); @@ -1152,6 +1153,7 @@ class JsonArray void show_warning( const std::string &err, int idx ); // iterative access + JsonValue next(); bool next_bool(); int next_int(); double next_float(); @@ -1541,7 +1543,7 @@ void serialize( const std::optional &obj, JsonOut &jsout ) template void deserialize( std::optional &obj, JsonIn &jsin ) { - if( jsin.test_null() ) { + if( jsin.read_null() ) { obj.reset(); } else { obj.emplace(); diff --git a/src/line.cpp b/src/line.cpp index 2701b5f4ae5e..73c45b4b4db6 100644 --- a/src/line.cpp +++ b/src/line.cpp @@ -13,6 +13,7 @@ #include "string_formatter.h" #include "string_utils.h" #include "enums.h" +#include "enum_conversions.h" #include "point_float.h" double iso_tangent( double distance, units::angle vertex ) @@ -410,6 +411,53 @@ std::vector continue_line( const std::vector &line, const in return line_to( line.back(), move_along_line( line.back(), line, distance ) ); } +namespace io +{ + +template<> +std::string enum_to_string( direction data ) +{ + switch( data ) { + // *INDENT-OFF* + case direction::ABOVENORTHWEST: return "above_north_west"; + case direction::NORTHWEST: return "north_west"; + case direction::BELOWNORTHWEST: return "below_north_west"; + case direction::ABOVENORTH: return "above_north"; + case direction::NORTH: return "north"; + case direction::BELOWNORTH: return "below_north"; + case direction::ABOVENORTHEAST: return "above_north_east"; + case direction::NORTHEAST: return "north_east"; + case direction::BELOWNORTHEAST: return "below_north_east"; + + case direction::ABOVEWEST: return "above_west"; + case direction::WEST: return "west"; + case direction::BELOWWEST: return "below_west"; + case direction::ABOVECENTER: return "above"; + case direction::CENTER: return "center"; + case direction::BELOWCENTER: return "below"; + case direction::ABOVEEAST: return "above_east"; + case direction::EAST: return "east"; + case direction::BELOWEAST: return "below_east"; + + case direction::ABOVESOUTHWEST: return "above_south_west"; + case direction::SOUTHWEST: return "south_west"; + case direction::BELOWSOUTHWEST: return "below_south_west"; + case direction::ABOVESOUTH: return "above_south"; + case direction::SOUTH: return "south"; + case direction::BELOWSOUTH: return "below_south"; + case direction::ABOVESOUTHEAST: return "above_south_east"; + case direction::SOUTHEAST: return "south_east"; + case direction::BELOWSOUTHEAST: return "below_south_east"; + // *INDENT-ON* + case direction::last: + break; + } + debugmsg( "Invalid direction" ); + abort(); +} + +} // namespace io + direction direction_from( point p ) noexcept { return static_cast( make_xyz( tripoint( p, 0 ) ) ); @@ -487,6 +535,9 @@ tripoint displace( direction dir ) return point_south_east + tripoint_above; case direction::BELOWSOUTHEAST: return point_south_east + tripoint_below; + case direction::last: + debugmsg( "Invalid direction" ); + abort(); } return tripoint_zero; @@ -531,6 +582,9 @@ point displace_XY( const direction dir ) case direction::ABOVESOUTHEAST: case direction::BELOWSOUTHEAST: return point_south_east; + case direction::last: + debugmsg( "Invalid direction" ); + abort(); } return point_zero; diff --git a/src/line.h b/src/line.h index ddb830f91c43..0f7b8d3aec71 100644 --- a/src/line.h +++ b/src/line.h @@ -13,6 +13,8 @@ #include "cached_options.h" #include "units_angle.h" +template struct enum_traits; + /** * Calculate base of an isosceles triangle * @param distance one of the equal lengths @@ -66,7 +68,23 @@ enum class direction : unsigned { ABOVESOUTHEAST = make_xyz_unit( tripoint_above + tripoint_south_east ), SOUTHEAST = make_xyz_unit( tripoint_south_east ), BELOWSOUTHEAST = make_xyz_unit( tripoint_below + tripoint_south_east ), + + last = 27 +}; + +template<> +struct enum_traits { + static constexpr direction last = direction::last; +}; + +namespace std +{ +template <> struct hash { + std::size_t operator()( const direction &d ) const { + return static_cast( d ); + } }; +} // namespace std template< class T > constexpr inline direction operator%( const direction &lhs, const T &rhs ) diff --git a/src/magic_ter_fur_transform.cpp b/src/magic_ter_fur_transform.cpp index 04f1b745762f..f8892f07b582 100644 --- a/src/magic_ter_fur_transform.cpp +++ b/src/magic_ter_fur_transform.cpp @@ -64,14 +64,7 @@ static void load_transform_results( const JsonObject &jsi, const std::string &js list.add( T( jsi.get_string( json_key ) ), 1 ); return; } - for( const JsonValue entry : jsi.get_array( json_key ) ) { - if( entry.test_array() ) { - JsonArray inner = entry.get_array(); - list.add( T( inner.get_string( 0 ) ), inner.get_int( 1 ) ); - } else { - list.add( T( entry.get_string() ), 1 ); - } - } + load_weighted_list( jsi.get_member( json_key ), list, 1 ); } template diff --git a/src/map.h b/src/map.h index 79804e4172c9..4e83b2bdfbc9 100644 --- a/src/map.h +++ b/src/map.h @@ -1548,8 +1548,8 @@ class map point p1, point p2, float density, bool individual = false, bool friendly = false, const std::string &name = "NONE", int mission_id = -1 ); - void place_gas_pump( point p, int charges, const std::string &fuel_type ); - void place_gas_pump( point p, int charges ); + void place_gas_pump( const point &p, int charges, const itype_id &fuel_type ); + void place_gas_pump( const point &p, int charges ); // 6 liters at 250 ml per charge void place_toilet( point p, int charges = 6 * 4 ); void place_vending( point p, const item_group_id &type, bool reinforced = false ); @@ -1757,13 +1757,13 @@ class map void copy_grid( const tripoint &to, const tripoint &from ); void draw_map( mapgendata &dat ); - void draw_office_tower( mapgendata &dat ); + void draw_office_tower( const mapgendata &dat ); void draw_lab( mapgendata &dat ); - void draw_temple( mapgendata &dat ); + void draw_temple( const mapgendata &dat ); void draw_mine( mapgendata &dat ); void draw_slimepit( mapgendata &dat ); - void draw_triffid( mapgendata &dat ); - void draw_connections( mapgendata &dat ); + void draw_triffid( const mapgendata &dat ); + void draw_connections( const mapgendata &dat ); // Builds a transparency cache and returns true if the cache was invalidated. // Used to determine if seen cache should be rebuilt. diff --git a/src/mapgen.cpp b/src/mapgen.cpp index 04c02db778b8..d41924308ad2 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -68,6 +68,7 @@ #include "submap.h" #include "text_snippets.h" #include "tileray.h" +#include "to_string_id.h" #include "translations.h" #include "trap.h" #include "value_ptr.h" @@ -78,6 +79,11 @@ #include "vpart_range.h" #include "weighted_list.h" +static const itype_id itype_avgas( "avgas" ); +static const itype_id itype_diesel( "diesel" ); +static const itype_id itype_gasoline( "gasoline" ); +static const itype_id itype_jp8( "jp8" ); + static const mongroup_id GROUP_BLOB( "GROUP_BLOB" ); static const mongroup_id GROUP_BREATHER( "GROUP_BREATHER" ); static const mongroup_id GROUP_BREATHER_HUB( "GROUP_BREATHER_HUB" ); @@ -102,7 +108,7 @@ static const trait_id trait_NPC_STATIC_NPC( "NPC_STATIC_NPC" ); static constexpr int MON_RADIUS = 3; -static void science_room( map *m, point p1, point p2, int z, int rotate ); +static void science_room( map *m, const point &p1, const point &p2, int z, int rotate ); // (x,y,z) are absolute coordinates of a submap // x%2 and y%2 must be 0! @@ -283,11 +289,25 @@ class mapgen_basic_container // Not needed anymore, pointers are now stored in weights_ (or not used at all) mapgens_.clear(); } + void finalize_parameters() { + for( auto &mapgen_function_ptr : weights_ ) { + mapgen_function_ptr.obj->finalize_parameters(); + } + } void check_consistency( const std::string &key ) { for( auto &mapgen_function_ptr : weights_ ) { mapgen_function_ptr.obj->check( key ); } } + + mapgen_parameters get_mapgen_params( mapgen_parameter_scope scope, + const std::string &context ) const { + mapgen_parameters result; + for( const weighted_object> &p : weights_ ) { + result.check_and_merge( p.obj->get_mapgen_params( scope ), context ); + } + return result; + } }; class mapgen_factory @@ -325,9 +345,15 @@ class mapgen_factory omw.second.setup(); inp_mngr.pump_events(); } - // Dummy entry, overmap terrain null should never appear and is therefor never generated. + // Dummy entry, overmap terrain null should never appear and is + // therefore never generated. mapgens_.erase( "null" ); } + void finalize_parameters() { + for( std::pair &omw : mapgens_ ) { + omw.second.finalize_parameters(); + } + } void check_consistency() { // Cache all strings that may get looked up here so we don't have to go through // all the sources for them upon each loop. @@ -359,6 +385,15 @@ class mapgen_factory } return iter->second.generate( dat, hardcoded_weight ); } + + mapgen_parameters get_map_special_params( const std::string &key ) const { + const auto iter = mapgens_.find( key ); + if( iter == mapgens_.end() ) { + return mapgen_parameters(); + } + return iter->second.get_mapgen_params( mapgen_parameter_scope::overmap_special, + string_format( "map special %s", key ) ); + } }; static mapgen_factory oter_mapgen; @@ -389,7 +424,21 @@ void calculate_mapgen_weights() // TODO: rename as it runs jsonfunction setup inp_mngr.pump_events(); } } - + // Having set up all the mapgens we can now perform a second + // pass of finalizing their parameters + oter_mapgen.finalize_parameters(); + for( auto &pr : nested_mapgen ) { + for( weighted_object> &ptr : pr.second ) { + ptr.obj->finalize_parameters(); + inp_mngr.pump_events(); + } + } + for( auto &pr : update_mapgen ) { + for( auto &ptr : pr.second ) { + ptr->finalize_parameters(); + inp_mngr.pump_events(); + } + } } void check_mapgen_definitions() @@ -569,7 +618,7 @@ void reset_mapgens() ///// 2 - right after init() finishes parsing all game json and terrain info/etc is set.. ///// ...parse more json! (mapgen_function_json) -size_t mapgen_function_json_base::calc_index( point p ) const +size_t mapgen_function_json_base::calc_index( const point &p ) const { if( p.x >= mapgensize.x ) { debugmsg( "invalid value %zu for x in calc_index", p.x ); @@ -601,6 +650,12 @@ static bool common_check_bounds( const jmapgen_int &x, const jmapgen_int &y, return true; } +void mapgen_function_json_base::merge_non_nest_parameters_into( + mapgen_parameters ¶ms, const std::string &outer_context ) const +{ + params.check_and_merge( parameters, outer_context, mapgen_parameter_scope::nest ); +} + bool mapgen_function_json_base::check_inbounds( const jmapgen_int &x, const jmapgen_int &y, const JsonObject &jso ) const { @@ -609,7 +664,6 @@ bool mapgen_function_json_base::check_inbounds( const jmapgen_int &x, const jmap mapgen_function_json_base::mapgen_function_json_base( const json_source_location &jsrcloc ) : jsrcloc( jsrcloc ) - , do_format( false ) , is_ready( false ) , mapgensize( SEEX * 2, SEEY * 2 ) , total_size( mapgensize ) @@ -804,6 +858,17 @@ void mapgen_function_json_base::setup_setmap( const JsonArray &parray ) } +void mapgen_function_json_base::finalize_parameters_common() +{ + objects.merge_parameters_into( parameters, "" ); +} + +mapgen_arguments mapgen_function_json_base::get_args( + const mapgendata &md, mapgen_parameter_scope scope ) const +{ + return parameters.get_args( md, scope ); +} + jmapgen_place::jmapgen_place( const JsonObject &jsi ) : x( jsi, "x" ) , y( jsi, "y" ) @@ -833,29 +898,655 @@ map_key::map_key( const JsonMember &member ) : str( member.name() ) } } +template +static bool is_null_helper( const string_id &id ) +{ + return id.is_null(); +} + +template +static bool is_null_helper( const int_id &id ) +{ + return id.id().is_null(); +} + +static bool is_null_helper( const std::string & ) +{ + return false; +} + +template +struct make_null_helper; + +template<> +struct make_null_helper { + std::string operator()() const { + return {}; + } +}; + +template +struct make_null_helper> { + string_id operator()() const { + return string_id::NULL_ID(); + } +}; + +template +struct make_null_helper> { + int_id operator()() const { + return string_id::NULL_ID().id(); + } +}; + +template +static string_id to_string_id_helper( const string_id &id ) +{ + return id; +} + +template +static string_id to_string_id_helper( const int_id &id ) +{ + return id.id(); +} + +static std::string to_string_id_helper( const std::string &s ) +{ + return s; +} + +template +static bool is_valid_helper( const string_id &id ) +{ + return id.is_valid(); +} + +template +static bool is_valid_helper( const int_id & ) +{ + return true; +} + +static bool is_valid_helper( const std::string & ) +{ + return true; +} + +// Mapgen often uses various id values. Usually these are specified verbatim +// as strings, but they can also be parameterized. This class encapsulates +// such a value. It records how the value was specified so that it can be +// calculated later based on the parameters chosen for a particular instance of +// the mapgen. +template +class mapgen_value +{ + public: + using StringId = to_string_id_t; + struct void_; + using Id_unless_string = + std::conditional_t::value, void_, Id>; + + struct value_source { + virtual ~value_source() = default; + virtual Id get( const mapgendata & ) const = 0; + virtual void check( const std::string &/*oter_name*/, const mapgen_parameters & + ) const {}; + virtual void check_consistent_with( + const value_source &, const std::string &context ) const = 0; + virtual std::vector all_possible_results( + const mapgen_parameters & ) const = 0; + }; + + struct null_source : value_source { + Id get( const mapgendata & ) const override { + return make_null_helper {}(); + } + + void check_consistent_with( + const value_source &o, const std::string &context ) const override { + if( const null_source *other = dynamic_cast( &o ) ) { + // OK + } else { + debugmsg( "inconsistent default types for %s", context ); + } + } + + std::vector all_possible_results( const mapgen_parameters & ) const override { + return { make_null_helper{}() }; + } + }; + + struct id_source : value_source { + Id id; + + explicit id_source( const std::string &s ) : + id( s ) { + } + + explicit id_source( const Id_unless_string &s ) : + id( s ) { + } + + Id get( const mapgendata & ) const override { + return id; + } + + void check( const std::string &context, const mapgen_parameters & ) const override { + if( !is_valid_helper( id ) ) { + debugmsg( "mapgen '%s' uses invalid entry '%s' in weighted list", + context, cata_variant( id ).get_string() ); + } + } + + void check_consistent_with( + const value_source &o, const std::string &context ) const override { + if( const id_source *other = dynamic_cast( &o ) ) { + if( id != other->id ) { + debugmsg( "inconsistent default values for %s (%s vs %s)", + context, cata_variant( id ).get_string(), + cata_variant( other->id ).get_string() ); + } + } else { + debugmsg( "inconsistent default types for %s", context ); + } + } + + std::vector all_possible_results( const mapgen_parameters & ) const override { + return { to_string_id_helper( id ) }; + } + }; + + struct param_source : value_source { + std::string param_name; + std::optional fallback; + + explicit param_source( const JsonObject &jo ) + : param_name( jo.get_string( "param" ) ) { + jo.read( "fallback", fallback, false ); + } + + Id get( const mapgendata &dat ) const override { + if( fallback ) { + return Id( dat.get_arg_or( param_name, *fallback ) ); + } else { + return Id( dat.get_arg( param_name ) ); + } + } + + void check( const std::string &context, const mapgen_parameters ¶meters + ) const override { + auto param_it = parameters.map.find( param_name ); + if( param_it == parameters.map.end() ) { + debugmsg( "mapgen '%s' uses undefined parameter '%s'", context, param_name ); + } else { + const mapgen_parameter ¶m = param_it->second; + constexpr cata_variant_type req_type = cata_variant_type_for(); + cata_variant_type param_type = param.type(); + // TODO: mutation_category_id also uses std::string as underlaying type, + // migrate to type safe string_id to get rid of this hack + if( param_type != req_type && req_type != cata_variant_type::string + && req_type != cata_variant_type::mutation_category_id ) { + debugmsg( "mapgen '%s' uses parameter '%s' of type '%s' in a context " + "expecting type '%s'", context, param_name, + io::enum_to_string( param_type ), + io::enum_to_string( req_type ) ); + } + if( param.scope() == mapgen_parameter_scope::overmap_special && !fallback ) { + debugmsg( "mapgen '%s' uses parameter '%s' of map_special scope without a " + "fallback. Such parameters must provide a fallback to allow " + "for changes to overmap_special definitions", context, + param_name ); + } + } + } + + void check_consistent_with( + const value_source &o, const std::string &context ) const override { + if( const param_source *other = dynamic_cast( &o ) ) { + if( param_name != other->param_name ) { + debugmsg( "inconsistent default values for %s (%s vs %s)", + context, param_name, other->param_name ); + } + } else { + debugmsg( "inconsistent default types for %s", context ); + } + } + + std::vector all_possible_results( + const mapgen_parameters ¶ms ) const override { + auto param_it = params.map.find( param_name ); + if( param_it == params.map.end() ) { + return {}; + } else { + const mapgen_parameter ¶m = param_it->second; + std::vector result; + for( const std::string &s : param.all_possible_values( params ) ) { + result.emplace_back( s ); + } + return result; + } + } + }; + + struct distribution_source : value_source { + weighted_int_list list; + + explicit distribution_source( const JsonObject &jo ) { + load_weighted_list( jo.get_member( "distribution" ), list, 1 ); + } + + Id get( const mapgendata & ) const override { + return *list.pick(); + } + + void check( const std::string &context, const mapgen_parameters & ) const override { + for( const weighted_object &wo : list ) { + if( !is_valid_helper( wo.obj ) ) { + debugmsg( "mapgen '%s' uses invalid entry '%s' in weighted list", + context, cata_variant( wo.obj ).get_string() ); + } + } + } + + void check_consistent_with( + const value_source &o, const std::string &context ) const override { + if( const distribution_source *other = + dynamic_cast( &o ) ) { + if( list != other->list ) { + const std::string my_list = list.to_debug_string(); + const std::string other_list = other->list.to_debug_string(); + debugmsg( "inconsistent default value distributions for %s (%s vs %s)", + context, my_list, other_list ); + } + } else { + debugmsg( "inconsistent default types for %s", context ); + } + } + + std::vector all_possible_results( const mapgen_parameters & ) const override { + std::vector result; + for( const weighted_object &wo : list ) { + result.push_back( wo.obj ); + } + return result; + } + }; + + struct switch_source : value_source { + // This has to be a pointer because mapgen_value is an incomplete + // type. We could resolve this by pulling out all these + // value_source classes and defining them at namespace scope after + // mapgen_value, but that would make the code much more verbose. + std::unique_ptr> on; + std::unordered_map cases; + + explicit switch_source( const JsonObject &jo ) + : on( std::make_unique>( jo.get_object( "switch" ) ) ) { + jo.read( "cases", cases, true ); + } + + Id get( const mapgendata &dat ) const override { + std::string based_on = on->get( dat ); + auto it = cases.find( based_on ); + if( it == cases.end() ) { + debugmsg( "switch does not handle case %s", based_on ); + return make_null_helper {}(); + } + return Id( it->second ); + } + + void check( const std::string &context, const mapgen_parameters ¶ms + ) const override { + on->check( context, params ); + for( const std::pair &p : cases ) { + if( !is_valid_helper( p.second ) ) { + debugmsg( "mapgen '%s' uses invalid entry '%s' in switch", + context, cata_variant( p.second ).get_string() ); + } + } + std::vector possible_values = on->all_possible_results( params ); + for( const std::string &value : possible_values ) { + if( !cases.count( value ) ) { + debugmsg( "mapgen '%s' has switch whcih does not account for potential " + "case '%s' of the switched-on value", context, value ); + } + } + } + + void check_consistent_with( + const value_source &o, const std::string &context ) const override { + if( const switch_source *other = dynamic_cast( &o ) ) { + on->check_consistent_with( *other->on, context ); + if( cases != other->cases ) { + auto dump_set = []( const std::unordered_map &s ) { + bool first = true; + std::string result = "{ "; + for( const std::pair &p : s ) { + if( first ) { + first = false; + } else { + result += ", "; + } + result += p.first; + result += ": "; + result += cata_variant( p.second ).get_string(); + } + return result; + }; + + const std::string my_list = dump_set( cases ); + const std::string other_list = dump_set( other->cases ); + debugmsg( "inconsistent switch cases for %s (%s vs %s)", + context, my_list, other_list ); + } + } else { + debugmsg( "inconsistent default types for %s", context ); + } + } + + std::vector all_possible_results( const mapgen_parameters & ) const override { + std::vector result; + for( const std::pair &p : cases ) { + result.push_back( p.second ); + } + return result; + } + }; + + mapgen_value() + : is_null_( true ) + , source_( make_shared_fast() ) + {} + + explicit mapgen_value( const std::string &s ) { + init_string( s ); + } + + explicit mapgen_value( const Id_unless_string &id ) { + init_string( id ); + } + + explicit mapgen_value( const JsonValue &jv ) { + if( jv.test_string() ) { + init_string( jv.get_string() ); + } else { + init_object( jv.get_object() ); + } + } + + explicit mapgen_value( const JsonObject &jo ) { + init_object( jo ); + } + + template + void init_string( const S &s ) { + source_ = make_shared_fast( s ); + is_null_ = is_null_helper( s ); + } + + void init_object( const JsonObject &jo ) { + if( jo.has_member( "param" ) ) { + source_ = make_shared_fast( jo ); + } else if( jo.has_member( "distribution" ) ) { + source_ = make_shared_fast( jo ); + } else if( jo.has_member( "switch" ) ) { + source_ = make_shared_fast( jo ); + } else { + jo.throw_error( + R"(Expected member "param", "distribution", or "switch" in mapgen object)" ); + } + } + + bool is_null() const { + return is_null_; + } + + void check( const std::string &context, const mapgen_parameters ¶ms ) const { + source_->check( context, params ); + } + void check_consistent_with( const mapgen_value &other, const std::string &context ) const { + source_->check_consistent_with( *other.source_, context ); + } + + Id get( const mapgendata &dat ) const { + return source_->get( dat ); + } + std::vector all_possible_results( const mapgen_parameters ¶ms ) const { + return source_->all_possible_results( params ); + } + + void deserialize( JsonIn &jsin ) { + if( jsin.test_object() ) { + *this = mapgen_value( jsin.get_object() ); + } else { + *this = mapgen_value( jsin.get_string() ); + } + } + private: + bool is_null_ = false; + shared_ptr_fast source_; +}; + +namespace io +{ + +template<> +std::string enum_to_string( mapgen_parameter_scope v ) +{ + switch( v ) { + // *INDENT-OFF* + case mapgen_parameter_scope::overmap_special: return "overmap_special"; + case mapgen_parameter_scope::omt: return "omt"; + case mapgen_parameter_scope::nest: return "nest"; + // *INDENT-ON* + case mapgen_parameter_scope::last: + break; + } + debugmsg( "unknown debug_menu::debug_menu_index %d", static_cast( v ) ); + return ""; +} + +} // namespace io + +mapgen_parameter::mapgen_parameter() = default; + +mapgen_parameter::mapgen_parameter( const mapgen_value &def, cata_variant_type type, + mapgen_parameter_scope scope ) + : scope_( scope ) + , type_( type ) + , default_( make_shared_fast>( def ) ) +{} + +void mapgen_parameter::deserialize( JsonIn &jsin ) +{ + JsonObject jo = jsin.get_object(); + optional( jo, false, "scope", scope_, mapgen_parameter_scope::overmap_special ); + jo.read( "type", type_, true ); + default_ = make_shared_fast>( jo.get_member( "default" ) ); +} + +cata_variant_type mapgen_parameter::type() const +{ + return type_; +} + +cata_variant mapgen_parameter::get( const mapgendata &md ) const +{ + return cata_variant::from_string( type_, default_->get( md ) ); +} + +std::vector mapgen_parameter::all_possible_values( + const mapgen_parameters ¶ms ) const +{ + return default_->all_possible_results( params ); +} + +void mapgen_parameter::check( const mapgen_parameters ¶ms, const std::string &context ) const +{ + default_->check( context, params ); + for( const std::string &value : all_possible_values( params ) ) { + if( !cata_variant::from_string( type_, std::string( value ) ).is_valid() ) { + debugmsg( "%s can take value %s which is not a valid value of type %s", + context, value, io::enum_to_string( type_ ) ); + } + } +} + +void mapgen_parameter::check_consistent_with( + const mapgen_parameter &other, const std::string &context ) const +{ + if( scope_ != other.scope_ ) { + debugmsg( "mismatched scope for mapgen parameters %s (%s vs %s)", + context, io::enum_to_string( scope_ ), io::enum_to_string( other.scope_ ) ); + } + if( type_ != other.type_ ) { + debugmsg( "mismatched type for mapgen parameters %s (%s vs %s)", + context, io::enum_to_string( type_ ), io::enum_to_string( other.type_ ) ); + } + default_->check_consistent_with( *other.default_, context ); +} + +auto mapgen_parameters::add_unique_parameter( + const std::string &prefix, const mapgen_value &def, cata_variant_type type, + mapgen_parameter_scope scope ) -> iterator +{ + uint64_t i = 0; + std::string candidate_name; + while( true ) { + candidate_name = string_format( "%s%d", prefix, i ); + if( map.find( candidate_name ) == map.end() ) { + break; + } + ++i; + } + + return map.emplace( candidate_name, mapgen_parameter( def, type, scope ) ).first; +} + +mapgen_parameters mapgen_parameters::params_for_scope( mapgen_parameter_scope scope ) const +{ + mapgen_parameters result; + for( const std::pair &p : map ) { + const mapgen_parameter ¶m = p.second; + if( param.scope() == scope ) { + result.map.insert( p ); + } + } + return result; +} + +mapgen_arguments mapgen_parameters::get_args( + const mapgendata &md, mapgen_parameter_scope scope ) const +{ + std::unordered_map result; + for( const std::pair &p : map ) { + const mapgen_parameter ¶m = p.second; + if( param.scope() == scope ) { + result.emplace( p.first, param.get( md ) ); + } + } + return { std::move( result ) }; +} + +void mapgen_parameters::check_and_merge( const mapgen_parameters &other, + const std::string &context, mapgen_parameter_scope up_to_scope ) +{ + for( const std::pair &p : other.map ) { + const mapgen_parameter &other_param = p.second; + if( other_param.scope() >= up_to_scope ) { + continue; + } + auto insert_result = map.insert( p ); + if( !insert_result.second ) { + const std::string &name = p.first; + const mapgen_parameter &this_param = insert_result.first->second; + this_param.check_consistent_with( + other_param, string_format( "parameter %s in %s", name, context ) ); + } + } +} + /** * This is a generic mapgen piece, the template parameter PieceType should be another specific * type of jmapgen_piece. This class contains a vector of those objects and will chose one of * it at random. */ template -class jmapgen_alternativly : public jmapgen_piece +class jmapgen_alternatively : public jmapgen_piece { public: // Note: this bypasses virtual function system, all items in this vector are of type // PieceType, they *can not* be of any other type. std::vector alternatives; - jmapgen_alternativly() = default; - void apply( mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y ) const override { + jmapgen_alternatively() = default; + mapgen_phase phase() const override { + if( alternatives.empty() ) { + return mapgen_phase::default_; + } + return alternatives[0].phase(); + } + void check( const std::string &context, const mapgen_parameters ¶ms ) const override { + if( alternatives.empty() ) { + debugmsg( "zero alternatives in jmapgen_alternatively in %s", context ); + } + for( const PieceType &piece : alternatives ) { + piece.check( context, params ); + } + } + void merge_parameters_into( mapgen_parameters ¶ms, + const std::string &outer_context ) const override { + for( const PieceType &piece : alternatives ) { + piece.merge_parameters_into( params, outer_context ); + } + } + void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y + ) const override { if( const auto chosen = random_entry_opt( alternatives ) ) { chosen->get().apply( dat, x, y ); } } - bool has_vehicle_collision( mapgendata &dat, point p ) const override { + bool has_vehicle_collision( const mapgendata &dat, const point &p ) const override { return dat.m.veh_at( tripoint( p, dat.zlevel() ) ).has_value(); } }; +template +class jmapgen_constrained : public jmapgen_piece +{ + public: + jmapgen_constrained( shared_ptr_fast und, + const std::vector> &cons ) + : underlying_piece( std::move( und ) ) + , constraints( cons ) + {} + + shared_ptr_fast underlying_piece; + std::vector> constraints; + + mapgen_phase phase() const override { + return underlying_piece->phase(); + } + void check( const std::string &context, const mapgen_parameters ¶ms ) const override { + underlying_piece->check( context, params ); + } + + void merge_parameters_into( mapgen_parameters ¶ms, const std::string &outer_context + ) const override { + underlying_piece->merge_parameters_into( params, outer_context ); + } + void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y + ) const override { + for( const mapgen_constraint &constraint : constraints ) { + Value param_value = dat.get_arg( constraint.parameter_name ); + if( param_value != constraint.value ) { + return; + } + } + underlying_piece->apply( dat, x, y ); + } +}; + /** * Places fields on the map. * "field": field type ident. @@ -865,19 +1556,27 @@ class jmapgen_alternativly : public jmapgen_piece class jmapgen_field : public jmapgen_piece { public: - field_type_id ftype; + mapgen_value ftype; int intensity; time_duration age; jmapgen_field( const JsonObject &jsi ) : - ftype( field_type_id( jsi.get_string( "field" ) ) ) + ftype( jsi.get_member( "field" ) ) , intensity( jsi.get_int( "intensity", 1 ) ) , age( time_duration::from_turns( jsi.get_int( "age", 0 ) ) ) { - if( !ftype.id() ) { - set_mapgen_defer( jsi, "field", "invalid field type" ); + } + void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y + ) const override { + field_type_id chosen_id = ftype.get( dat ); + if( chosen_id.id().is_null() ) { + return; } + dat.m.add_field( tripoint( x.get(), y.get(), dat.m.get_abs_sub().z ), chosen_id, + intensity, age ); } - void apply( mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y ) const override { - dat.m.add_field( tripoint( x.get(), y.get(), dat.m.get_abs_sub().z ), ftype, intensity, age ); + + void check( const std::string &oter_name, const mapgen_parameters ¶meters + ) const override { + ftype.check( oter_name, parameters ); } }; /** @@ -887,36 +1586,42 @@ class jmapgen_field : public jmapgen_piece class jmapgen_npc : public jmapgen_piece { public: - string_id npc_class; + mapgen_value> npc_class; bool target; - std::vector traits; + std::vector traits; jmapgen_npc( const JsonObject &jsi ) : - npc_class( jsi.get_string( "class" ) ) + npc_class( jsi.get_member( "class" ) ) , target( jsi.get_bool( "target", false ) ) { - if( !npc_class.is_valid() ) { - set_mapgen_defer( jsi, "class", "unknown npc class" ); - } if( jsi.has_string( "add_trait" ) ) { std::string new_trait = jsi.get_string( "add_trait" ); - traits.emplace_back( new_trait ); + traits.emplace_back(); + jsi.read( "add_trait", traits.back() ); } else if( jsi.has_array( "add_trait" ) ) { - for( const std::string new_trait : jsi.get_array( "add_trait" ) ) { - traits.emplace_back( new_trait ); - } + jsi.read( "add_trait", traits ); } } - void apply( mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y ) const override { - character_id npc_id = dat.m.place_npc( point( x.get(), y.get() ), npc_class ); + void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y + ) const override { + string_id chosen_id = npc_class.get( dat ); + if( chosen_id.is_null() ) { + return; + } + character_id npc_id = dat.m.place_npc( point( x.get(), y.get() ), chosen_id ); if( dat.mission() && target ) { dat.mission()->set_target_npc_id( npc_id ); } npc *p = g->find_npc( npc_id ); if( p != nullptr ) { - for( const std::string &new_trait : traits ) { - p->set_mutation( trait_id( new_trait ) ); + for( const trait_id &new_trait : traits ) { + p->set_mutation( new_trait ); } } } + + void check( const std::string &oter_name, const mapgen_parameters ¶meters + ) const override { + npc_class.check( oter_name, parameters ); + } }; /** * Place ownership area @@ -924,14 +1629,26 @@ class jmapgen_npc : public jmapgen_piece class jmapgen_faction : public jmapgen_piece { public: - faction_id id; - jmapgen_faction( const JsonObject &jsi ) { - if( jsi.has_string( "id" ) ) { - id = faction_id( jsi.get_string( "id" ) ); + mapgen_value id; + jmapgen_faction( const JsonObject &jsi ) + : id( jsi.get_member( "id" ) ) { + } + mapgen_phase phase() const override { + return mapgen_phase::faction_ownership; + } + void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y + ) const override { + faction_id chosen_id = id.get( dat ); + if( chosen_id.is_null() ) { + return; } + dat.m.apply_faction_ownership( point( x.val, y.val ), point( x.valmax, y.valmax ), + chosen_id ); } - void apply( mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y ) const override { - dat.m.apply_faction_ownership( point( x.val, y.val ), point( x.valmax, y.valmax ), id ); + + void check( const std::string &oter_name, const mapgen_parameters ¶meters + ) const override { + id.check( oter_name, parameters ); } }; /** @@ -950,8 +1667,9 @@ class jmapgen_sign : public jmapgen_piece jsi.throw_error( "jmapgen_sign: needs either signage or snippet" ); } } - void apply( mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y ) const override { - const point r{ x.get(), y.get() }; + void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y + ) const override { + const point r( x.get(), y.get() ); dat.m.furn_set( r, f_null ); dat.m.furn_set( r, furn_str_id( "f_sign" ) ); @@ -983,7 +1701,7 @@ class jmapgen_sign : public jmapgen_piece replace_name_tags( signtext ); return signtext; } - bool has_vehicle_collision( mapgendata &dat, point p ) const override { + bool has_vehicle_collision( const mapgendata &dat, const point &p ) const override { return dat.m.veh_at( tripoint( p, dat.zlevel() ) ).has_value(); } }; @@ -1004,8 +1722,9 @@ class jmapgen_graffiti : public jmapgen_piece jsi.throw_error( "jmapgen_graffiti: needs either text or snippet" ); } } - void apply( mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y ) const override { - const point r{ x.get(), y.get() }; + void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y + ) const override { + const point r( x.get(), y.get() ); std::string graffiti; @@ -1044,22 +1763,33 @@ class jmapgen_vending_machine : public jmapgen_piece { public: bool reinforced; - item_group_id item_group; + mapgen_value group_id; jmapgen_vending_machine( const JsonObject &jsi ) : - reinforced( jsi.get_bool( "reinforced", false ) ) - , item_group( jsi.get_string( "item_group", "default_vending_machine" ) ) { - if( !item_group::group_is_defined( item_group ) ) { - set_mapgen_defer( jsi, "item_group", "no such item group" ); + reinforced( jsi.get_bool( "reinforced", false ) ) { + if( jsi.has_member( "item_group" ) ) { + group_id = mapgen_value( jsi.get_member( "item_group" ) ); + } else { + group_id = mapgen_value( "default_vending_machine" ); } } - void apply( mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y ) const override { - point r{ x.get(), y.get() }; + void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y + ) const override { + const point r( x.get(), y.get() ); dat.m.furn_set( r, f_null ); - dat.m.place_vending( r, item_group, reinforced ); + item_group_id chosen_id = group_id.get( dat ); + if( chosen_id.is_null() ) { + return; + } + dat.m.place_vending( r, chosen_id, reinforced ); } - bool has_vehicle_collision( mapgendata &dat, point p ) const override { + bool has_vehicle_collision( const mapgendata &dat, const point &p ) const override { return dat.m.veh_at( tripoint( p, dat.zlevel() ) ).has_value(); } + + void check( const std::string &oter_name, const mapgen_parameters ¶meters + ) const override { + group_id.check( oter_name, parameters ); + } }; /** * Place a toilet with (dirty) water in it. @@ -1072,8 +1802,9 @@ class jmapgen_toilet : public jmapgen_piece jmapgen_toilet( const JsonObject &jsi ) : amount( jsi, "amount", 0, 0 ) { } - void apply( mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y ) const override { - const point r{ x.get(), y.get() }; + void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y + ) const override { + const point r( x.get(), y.get() ); const int charges = amount.get(); dat.m.furn_set( r, f_null ); if( charges == 0 ) { @@ -1082,7 +1813,7 @@ class jmapgen_toilet : public jmapgen_piece dat.m.place_toilet( r, charges ); } } - bool has_vehicle_collision( mapgendata &dat, point p ) const override { + bool has_vehicle_collision( const mapgendata &dat, const point &p ) const override { return dat.m.veh_at( tripoint( p, dat.zlevel() ) ).has_value(); } }; @@ -1094,32 +1825,44 @@ class jmapgen_gaspump : public jmapgen_piece { public: jmapgen_int amount; - std::string fuel; + mapgen_value fuel; jmapgen_gaspump( const JsonObject &jsi ) : amount( jsi, "amount", 0, 0 ) { - if( jsi.has_string( "fuel" ) ) { - fuel = jsi.get_string( "fuel" ); + if( jsi.has_member( "fuel" ) ) { + jsi.read( "fuel", fuel ); + } + } + void check( const std::string &oter_name, const mapgen_parameters ¶meters + ) const override { + fuel.check( oter_name, parameters ); + static const std::unordered_set valid_fuels = { + itype_id::NULL_ID(), itype_gasoline, itype_diesel, itype_jp8, itype_avgas + }; + for( const itype_id &possible_fuel : fuel.all_possible_results( parameters ) ) { // may want to not force this, if we want to support other fuels for some reason - if( fuel != "gasoline" && fuel != "diesel" ) { - jsi.throw_error( "invalid fuel", "fuel" ); + if( !valid_fuels.count( possible_fuel ) ) { + debugmsg( "invalid fuel %s in %s", possible_fuel.str(), oter_name ); } } } - void apply( mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y ) const override { - const point r{ x.get(), y.get() }; + + void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y + ) const override { + const point r( x.get(), y.get() ); int charges = amount.get(); dat.m.furn_set( r, f_null ); if( charges == 0 ) { charges = rng( 10000, 50000 ); } - if( !fuel.empty() ) { - dat.m.place_gas_pump( r, charges, fuel ); - } else { + itype_id chosen_fuel = fuel.get( dat ); + if( chosen_fuel.is_null() ) { dat.m.place_gas_pump( r, charges ); + } else { + dat.m.place_gas_pump( r, charges, chosen_fuel ); } } - bool has_vehicle_collision( mapgendata &dat, point p ) const override { + bool has_vehicle_collision( const mapgendata &dat, const point &p ) const override { return dat.m.veh_at( tripoint( p, dat.zlevel() ) ).has_value(); } }; @@ -1134,22 +1877,24 @@ class jmapgen_liquid_item : public jmapgen_piece { public: jmapgen_int amount; - itype_id liquid; + mapgen_value liquid; jmapgen_int chance; jmapgen_liquid_item( const JsonObject &jsi ) : amount( jsi, "amount", 0, 0 ) - , liquid( jsi.get_string( "liquid" ) ) + , liquid( jsi.get_member( "liquid" ) ) , chance( jsi, "chance", 1, 1 ) { - // Itemgroups apply migrations when being loaded, but we need to migrate - // individual items here. - liquid = item_controller->migrate_id( liquid ); - if( !liquid.is_valid() ) { - set_mapgen_defer( jsi, "liquid", "no such item type '" + liquid.str() + "'" ); - } } - void apply( mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y ) const override { + void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y + ) const override { if( one_in( chance.get() ) ) { - detached_ptr newliquid = item::spawn( liquid, calendar::start_of_cataclysm ); + itype_id chosen_id = liquid.get( dat ); + if( chosen_id.is_null() ) { + return; + } + // Itemgroups apply migrations when being loaded, but we need to migrate + // individual items here. + itype_id migrated = item_controller->migrate_id( chosen_id ); + detached_ptr newliquid = item::spawn( migrated, calendar::start_of_cataclysm ); if( amount.valmax > 0 ) { newliquid->charges = amount.get(); } @@ -1157,6 +1902,11 @@ class jmapgen_liquid_item : public jmapgen_piece std::move( newliquid ) ); } } + + void check( const std::string &oter_name, const mapgen_parameters ¶meters + ) const override { + liquid.check( oter_name, parameters ); + } }; /** @@ -1175,7 +1925,13 @@ class jmapgen_item_group : public jmapgen_piece group_id = item_group::load_item_group( group, "collection" ); repeat = jmapgen_int( jsi, "repeat", 1, 1 ); } - void apply( mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y ) const override { + void check( const std::string &context, const mapgen_parameters & ) const override { + if( !group_id.is_valid() ) { + debugmsg( "Invalid item_group_id \"%s\" in %s", group_id.str(), context ); + } + } + void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y + ) const override { dat.m.place_items( group_id, chance.get(), point( x.val, y.val ), point( x.valmax, y.valmax ), true, calendar::start_of_cataclysm ); } @@ -1214,7 +1970,8 @@ class jmapgen_loot : public jmapgen_piece } } - void apply( mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y ) const override { + void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y + ) const override { if( rng( 0, 99 ) < chance ) { const Item_spawn_data *const isd = &result_group; std::vector> spawn = isd->create( calendar::start_of_cataclysm ); @@ -1237,21 +1994,29 @@ class jmapgen_loot : public jmapgen_piece class jmapgen_monster_group : public jmapgen_piece { public: - mongroup_id id; + mapgen_value id; float density; jmapgen_int chance; jmapgen_monster_group( const JsonObject &jsi ) : - id( jsi.get_string( "monster" ) ) + id( jsi.get_member( "monster" ) ) , density( jsi.get_float( "density", -1.0f ) ) , chance( jsi, "chance", 1, 1 ) { - if( !id.is_valid() ) { - set_mapgen_defer( jsi, "monster", "no such monster group" ); - } } - void apply( mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y ) const override { - dat.m.place_spawns( id, chance.get(), point( x.val, y.val ), point( x.valmax, y.valmax ), + void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y + ) const override { + mongroup_id chosen_id = id.get( dat ); + if( chosen_id.is_null() ) { + return; + } + dat.m.place_spawns( chosen_id, chance.get(), point( x.val, y.val ), + point( x.valmax, y.valmax ), density == -1.0f ? dat.monster_density() : density ); } + + void check( const std::string &oter_name, const mapgen_parameters ¶meters + ) const override { + id.check( oter_name, parameters ); + } }; /** * Place spawn points for a specific monster. @@ -1268,8 +2033,8 @@ class jmapgen_monster_group : public jmapgen_piece class jmapgen_monster : public jmapgen_piece { public: - weighted_int_list ids; - mongroup_id m_id = mongroup_id::NULL_ID(); + weighted_int_list> ids; + mapgen_value m_id; jmapgen_int chance; jmapgen_int pack_size; bool one_or_none; @@ -1282,41 +2047,28 @@ class jmapgen_monster : public jmapgen_piece , one_or_none( jsi.get_bool( "one_or_none", !( jsi.has_member( "repeat" ) || jsi.has_member( "pack_size" ) ) ) ) , friendly( jsi.get_bool( "friendly", false ) ) - , name( jsi.get_string( "name", "NONE" ) ) - , target( jsi.get_bool( "target", false ) ) { - if( jsi.has_string( "group" ) ) { - m_id = mongroup_id( jsi.get_string( "group" ) ); - if( !m_id.is_valid() ) { - set_mapgen_defer( jsi, "group", "no such monster group" ); - return; - } - } else if( jsi.has_array( "monster" ) ) { - for( const JsonValue entry : jsi.get_array( "monster" ) ) { - mtype_id id; - int weight = 100; - if( entry.test_array() ) { - JsonArray inner = entry.get_array(); - id = mtype_id( inner.get_string( 0 ) ); - weight = inner.get_int( 1 ); - } else { - id = mtype_id( entry.get_string() ); - } - if( !id.is_valid() ) { - set_mapgen_defer( jsi, "monster", "no such monster" ); - return; - } - ids.add( id, weight ); - } + , name( jsi.get_string( "name", "NONE" ) ) + , target( jsi.get_bool( "target", false ) ) { + if( jsi.has_member( "group" ) ) { + jsi.read( "group", m_id ); + } else if( jsi.has_array( "monster" ) ) { + load_weighted_list( jsi.get_member( "monster" ), ids, 100 ); } else { - mtype_id id = mtype_id( jsi.get_string( "monster" ) ); - if( !id.is_valid() ) { - set_mapgen_defer( jsi, "monster", "no such monster" ); - return; - } + mapgen_value id( jsi.get_member( "monster" ) ); ids.add( id, 100 ); } } - void apply( mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y ) const override { + + void check( const std::string &oter_name, const mapgen_parameters ¶meters + ) const override { + for( const weighted_object> &id : ids ) { + id.obj.check( oter_name, parameters ); + } + m_id.check( oter_name, parameters ); + } + + void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y + ) const override { int raw_odds = chance.get(); @@ -1349,15 +2101,20 @@ class jmapgen_monster : public jmapgen_piece } } - if( m_id != mongroup_id::NULL_ID() ) { - MonsterGroupResult spawn_details = MonsterGroupManager::GetResultFromGroup( m_id ); + mongroup_id chosen_group = m_id.get( dat ); + if( !chosen_group.is_null() ) { + MonsterGroupResult spawn_details = + MonsterGroupManager::GetResultFromGroup( chosen_group ); dat.m.add_spawn( spawn_details.name, spawn_count * pack_size.get(), { x.get(), y.get(), dat.m.get_abs_sub().z }, friendly, -1, mission_id, name ); } else { - dat.m.add_spawn( *( ids.pick() ), spawn_count * pack_size.get(), - { x.get(), y.get(), dat.m.get_abs_sub().z }, - friendly, -1, mission_id, name ); + mtype_id chosen_type = ids.pick()->get( dat ); + if( !chosen_type.is_null() ) { + dat.m.add_spawn( chosen_type, spawn_count * pack_size.get(), + { x.get(), y.get(), dat.m.get_abs_sub().z }, + friendly, -1, mission_id, name ); + } } } }; @@ -1373,13 +2130,13 @@ class jmapgen_monster : public jmapgen_piece class jmapgen_vehicle : public jmapgen_piece { public: - vgroup_id type; + mapgen_value type; jmapgen_int chance; std::vector rotation; int fuel; int status; jmapgen_vehicle( const JsonObject &jsi ) : - type( jsi.get_string( "vehicle" ) ) + type( jsi.get_member( "vehicle" ) ) , chance( jsi, "chance", 1, 1 ) //, rotation( jsi.get_int( "rotation", 0 ) ) // unless there is a way for the json parser to // return a single int as a list, we have to manually check this in the constructor below @@ -1392,20 +2149,27 @@ class jmapgen_vehicle : public jmapgen_piece } else { rotation.push_back( units::from_degrees( jsi.get_int( "rotation", 0 ) ) ); } - - if( !type.is_valid() ) { - set_mapgen_defer( jsi, "vehicle", "no such vehicle type or group" ); - } } - void apply( mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y ) const override { + void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y + ) const override { if( !x_in_y( chance.get(), 100 ) ) { return; } - dat.m.add_vehicle( type, point( x.get(), y.get() ), random_entry( rotation ), fuel, status ); + vgroup_id chosen_id = type.get( dat ); + if( chosen_id.is_null() ) { + return; + } + dat.m.add_vehicle( chosen_id, point( x.get(), y.get() ), random_entry( rotation ), + fuel, status ); } - bool has_vehicle_collision( mapgendata &dat, point p ) const override { + bool has_vehicle_collision( const mapgendata &dat, const point &p ) const override { return dat.m.veh_at( tripoint( p, dat.zlevel() ) ).has_value(); } + + void check( const std::string &oter_name, const mapgen_parameters ¶meters + ) const override { + type.check( oter_name, parameters ); + } }; /** * Place a specific item. @@ -1417,31 +2181,39 @@ class jmapgen_vehicle : public jmapgen_piece class jmapgen_spawn_item : public jmapgen_piece { public: - itype_id type; + mapgen_value type; jmapgen_int amount; jmapgen_int chance; jmapgen_spawn_item( const JsonObject &jsi ) : - type( jsi.get_string( "item" ) ) + type( jsi.get_member( "item" ) ) , amount( jsi, "amount", 1, 1 ) , chance( jsi, "chance", 100, 100 ) { - // Itemgroups apply migrations when being loaded, but we need to migrate - // individual items here. - type = item_controller->migrate_id( type ); - if( !type.is_valid() ) { - set_mapgen_defer( jsi, "item", "no such item" ); - } repeat = jmapgen_int( jsi, "repeat", 1, 1 ); } - void apply( mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y ) const override { + void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y + ) const override { + itype_id chosen_id = type.get( dat ); + if( chosen_id.is_null() ) { + return; + } + // Itemgroups apply migrations when being loaded, but we need to migrate + // individual items here. + chosen_id = item_controller->migrate_id( chosen_id ); + const int c = chance.get(); // 100% chance = exactly 1 item, otherwise scale by item spawn rate. const float spawn_rate = get_option( "ITEM_SPAWNRATE" ); int spawn_count = ( c == 100 ) ? 1 : roll_remainder( c * spawn_rate / 100.0f ); for( int i = 0; i < spawn_count; i++ ) { - dat.m.spawn_item( point( x.get(), y.get() ), type, amount.get() ); + dat.m.spawn_item( point( x.get(), y.get() ), chosen_id, amount.get() ); } } + + void check( const std::string &oter_name, const mapgen_parameters ¶meters + ) const override { + type.check( oter_name, parameters ); + } }; /** * Place a trap. @@ -1450,31 +2222,42 @@ class jmapgen_spawn_item : public jmapgen_piece class jmapgen_trap : public jmapgen_piece { public: - trap_id id; - jmapgen_trap( const JsonObject &jsi ) : - id( 0 ) { - const trap_str_id sid( jsi.get_string( "trap" ) ); - if( !sid.is_valid() ) { - set_mapgen_defer( jsi, "trap", "no such trap" ); - } - id = sid.id(); + mapgen_value id; + jmapgen_trap( const JsonObject &jsi ) { + init( jsi.get_member( "trap" ) ); } - jmapgen_trap( const std::string &tid ) : - id( 0 ) { - const trap_str_id sid( tid ); - if( !sid.is_valid() ) { - throw std::runtime_error( "unknown trap type" ); + explicit jmapgen_trap( const JsonValue &tid ) { + if( tid.test_object() ) { + JsonObject jo = tid.get_object(); + if( jo.has_member( "trap" ) ) { + init( jo.get_member( "trap" ) ); + return; + } } - id = sid.id(); + init( tid ); } - void apply( mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y ) const override { + void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y + ) const override { + trap_id chosen_id = id.get( dat ); + if( chosen_id.id().is_null() ) { + return; + } const tripoint actual_loc = tripoint( x.get(), y.get(), dat.m.get_abs_sub().z ); - dat.m.trap_set( actual_loc, id ); + dat.m.trap_set( actual_loc, chosen_id ); } - bool has_vehicle_collision( mapgendata &dat, point p ) const override { + bool has_vehicle_collision( const mapgendata &dat, const point &p ) const override { return dat.m.veh_at( tripoint( p, dat.zlevel() ) ).has_value(); } + + void check( const std::string &oter_name, const mapgen_parameters ¶meters + ) const override { + id.check( oter_name, parameters ); + } + private: + void init( const JsonValue &jsi ) { + id = mapgen_value( jsi ); + } }; /** * Place a furniture. @@ -1483,15 +2266,29 @@ class jmapgen_trap : public jmapgen_piece class jmapgen_furniture : public jmapgen_piece { public: - furn_id id; - jmapgen_furniture( const JsonObject &jsi ) : jmapgen_furniture( jsi.get_string( "furn" ) ) {} - jmapgen_furniture( const std::string &fid ) : id( furn_id( fid ) ) {} - void apply( mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y ) const override { - dat.m.furn_set( point( x.get(), y.get() ), id ); + mapgen_value id; + jmapgen_furniture( const JsonObject &jsi ) : + jmapgen_furniture( jsi.get_member( "furn" ) ) {} + explicit jmapgen_furniture( const JsonValue &fid ) : id( fid ) {} + mapgen_phase phase() const override { + return mapgen_phase::furniture; + } + void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y + ) const override { + furn_id chosen_id = id.get( dat ); + if( chosen_id.id().is_null() ) { + return; + } + dat.m.furn_set( point( x.get(), y.get() ), chosen_id ); } - bool has_vehicle_collision( mapgendata &dat, point p ) const override { + bool has_vehicle_collision( const mapgendata &dat, const point &p ) const override { return dat.m.veh_at( tripoint( p, dat.zlevel() ) ).has_value(); } + + void check( const std::string &oter_name, const mapgen_parameters ¶meters + ) const override { + id.check( oter_name, parameters ); + } }; /** * Place terrain. @@ -1500,11 +2297,24 @@ class jmapgen_furniture : public jmapgen_piece class jmapgen_terrain : public jmapgen_piece { public: - ter_id id; - jmapgen_terrain( const JsonObject &jsi ) : jmapgen_terrain( jsi.get_string( "ter" ) ) {} - jmapgen_terrain( const std::string &tid ) : id( ter_id( tid ) ) {} - void apply( mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y ) const override { - dat.m.ter_set( point( x.get(), y.get() ), id ); + mapgen_value id; + jmapgen_terrain( const JsonObject &jsi ) : jmapgen_terrain( jsi.get_member( "ter" ) ) {} + explicit jmapgen_terrain( const JsonValue &tid ) : id( mapgen_value( tid ) ) {} + + bool is_nop() const override { + return id.is_null(); + } + mapgen_phase phase() const override { + return mapgen_phase::terrain; + } + + void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y + ) const override { + ter_id chosen_id = id.get( dat ); + if( chosen_id.id().is_null() ) { + return; + } + dat.m.ter_set( point( x.get(), y.get() ), chosen_id ); // Delete furniture if a wall was just placed over it. TODO: need to do anything for fluid, monsters? if( dat.m.has_flag_ter( "WALL", point( x.get(), y.get() ) ) ) { dat.m.furn_set( point( x.get(), y.get() ), f_null ); @@ -1514,9 +2324,14 @@ class jmapgen_terrain : public jmapgen_piece } } } - bool has_vehicle_collision( mapgendata &dat, point p ) const override { + bool has_vehicle_collision( const mapgendata &dat, const point &p ) const override { return dat.m.veh_at( tripoint( p, dat.zlevel() ) ).has_value(); } + + void check( const std::string &oter_name, const mapgen_parameters ¶meters + ) const override { + id.check( oter_name, parameters ); + } }; /** * Run a transformation. @@ -1525,12 +2340,22 @@ class jmapgen_terrain : public jmapgen_piece class jmapgen_ter_furn_transform: public jmapgen_piece { public: - ter_furn_transform_id id; - jmapgen_ter_furn_transform( const JsonObject &jsi ) : jmapgen_ter_furn_transform( - jsi.get_string( "transform" ) ) {} - jmapgen_ter_furn_transform( const std::string &rid ) : id( ter_furn_transform_id( rid ) ) {} - void apply( mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y ) const override { - id->transform( dat.m, tripoint( x.get(), y.get(), dat.m.get_abs_sub().z ) ); + mapgen_value id; + jmapgen_ter_furn_transform( const JsonObject &jsi ) : + id( jsi.get_member( "transform" ) ) {} + + void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y + ) const override { + ter_furn_transform_id chosen_id = id.get( dat ); + if( chosen_id.is_null() ) { + return; + } + chosen_id->transform( dat.m, tripoint( x.get(), y.get(), dat.m.get_abs_sub().z ) ); + } + + void check( const std::string &oter_name, const mapgen_parameters ¶meters + ) const override { + id.check( oter_name, parameters ); } }; /** @@ -1540,21 +2365,37 @@ class jmapgen_ter_furn_transform: public jmapgen_piece class jmapgen_make_rubble : public jmapgen_piece { public: - furn_id rubble_type = f_rubble; - ter_id floor_type = t_dirt; + mapgen_value rubble_type = mapgen_value( f_rubble ); + mapgen_value floor_type = mapgen_value( t_dirt ); bool overwrite = false; jmapgen_make_rubble( const JsonObject &jsi ) { - if( jsi.has_string( "rubble_type" ) ) { - rubble_type = furn_id( jsi.get_string( "rubble_type" ) ); + if( jsi.has_member( "rubble_type" ) ) { + rubble_type = mapgen_value( jsi.get_member( "rubble_type" ) ); } - if( jsi.has_string( "floor_type" ) ) { - floor_type = ter_id( jsi.get_string( "floor_type" ) ); + if( jsi.has_member( "floor_type" ) ) { + floor_type = mapgen_value( jsi.get_member( "floor_type" ) ); } jsi.read( "overwrite", overwrite ); } - void apply( mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y ) const override { - dat.m.make_rubble( tripoint( x.get(), y.get(), dat.m.get_abs_sub().z ), rubble_type, - floor_type, overwrite ); + void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y + ) const override { + furn_id chosen_rubble_type = rubble_type.get( dat ); + ter_id chosen_floor_type = floor_type.get( dat ); + if( chosen_rubble_type.id().is_null() ) { + return; + } + if( chosen_floor_type.id().is_null() ) { + debugmsg( "null floor type when making rubble" ); + chosen_floor_type = t_dirt; + } + dat.m.make_rubble( tripoint( x.get(), y.get(), dat.m.get_abs_sub().z ), + chosen_rubble_type, chosen_floor_type, overwrite ); + } + + void check( const std::string &oter_name, const mapgen_parameters ¶meters + ) const override { + rubble_type.check( oter_name, parameters ); + floor_type.check( oter_name, parameters ); } }; @@ -1588,7 +2429,8 @@ class jmapgen_computer : public jmapgen_piece } } } - void apply( mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y ) const override { + void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y + ) const override { const point r{ x.get(), y.get() }; dat.m.ter_set( r, t_console ); dat.m.furn_set( r, f_null ); @@ -1609,7 +2451,7 @@ class jmapgen_computer : public jmapgen_piece cpu->set_access_denied_msg( access_denied.translated() ); } } - bool has_vehicle_collision( mapgendata &dat, point p ) const override { + bool has_vehicle_collision( const mapgendata &dat, const point &p ) const override { return dat.m.veh_at( tripoint( p, dat.zlevel() ) ).has_value(); } }; @@ -1623,12 +2465,12 @@ class jmapgen_computer : public jmapgen_piece class jmapgen_sealed_item : public jmapgen_piece { public: - furn_id furniture; + mapgen_value furniture; jmapgen_int chance; std::optional item_spawner; std::optional item_group_spawner; jmapgen_sealed_item( const JsonObject &jsi ) - : furniture( jsi.get_string( "furniture" ) ) + : furniture( jsi.get_member( "furniture" ) ) , chance( jsi, "chance", 100, 100 ) { if( jsi.has_object( "item" ) ) { JsonObject item_obj = jsi.get_object( "item" ); @@ -1640,79 +2482,90 @@ class jmapgen_sealed_item : public jmapgen_piece } } - void check( const std::string &oter_name ) const override { - const furn_t &furn = furniture.obj(); - std::string summary = string_format( - "sealed_item special in json mapgen for overmap terrain %s using furniture %s", - oter_name, furn.id.str() ); - - if( !furniture.is_valid() ) { - debugmsg( "%s which is not valid furniture", summary ); - } - + void check( const std::string &oter_name, const mapgen_parameters ¶ms ) const override { + std::string short_summary = + string_format( "sealed_item special in json mapgen for %s", oter_name ); if( !item_spawner && !item_group_spawner ) { debugmsg( "%s specifies neither an item nor an item group. " "It should specify at least one.", - summary ); + short_summary ); return; } - if( furn.has_flag( "PLANT" ) ) { - // plant furniture requires exactly one seed item within it - if( item_spawner && item_group_spawner ) { - debugmsg( "%s (with flag PLANT) specifies both an item and an item group. " - "It should specify exactly one.", - summary ); + for( const furn_str_id &f : furniture.all_possible_results( params ) ) { + std::string summary = + string_format( "%s using furniture %s", short_summary, f.str() ); + + if( !f.is_valid() ) { + debugmsg( "%s which is not valid furniture", summary ); return; } - if( item_spawner ) { - int count = item_spawner->amount.get(); - if( count != 1 ) { - debugmsg( "%s (with flag PLANT) spawns %d items; it should spawn exactly " - "one.", summary, count ); - return; - } - int item_chance = item_spawner->chance.get(); - if( item_chance != 100 ) { - debugmsg( "%s (with flag PLANT) spawns an item (%s) with probability %d%%; " - "it should always spawn. You can move the \"chance\" up to the " - "sealed_item instead of the \"item\" within.", - summary, item_spawner->type, item_chance ); - return; - } - if( !item_spawner->type->seed ) { - debugmsg( "%s (with flag PLANT) spawns item type %s which is not a seed.", - summary, item_spawner->type ); - return; - } - } + const furn_t &furn = *f; - if( item_group_spawner ) { - int ig_chance = item_group_spawner->chance.get(); - if( ig_chance != 100 ) { - debugmsg( "%s (with flag PLANT) spawns item group %s with chance %d. " - "It should have chance 100. You can move the \"chance\" up to the " - "sealed_item instead of the \"items\" within.", - summary, item_group_spawner->group_id.str(), ig_chance ); + if( furn.has_flag( "PLANT" ) ) { + // plant furniture requires exactly one seed item within it + if( item_spawner && item_group_spawner ) { + debugmsg( "%s (with flag PLANT) specifies both an item and an item group. " + "It should specify exactly one.", + summary ); return; } - item_group_id group_id = item_group_spawner->group_id; - for( const itype *type : item_group::every_possible_item_from( group_id ) ) { - if( !type->seed ) { - debugmsg( "%s (with flag PLANT) spawns item group %s which can " - "spawn item %s which is not a seed.", - summary, group_id.str(), type->get_id() ); + + if( item_spawner ) { + item_spawner->check( oter_name, params ); + int count = item_spawner->amount.get(); + if( count != 1 ) { + debugmsg( "%s (with flag PLANT) spawns %d items; it should spawn " + "exactly one.", summary, count ); + return; + } + int item_chance = item_spawner->chance.get(); + if( item_chance != 100 ) { + debugmsg( "%s (with flag PLANT) spawns an item with probability %d%%; " + "it should always spawn. You can move the \"chance\" up to " + "the sealed_item instead of the \"item\" within.", + summary, item_chance ); return; } + for( const itype_id &t : + item_spawner->type.all_possible_results( params ) ) { + if( !t->seed ) { + debugmsg( "%s (with flag PLANT) spawns item type %s which is not a " + "seed.", summary, t.str() ); + return; + } + } } - /// TODO: Somehow check that the item group always produces exactly one item. + if( item_group_spawner ) { + item_group_spawner->check( oter_name, params ); + int ig_chance = item_group_spawner->chance.get(); + if( ig_chance != 100 ) { + debugmsg( "%s (with flag PLANT) spawns item group %s with chance %d. " + "It should have chance 100. You can move the \"chance\" up " + "to the sealed_item instead of the \"items\" within.", + summary, item_group_spawner->group_id.str(), ig_chance ); + return; + } + item_group_id group_id = item_group_spawner->group_id; + for( const itype *type : + item_group::every_possible_item_from( group_id ) ) { + if( !type->seed ) { + debugmsg( "%s (with flag PLANT) spawns item group %s which can " + "spawn item %s which is not a seed.", + summary, group_id.str(), type->get_id().str() ); + return; + } + } + + } } } } - void apply( mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y ) const override { + void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y + ) const override { const int c = chance.get(); // 100% chance = always generate, otherwise scale by item spawn rate. @@ -1729,9 +2582,10 @@ class jmapgen_sealed_item : public jmapgen_piece if( item_group_spawner ) { item_group_spawner->apply( dat, x, y ); } - dat.m.furn_set( point( x.get(), y.get() ), furniture ); + furn_id chosen_furn = furniture.get( dat ); + dat.m.furn_set( point( x.get(), y.get() ), chosen_furn ); } - bool has_vehicle_collision( mapgendata &dat, point p ) const override { + bool has_vehicle_collision( const mapgendata &dat, const point &p ) const override { return dat.m.veh_at( tripoint( p, dat.zlevel() ) ).has_value(); } }; @@ -1744,18 +2598,26 @@ class jmapgen_sealed_item : public jmapgen_piece class jmapgen_translate : public jmapgen_piece { public: - ter_id from; - ter_id to; - jmapgen_translate( const JsonObject &jsi ) { - if( jsi.has_string( "from" ) && jsi.has_string( "to" ) ) { - const std::string from_id = jsi.get_string( "from" ); - const std::string to_id = jsi.get_string( "to" ); - from = ter_id( from_id ); - to = ter_id( to_id ); - } + mapgen_value from; + mapgen_value to; + jmapgen_translate( const JsonObject &jsi ) + : from( jsi.get_member( "from" ) ) + , to( jsi.get_member( "to" ) ) { + } + mapgen_phase phase() const override { + return mapgen_phase::transform; } - void apply( mapgendata &dat, const jmapgen_int &/*x*/, const jmapgen_int &/*y*/ ) const override { - dat.m.translate( from, to ); + void apply( const mapgendata &dat, const jmapgen_int &/*x*/, + const jmapgen_int &/*y*/ ) const override { + ter_id chosen_from = from.get( dat ); + ter_id chosen_to = to.get( dat ); + dat.m.translate( chosen_from, chosen_to ); + } + + void check( const std::string &oter_name, const mapgen_parameters ¶meters + ) const override { + from.check( oter_name, parameters ); + to.check( oter_name, parameters ); } }; /** @@ -1764,40 +2626,32 @@ class jmapgen_translate : public jmapgen_piece class jmapgen_zone : public jmapgen_piece { public: - zone_type_id zone_type; - faction_id faction; + mapgen_value zone_type; + mapgen_value faction; std::string name; - jmapgen_zone( const JsonObject &jsi ) { - if( jsi.has_string( "faction" ) && jsi.has_string( "type" ) ) { - std::string fac_id = jsi.get_string( "faction" ); - faction = faction_id( fac_id ); - std::string zone_id = jsi.get_string( "type" ); - zone_type = zone_type_id( zone_id ); - if( jsi.has_string( "name" ) ) { - name = jsi.get_string( "name" ); - } + jmapgen_zone( const JsonObject &jsi ) + : zone_type( jsi.get_member( "type" ) ) + , faction( jsi.get_member( "faction" ) ) { + if( jsi.has_string( "name" ) ) { + name = jsi.get_string( "name" ); } } - void apply( mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y ) const override { + void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y + ) const override { + zone_type_id chosen_zone_type = zone_type.get( dat ); + faction_id chosen_faction = faction.get( dat ); zone_manager &mgr = zone_manager::get_manager(); const tripoint start = dat.m.getabs( tripoint( x.val, y.val, 0 ) ); const tripoint end = dat.m.getabs( tripoint( x.valmax, y.valmax, 0 ) ); - mgr.add( name, zone_type, faction, false, true, start, end ); + mgr.add( name, chosen_zone_type, chosen_faction, false, true, start, end ); } -}; -static void load_weighted_entries( const JsonObject &jsi, const std::string &json_key, - weighted_int_list &list ) -{ - for( const JsonValue entry : jsi.get_array( json_key ) ) { - if( entry.test_array() ) { - JsonArray inner = entry.get_array(); - list.add( inner.get_string( 0 ), inner.get_int( 1 ) ); - } else { - list.add( entry.get_string(), 100 ); + void check( const std::string &oter_name, const mapgen_parameters ¶meters + ) const override { + zone_type.check( oter_name, parameters ); + faction.check( oter_name, parameters ); } - } -} +}; /** * Calls another mapgen call inside the current one. @@ -1810,25 +2664,22 @@ class jmapgen_nested : public jmapgen_piece 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::unordered_map> neighbors; public: - neighbor_oter_check( const JsonObject &jsi ) { - for( om_direction::type dir : om_direction::all ) { - int index = static_cast( dir ); - neighbors[index] = jsi.get_tags( io::enum_to_string( dir ) ); - has_any |= !neighbors[index].empty(); - - above = jsi.get_tags( "above" ); - has_any |= !above.empty(); + explicit neighbor_oter_check( const JsonObject &jsi ) { + for( 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 &oter_name ) const { - for( const std::set &p : neighbors ) { - for( const oter_type_str_id &id : p ) { + for( const auto &p : neighbors ) { + for( const oter_type_str_id &id : p.second ) { if( !id.is_valid() ) { debugmsg( "Invalid oter_type_str_id '%s' in %s", id.str(), oter_name ); } @@ -1837,36 +2688,24 @@ class jmapgen_nested : public jmapgen_piece } bool test( const mapgendata &dat ) const { - if( !has_any ) { - return true; - } - - 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]; + for( const std::pair> &p : + neighbors ) { + const direction dir = p.first; + const cata::flat_set &allowed_neighbors = p.second; - if( allowed_neighbors.empty() ) { - continue; // no constraints on this direction, skip. - } + assert( !allowed_neighbors.empty() ); bool this_direction_matches = false; 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 ); + this_direction_matches |= + is_ot_match( allowed_neighbor.str(), dat.neighbor_at( dir ).id(), + ot_match_type::contains ); } - all_directions_match &= this_direction_matches; - } - - if( !above.empty() ) { - bool above_matches = false; - for( const oter_type_str_id &allowed_neighbor : above ) { - above_matches |= is_ot_match( allowed_neighbor.str(), dat.above().id(), ot_match_type::contains ); + if( !this_direction_matches ) { + return false; } - all_directions_match &= above_matches; } - - return all_directions_match; + return true; } }; @@ -1964,9 +2803,14 @@ class jmapgen_nested : public jmapgen_piece : 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 ); + if( jsi.has_member( "chunks" ) ) { + load_weighted_list( jsi.get_member( "chunks" ), entries, 100 ); + } + if( jsi.has_member( "else_chunks" ) ) { + load_weighted_list( jsi.get_member( "else_chunks" ), else_entries, 100 ); + } } + 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; @@ -1974,7 +2818,36 @@ class jmapgen_nested : public jmapgen_piece return else_entries; } } - void apply( mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y ) const override { + mapgen_phase phase() const override { + return mapgen_phase::nested_mapgen; + } + void merge_parameters_into( mapgen_parameters ¶ms, + const std::string &outer_context ) const override { + auto merge_from = [&]( const std::string & name ) { + if( name == "null" ) { + return; + } + const auto iter = nested_mapgen.find( name ); + if( iter == nested_mapgen.end() ) { + debugmsg( "Unknown nested mapgen function id %s", name ); + return; + } + using Obj = weighted_object>; + for( const Obj &nested : iter->second ) { + nested.obj->merge_non_nest_parameters_into( params, outer_context ); + } + }; + + for( const weighted_object &name : entries ) { + merge_from( name.obj ); + } + + for( const weighted_object &name : else_entries ) { + merge_from( name.obj ); + } + } + void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y + ) const override { 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. @@ -1996,12 +2869,12 @@ class jmapgen_nested : public jmapgen_piece ( *ptr )->nest( dat, point( x.get(), y.get() ) ); } - void check( const std::string &oter_name ) const override { + void check( const std::string &oter_name, const mapgen_parameters & ) 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 { + bool has_vehicle_collision( const mapgendata &dat, const point &p ) const override { const weighted_int_list &selected_entries = get_entries( dat ); if( selected_entries.empty() ) { @@ -2131,20 +3004,18 @@ template void load_place_mapings_string( const JsonValue &value, mapgen_palette::placing_map::mapped_type &vect ) { - if( value.test_string() ) { + if( value.test_string() || value.test_object() ) { try { - vect.push_back( make_shared_fast( value.get_string() ) ); + vect.push_back( make_shared_fast( value ) ); } catch( const std::runtime_error &err ) { // Using the json object here adds nice formatting and context information value.throw_error( err.what() ); } - } else if( value.test_object() ) { - load_place_mapings( value.get_object(), vect ); } else { for( const JsonValue entry : value.get_array() ) { if( entry.test_string() ) { try { - vect.push_back( make_shared_fast( entry.get_string() ) ); + vect.push_back( make_shared_fast( entry ) ); } catch( const std::runtime_error &err ) { // Using the json object here adds nice formatting and context information entry.throw_error( err.what() ); @@ -2157,7 +3028,7 @@ void load_place_mapings_string( const JsonValue &value, } /* This function is like load_place_mapings_string, except if the input is an array it will create an -instance of jmapgen_alternativly which will chose the mapgen piece to apply to the map randomly. +instance of jmapgen_alternatively which will chose the mapgen piece to apply to the map randomly. Use this with terrain or traps or other things that can not be applied twice to the same place. */ template @@ -2167,11 +3038,11 @@ void load_place_mapings_alternatively( const JsonValue &value, if( !value.test_array() ) { load_place_mapings_string( value, vect ); } else { - auto alter = make_shared_fast< jmapgen_alternativly >(); + auto alter = make_shared_fast< jmapgen_alternatively >(); for( const JsonValue entry : value.get_array() ) { if( entry.test_string() ) { try { - alter->alternatives.emplace_back( entry.get_string() ); + alter->alternatives.emplace_back( entry ); } catch( const std::runtime_error &err ) { // Using the json object here adds nice formatting and context information entry.throw_error( err.what() ); @@ -2187,15 +3058,12 @@ void load_place_mapings_alternatively( const JsonValue &value, } // Test if this is a string or object, and then just emplace it. - if( piece_and_count_jarr.test_string() ) { + if( piece_and_count_jarr.test_string() || piece_and_count_jarr.test_object() ) { try { - alter->alternatives.emplace_back( piece_and_count_jarr.next_string() ); + alter->alternatives.emplace_back( piece_and_count_jarr.next() ); } catch( const std::runtime_error &err ) { piece_and_count_jarr.throw_error( err.what() ); } - } else if( piece_and_count_jarr.test_object() ) { - JsonObject jsi = piece_and_count_jarr.next_object(); - alter->alternatives.emplace_back( jsi ); } else { piece_and_count_jarr.throw_error( "First entry must be a string or object." ); } @@ -2256,12 +3124,6 @@ void mapgen_palette::load_place_mapings( const JsonObject &jo, const std::string if( !jo.has_object( member_name ) ) { return; } - /* This is kind of a hack. Loading furniture/terrain from `jo` is already done in - * mapgen_palette::load_temp, continuing here would load it again and cause trouble. - */ - if( member_name == "terrain" || member_name == "furniture" ) { - return; - } for( const JsonMember member : jo.get_object( member_name ) ) { const map_key key( member ); auto &vect = format_placings[ key ]; @@ -2269,58 +3131,57 @@ void mapgen_palette::load_place_mapings( const JsonObject &jo, const std::string } } -std::map palettes; +static std::map palettes; -static bool check_furn( const furn_id &id, const std::string &context ) +template<> +const mapgen_palette &string_id::obj() const { - const furn_t &furn = id.obj(); - if( furn.has_flag( "PLANT" ) ) { - debugmsg( "json mapgen for %s specifies furniture %s, which has flag " - "PLANT. Such furniture must be specified in a \"sealed_item\" special.", - context, furn.id.str() ); - // Only report once per mapgen object, otherwise the reports are - // very repetitive - return true; + auto it = palettes.find( *this ); + if( it == palettes.end() ) { + static const mapgen_palette null_palette; + return null_palette; } - return false; + return it->second; +} + +template<> +bool string_id::is_valid() const +{ + return palettes.find( *this ) != palettes.end(); } void mapgen_palette::check() { - std::string context = "palette " + id; - for( const std::pair &p : format_furniture ) { - if( check_furn( p.second, context ) ) { - return; - } + std::string context = "palette " + id.str(); + mapgen_parameters no_parameters; + for( const std::pair ¶m : parameters.map ) { + std::string this_context = string_format( "parameter %s in %s", param.first, context ); + param.second.check( no_parameters, this_context ); } - - for( const auto &p : format_placings ) { + for( const std::pair>> &p : + format_placings ) { for( const shared_ptr_fast &j : p.second ) { - j->check( context ); + j->check( context, parameters ); } } } -mapgen_palette mapgen_palette::load_temp( const JsonObject &jo, const std::string &src ) +mapgen_palette mapgen_palette::load_temp( const JsonObject &jo, const std::string &src, + const std::string &context ) { - return load_internal( jo, src, false, true ); + return load_internal( jo, src, context, false, true ); } void mapgen_palette::load( const JsonObject &jo, const std::string &src ) { - mapgen_palette ret = load_internal( jo, src, true, false ); - if( ret.id.empty() ) { + mapgen_palette ret = load_internal( jo, src, "", true, false ); + if( ret.id.is_empty() ) { jo.throw_error( "Named palette needs an id" ); } palettes[ ret.id ] = ret; } -void mapgen_palette::reset() -{ - palettes.clear(); -} - const mapgen_palette &mapgen_palette::get( const palette_id &id ) { const auto iter = palettes.find( id ); @@ -2340,77 +3201,119 @@ void mapgen_palette::check_definitions() } } -void mapgen_palette::add( const palette_id &rh ) +void mapgen_palette::reset() +{ + palettes.clear(); +} + +void mapgen_palette::add( const mapgen_value &rh, const add_palette_context &context ) +{ + std::vector possible_values = rh.all_possible_results( *context.parameters ); + assert( !possible_values.empty() ); + if( possible_values.size() == 1 ) { + add( palette_id( possible_values.front() ), context ); + } else { + const auto param_it = + context.parameters->add_unique_parameter( + "palette_choice_", rh, cata_variant_type::palette_id, + mapgen_parameter_scope::overmap_special ); + const std::string ¶m_name = param_it->first; + add_palette_context context_with_extra_constraint( context ); + for( const std::string &value : possible_values ) { + palette_id val_id( value ); + context_with_extra_constraint.constraints.emplace_back( param_name, val_id ); + add( val_id, context_with_extra_constraint ); + context_with_extra_constraint.constraints.pop_back(); + } + } +} + +void mapgen_palette::add( const palette_id &rh, const add_palette_context &context ) { - add( get( rh ) ); + add( get( rh ), context ); } -void mapgen_palette::add( const mapgen_palette &rh ) +void mapgen_palette::add( const mapgen_palette &rh, const add_palette_context &context ) { - for( auto &placing : rh.format_placings ) { - format_placings[ placing.first ] = placing.second; + std::string actual_context = id.is_empty() ? context.context : "palette " + id.str(); + + if( !rh.id.is_empty() ) { + const std::vector &ancestors = context.ancestors; + auto loop_start = std::find( ancestors.begin(), ancestors.end(), rh.id ); + if( loop_start != ancestors.end() ) { + std::string loop_ids = enumerate_as_string( loop_start, ancestors.end(), + []( const palette_id & i ) { + return i.str(); + }, enumeration_conjunction::arrow ); + debugmsg( "loop in palette references: %s", loop_ids ); + return; + } + } + add_palette_context new_context = context; + new_context.ancestors.push_back( rh.id ); + + for( const mapgen_value &recursive_palette : rh.palettes_used ) { + add( recursive_palette, new_context ); } - for( auto &placing : rh.format_terrain ) { - format_terrain[ placing.first ] = placing.second; + for( const auto &placing : rh.format_placings ) { + const std::vector> &constraints = context.constraints; + std::vector> constrained_placings = placing.second; + if( !constraints.empty() ) { + for( shared_ptr_fast &piece : constrained_placings ) { + piece = make_shared_fast>( + std::move( piece ), constraints ); + } + } + std::vector> &these_placings = + format_placings[placing.first]; + these_placings.insert( these_placings.end(), + constrained_placings.begin(), constrained_placings.end() ); } - for( auto &placing : rh.format_furniture ) { - format_furniture[ placing.first ] = placing.second; + for( const auto &placing : rh.keys_with_terrain ) { + keys_with_terrain.insert( placing ); } + parameters.check_and_merge( rh.parameters, actual_context ); } mapgen_palette mapgen_palette::load_internal( const JsonObject &jo, const std::string &, - bool require_id, bool allow_recur ) + const std::string &context, bool require_id, bool allow_recur ) { mapgen_palette new_pal; auto &format_placings = new_pal.format_placings; - auto &format_terrain = new_pal.format_terrain; - auto &format_furniture = new_pal.format_furniture; + auto &keys_with_terrain = new_pal.keys_with_terrain; if( require_id ) { - new_pal.id = jo.get_string( "id" ); + new_pal.id = palette_id( jo.get_string( "id" ) ); } + jo.read( "parameters", new_pal.parameters.map ); + if( jo.has_array( "palettes" ) ) { + jo.read( "palettes", new_pal.palettes_used ); if( allow_recur ) { - auto pals = jo.get_string_array( "palettes" ); - for( auto &p : pals ) { - new_pal.add( p ); + // allow_recur means that it's safe to assume all the palettes have + // been defined and we can inline now. Otherwise we just leave the + // list in our palettes_used array and it will be consumed + // recursively by calls to add which add this palette. + add_palette_context add_context{ context, &new_pal.parameters }; + for( auto &p : new_pal.palettes_used ) { + new_pal.add( p, add_context ); } - } else { - jo.throw_error( "Recursive palettes are not implemented yet" ); + new_pal.palettes_used.clear(); } } // mandatory: every character in rows must have matching entry, unless fill_ter is set - // "terrain": { "a": "t_grass", "b": "t_lava" } + // "terrain": { "a": "t_grass", "b": "t_lava" }. To help enforce this we + // keep track of everything in the "terrain" object if( jo.has_member( "terrain" ) ) { for( const JsonMember member : jo.get_object( "terrain" ) ) { - const map_key key( member ); - if( member.test_string() ) { - format_terrain[key] = ter_id( member.get_string() ); - } else { - auto &vect = format_placings[ key ]; - ::load_place_mapings( member, vect ); - if( !vect.empty() ) { - // Dummy entry to signal that this terrain is actually defined, because - // the code below checks that each square on the map has a valid terrain - // defined somehow. - format_terrain[key] = t_null; - } - } + keys_with_terrain.insert( map_key( member ) ); } } - if( jo.has_object( "furniture" ) ) { - for( const JsonMember member : jo.get_object( "furniture" ) ) { - const map_key key( member ); - if( member.test_string() ) { - format_furniture[key] = furn_id( member.get_string() ); - } else { - auto &vect = format_placings[ key ]; - ::load_place_mapings( member, vect ); - } - } - } + std::string c = "palette " + new_pal.id.str(); + new_pal.load_place_mapings( jo, "terrain", format_placings ); + new_pal.load_place_mapings( jo, "furniture", format_placings ); new_pal.load_place_mapings( jo, "fields", format_placings ); new_pal.load_place_mapings( jo, "npcs", format_placings ); new_pal.load_place_mapings( jo, "signs", format_placings ); @@ -2425,8 +3328,6 @@ mapgen_palette mapgen_palette::load_internal( const JsonObject &jo, const std::s new_pal.load_place_mapings( jo, "item", format_placings ); new_pal.load_place_mapings( jo, "traps", format_placings ); new_pal.load_place_mapings( jo, "monster", format_placings ); - new_pal.load_place_mapings( jo, "furniture", format_placings ); - new_pal.load_place_mapings( jo, "terrain", format_placings ); new_pal.load_place_mapings( jo, "rubble", format_placings ); new_pal.load_place_mapings( jo, "computers", format_placings ); new_pal.load_place_mapings( jo, "sealed_item", format_placings ); @@ -2438,9 +3339,24 @@ mapgen_palette mapgen_palette::load_internal( const JsonObject &jo, const std::s new_pal.load_place_mapings( jo, "ter_furn_transforms", format_placings ); new_pal.load_place_mapings( jo, "faction_owner_character", format_placings ); + + for( mapgen_palette::placing_map::value_type &p : format_placings ) { + p.second.erase( + std::remove_if( + p.second.begin(), p.second.end(), + []( const shared_ptr_fast &placing ) { + return placing->is_nop(); + } ), p.second.end() ); + } return new_pal; } +mapgen_palette::add_palette_context::add_palette_context( + const std::string &ctx, mapgen_parameters *params ) + : context( ctx ) + , parameters( params ) +{} + bool mapgen_function_json::setup_internal( const JsonObject &jo ) { // Just to make sure no one does anything stupid @@ -2504,6 +3420,21 @@ void update_mapgen_function_json::setup() setup_common(); } +void mapgen_function_json::finalize_parameters() +{ + finalize_parameters_common(); +} + +void mapgen_function_json_nested::finalize_parameters() +{ + finalize_parameters_common(); +} + +void update_mapgen_function_json::finalize_parameters() +{ + finalize_parameters_common(); +} + /* * Parse json, pre-calculating values for stuff, then cheerfully throw json away. Faster than regular mapf, in theory */ @@ -2538,20 +3469,20 @@ bool mapgen_function_json_base::setup_common( const JsonObject &jo ) JsonArray sparray; JsonObject pjo; - format.resize( static_cast( mapgensize.x * mapgensize.y ) ); // just like mapf::basic_bind("stuff",blargle("foo", etc) ), only json input and faster when applying if( jo.has_array( "rows" ) ) { // TODO: forward correct 'src' parameter mapgen_palette palette = mapgen_palette::load_temp( jo, - mod_management::get_default_core_content_pack().str() ); - auto &format_terrain = palette.format_terrain; - auto &format_furniture = palette.format_furniture; + mod_management::get_default_core_content_pack().str(), "" ); + auto &keys_with_terrain = palette.keys_with_terrain; auto &format_placings = palette.format_placings; - if( format_terrain.empty() ) { + if( palette.keys_with_terrain.empty() ) { return false; } + parameters = palette.get_parameters(); + // mandatory: mapgensize rows of mapgensize character lines, each of which must have a // matching key in "terrain", unless fill_ter is set // "rows:" [ "aaaajustlikeinmapgen.cpp", "this.must!be!exactly.24!", "and_must_match_terrain_", .... ] @@ -2588,12 +3519,10 @@ bool mapgen_function_json_base::setup_common( const JsonObject &jo ) for( int i = m_offset.x; i < expected_dim.x; i++ ) { const point p = point( i, c ) - m_offset; const map_key key = row_keys[i]; - const auto iter_ter = format_terrain.find( key ); - const auto iter_furn = format_furniture.find( key ); + const auto iter_ter = keys_with_terrain.find( key ); const auto fpi = format_placings.find( key ); - const bool has_terrain = iter_ter != format_terrain.end(); - const bool has_furn = iter_furn != format_furniture.end(); + const bool has_terrain = iter_ter != keys_with_terrain.end(); const bool has_placing = fpi != format_placings.end(); if( !has_terrain && !fallback_terrain_exists ) { @@ -2602,7 +3531,7 @@ bool mapgen_function_json_base::setup_common( const JsonObject &jo ) "'%s' is not in 'terrain', and no 'fill_ter' is set!", c + 1, i + 1, key.str ) ); } - if( test_mode && !has_terrain && !has_furn && !has_placing && + if( test_mode && !has_terrain && !has_placing && key.str != " " && key.str != "." ) { // TODO: Once all the in-tree mods don't report this error, // it should be changed to happen in regular games (not @@ -2613,12 +3542,6 @@ bool mapgen_function_json_base::setup_common( const JsonObject &jo ) "'%s' has no terrain, furniture, or other definition", c + 1, i + 1, key.str ) ); } - if( has_terrain ) { - format[ calc_index( p ) ].ter = iter_ter->second; - } - if( has_furn ) { - format[ calc_index( p ) ].furn = iter_furn->second; - } if( has_placing ) { jmapgen_place where( p ); for( auto &what : fpi->second ) { @@ -2628,7 +3551,6 @@ bool mapgen_function_json_base::setup_common( const JsonObject &jo ) } } fallback_terrain_exists = true; - do_format = true; } // No fill_ter? No format? GTFO. @@ -2670,6 +3592,9 @@ bool mapgen_function_json_base::setup_common( const JsonObject &jo ) objects.load_objects( jo, "place_ter_furn_transforms" ); // Needs to be last as it affects other placed items objects.load_objects( jo, "faction_owner" ); + + objects.finalize(); + if( !mapgen_defer::defer ) { is_ready = true; // skip setup attempts from any additional pointers } @@ -2686,14 +3611,22 @@ void mapgen_function_json_nested::check( const std::string &oter_name ) const check_common( oter_name ); } -void mapgen_function_json_base::check_common( const std::string &oter_name ) const +static bool check_furn( const furn_id &id, const std::string &context ) { - for( const ter_furn_id &id : format ) { - if( check_furn( id.furn, "oter " + oter_name ) ) { - return; - } + const furn_t &furn = id.obj(); + if( furn.has_flag( "PLANT" ) ) { + debugmsg( "json mapgen for %s specifies furniture %s, which has flag " + "PLANT. Such furniture must be specified in a \"sealed_item\" special.", + context, furn.id.str() ); + // Only report once per mapgen object, otherwise the reports are + // very repetitive + return true; } + return false; +} +void mapgen_function_json_base::check_common( const std::string &oter_name ) const +{ for( const jmapgen_setmap &setmap : setmap_points ) { if( setmap.op != JMAPGEN_SETMAP_FURN && setmap.op != JMAPGEN_SETMAP_LINE_FURN && @@ -2706,13 +3639,30 @@ void mapgen_function_json_base::check_common( const std::string &oter_name ) con } } - objects.check( oter_name ); + objects.check( oter_name, parameters ); +} + +void jmapgen_objects::finalize() +{ + std::stable_sort( objects.begin(), objects.end(), + []( const jmapgen_obj & l, const jmapgen_obj & r ) { + return l.second->phase() < r.second->phase(); + } ); +} + +void jmapgen_objects::check( const std::string &oter_name, + const mapgen_parameters ¶meters ) const +{ + for( const jmapgen_obj &obj : objects ) { + obj.second->check( oter_name, parameters ); + } } -void jmapgen_objects::check( const std::string &oter_name ) const +void jmapgen_objects::merge_parameters_into( mapgen_parameters ¶ms, + const std::string &outer_context ) const { for( const jmapgen_obj &obj : objects ) { - obj.second->check( oter_name ); + obj.second->merge_parameters_into( params, outer_context ); } } @@ -2724,7 +3674,7 @@ void jmapgen_objects::check( const std::string &oter_name ) const * (set|line|square)_(ter|furn|trap|radiation); simple (x, y, int) or (x1,y1,x2,y2, int) functions * TODO: optimize, though gcc -O2 optimizes enough that splitting the switch has no effect */ -bool jmapgen_setmap::apply( mapgendata &dat, point offset ) const +bool jmapgen_setmap::apply( const mapgendata &dat, const point &offset ) const { if( chance != 1 && !one_in( chance ) ) { return true; @@ -2835,7 +3785,7 @@ bool jmapgen_setmap::apply( mapgendata &dat, point offset ) const return true; } -bool jmapgen_setmap::has_vehicle_collision( mapgendata &dat, point offset ) const +bool jmapgen_setmap::has_vehicle_collision( const mapgendata &dat, const point &offset ) const { const auto get = []( const jmapgen_int & v, int v_offset ) { return v.get() + v_offset; @@ -2873,44 +3823,10 @@ bool jmapgen_setmap::has_vehicle_collision( mapgendata &dat, point offset ) cons return false; } -void mapgen_function_json_base::formatted_set_incredibly_simple( map &m, point offset ) const -{ - for( int y = 0; y < mapgensize.y; y++ ) { - for( int x = 0; x < mapgensize.x; x++ ) { - point p( x, y ); - const size_t index = calc_index( p ); - const ter_furn_id &tdata = format[index]; - const point map_pos = p + offset; - if( tdata.furn != f_null ) { - if( tdata.ter != t_null ) { - m.set( map_pos, tdata.ter, tdata.furn ); - } else { - m.furn_set( map_pos, tdata.furn ); - } - } else if( tdata.ter != t_null ) { - m.ter_set( map_pos, tdata.ter ); - } - } - } -} - -bool mapgen_function_json_base::has_vehicle_collision( mapgendata &dat, point offset ) const +bool mapgen_function_json_base::has_vehicle_collision( + const mapgendata &dat, const point &offset ) const { - if( do_format ) { - for( int y = 0; y < mapgensize.y; y++ ) { - for( int x = 0; x < mapgensize.x; x++ ) { - const point p( x, y ); - const ter_furn_id &tdata = format[calc_index( p )]; - const point map_pos = p + offset; - if( ( tdata.furn != f_null || tdata.ter != t_null ) && - dat.m.veh_at( tripoint( map_pos, dat.zlevel() ) ).has_value() ) { - return true; - } - } - } - } - - for( auto &elem : setmap_points ) { + for( const jmapgen_setmap &elem : setmap_points ) { if( elem.has_vehicle_collision( dat, offset ) ) { return true; } @@ -2948,16 +3864,16 @@ void mapgen_function_json::generate( mapgendata &md ) m->rotate( ( -ter.get_rotation() + 4 ) % 4 ); } } - if( do_format ) { - formatted_set_incredibly_simple( *m, point_zero ); - } + + mapgendata md_with_params( md, get_args( md, mapgen_parameter_scope::omt ) ); + for( auto &elem : setmap_points ) { - elem.apply( md, point_zero ); + elem.apply( md_with_params, point_zero ); } - objects.apply( md, point_zero ); + objects.apply( md_with_params, point_zero ); - resolve_regional_terrain_and_furniture( md ); + resolve_regional_terrain_and_furniture( md_with_params ); m->rotate( rotation.get() ); @@ -2966,28 +3882,31 @@ void mapgen_function_json::generate( mapgendata &md ) } } -void mapgen_function_json_nested::nest( mapgendata &dat, point offset ) const +mapgen_parameters mapgen_function_json::get_mapgen_params( mapgen_parameter_scope scope ) const +{ + return parameters.params_for_scope( scope ); +} + +void mapgen_function_json_nested::nest( const mapgendata &md, const point &offset ) const { // TODO: Make rotation work for submaps, then pass this value into elem & objects apply. //int chosen_rotation = rotation.get() % 4; - if( do_format ) { - formatted_set_incredibly_simple( dat.m, offset ); - } + mapgendata md_with_params( md, get_args( md, mapgen_parameter_scope::nest ) ); - for( auto &elem : setmap_points ) { - elem.apply( dat, offset ); + for( const jmapgen_setmap &elem : setmap_points ) { + elem.apply( md_with_params, offset ); } - objects.apply( dat, offset ); + objects.apply( md_with_params, offset ); - resolve_regional_terrain_and_furniture( dat ); + resolve_regional_terrain_and_furniture( md_with_params ); } /* * Apply mapgen as per a derived-from-json recipe; in theory fast, but not very versatile */ -void jmapgen_objects::apply( mapgendata &dat ) const +void jmapgen_objects::apply( const mapgendata &dat ) const { for( auto &obj : objects ) { const auto &where = obj.first; @@ -3001,7 +3920,7 @@ void jmapgen_objects::apply( mapgendata &dat ) const } } -void jmapgen_objects::apply( mapgendata &dat, point offset ) const +void jmapgen_objects::apply( const mapgendata &dat, const point &offset ) const { if( offset == point_zero ) { // It's a bit faster @@ -3023,7 +3942,7 @@ void jmapgen_objects::apply( mapgendata &dat, point offset ) const } } -bool jmapgen_objects::has_vehicle_collision( mapgendata &dat, point offset ) const +bool jmapgen_objects::has_vehicle_collision( const mapgendata &dat, const point &offset ) const { for( auto &obj : objects ) { auto where = obj.first; @@ -3078,7 +3997,7 @@ void map::draw_map( mapgendata &dat ) const int SOUTH_EDGE = 2 * SEEY - 1; const int EAST_EDGE = 2 * SEEX - 1; -void map::draw_office_tower( mapgendata &dat ) +void map::draw_office_tower( const mapgendata &dat ) { const oter_id &terrain_type = dat.terrain_type(); const auto place_office_chairs = [&]() { @@ -4468,7 +5387,7 @@ void map::draw_lab( mapgendata &dat ) } } -void map::draw_temple( mapgendata &dat ) +void map::draw_temple( const mapgendata &dat ) { const oter_id &terrain_type = dat.terrain_type(); if( terrain_type == "temple" || terrain_type == "temple_stairs" ) { @@ -5035,7 +5954,7 @@ void map::draw_slimepit( mapgendata &dat ) } } -void map::draw_triffid( mapgendata &dat ) +void map::draw_triffid( const mapgendata &dat ) { const oter_id &terrain_type = dat.terrain_type(); if( terrain_type == "triffid_roots" ) { @@ -5183,7 +6102,7 @@ void map::draw_triffid( mapgendata &dat ) } } -void map::draw_connections( mapgendata &dat ) +void map::draw_connections( const mapgendata &dat ) { const oter_id &terrain_type = dat.terrain_type(); if( is_ot_match( "subway", terrain_type, @@ -5379,7 +6298,7 @@ void map::place_spawns( const mongroup_id &group, const int chance, } } -void map::place_gas_pump( point p, int charges, const std::string &fuel_type ) +void map::place_gas_pump( const point &p, int charges, const itype_id &fuel_type ) { detached_ptr fuel = item::spawn( fuel_type, calendar::start_of_cataclysm ); fuel->charges = charges; @@ -5387,9 +6306,9 @@ void map::place_gas_pump( point p, int charges, const std::string &fuel_type ) add_item( p, std::move( fuel ) ); } -void map::place_gas_pump( point p, int charges ) +void map::place_gas_pump( const point &p, int charges ) { - place_gas_pump( p, charges, one_in( 4 ) ? "diesel" : "gasoline" ); + place_gas_pump( p, charges, one_in( 4 ) ? itype_diesel : itype_gasoline ); } void map::place_toilet( point p, int charges ) @@ -5901,7 +6820,7 @@ bool connects_to( const oter_id &there, int dir ) } } -void science_room( map *m, point p1, point p2, int z, int rotate ) +void science_room( map *m, const point &p1, const point &p2, int z, int rotate ) { int height = p2.y - p1.y; int width = p2.x - p1.x; @@ -6501,9 +7420,11 @@ bool update_mapgen_function_json::update_map( const tripoint_abs_omt &omt_pos, p return update_map( md, offset, verify ); } -bool update_mapgen_function_json::update_map( mapgendata &md, point offset, +bool update_mapgen_function_json::update_map( const mapgendata &md, const point &offset, const bool verify ) const { + mapgendata md_with_params( md, get_args( md, mapgen_parameter_scope::omt ) ); + class rotation_guard { public: @@ -6527,21 +7448,21 @@ bool update_mapgen_function_json::update_map( mapgendata &md, point offset, const mapgendata &md; const int rotation; }; - rotation_guard rot( md ); + rotation_guard rot( md_with_params ); - for( auto &elem : setmap_points ) { - if( verify && elem.has_vehicle_collision( md, offset ) ) { + for( const jmapgen_setmap &elem : setmap_points ) { + if( verify && elem.has_vehicle_collision( md_with_params, offset ) ) { return false; } - elem.apply( md, offset ); + elem.apply( md_with_params, offset ); } - if( verify && objects.has_vehicle_collision( md, offset ) ) { + if( verify && objects.has_vehicle_collision( md_with_params, offset ) ) { return false; } - objects.apply( md, offset ); + objects.apply( md_with_params, offset ); - resolve_regional_terrain_and_furniture( md ); + resolve_regional_terrain_and_furniture( md_with_params ); return true; } @@ -6636,6 +7557,11 @@ bool run_mapgen_func( const std::string &mapgen_id, mapgendata &dat ) return oter_mapgen.generate( dat, mapgen_id ); } +mapgen_parameters get_map_special_params( const std::string &mapgen_id ) +{ + return oter_mapgen.get_map_special_params( mapgen_id ); +} + int register_mapgen_function( const std::string &key ) { if( const auto ptr = get_mapgen_cfunction( key ) ) { diff --git a/src/mapgen.h b/src/mapgen.h index 02f7912d3837..f2da70807f78 100644 --- a/src/mapgen.h +++ b/src/mapgen.h @@ -6,10 +6,16 @@ #include #include #include +#include #include #include #include "pimpl.h" +#include "cata_variant.h" +#include "coordinates.h" +#include "json.h" +#include "memory_fast.h" +#include "mapgen_parameter.h" #include "point.h" #include "regional_settings.h" #include "type_id.h" @@ -18,6 +24,7 @@ class JsonArray; class JsonMember; class JsonObject; class map; +template class mapgen_value; class mapgendata; class mission; struct json_source_location; @@ -38,8 +45,13 @@ class mapgen_function public: virtual ~mapgen_function() = default; virtual void setup() { } // throws + virtual void finalize_parameters() { } virtual void check( const std::string & /*oter_name*/ ) const { } + virtual void generate( mapgendata & ) = 0; + virtual mapgen_parameters get_mapgen_params( mapgen_parameter_scope ) const { + return {}; + } }; ///////////////////////////////////////////////////////////////////////////////// @@ -119,14 +131,35 @@ struct jmapgen_setmap { x( ix ), y( iy ), x2( ix2 ), y2( iy2 ), op( iop ), val( ival ), chance( ione_in ), repeat( irepeat ), rotation( irotation ), fuel( ifuel ), status( istatus ) {} - bool apply( mapgendata &dat, point offset ) const; + bool apply( const mapgendata &dat, const point &offset ) const; /** * checks if applying these objects to data would cause cause a collision with vehicles * on the same map **/ - bool has_vehicle_collision( mapgendata &dat, point offset ) const; + bool has_vehicle_collision( const mapgendata &dat, const point &offset ) const; +}; + +struct spawn_data { + std::map ammo; + std::vector patrol_points_rel_ms; }; +/** Mapgen pieces will be applied in order of phases. The phases are as + * follows: */ +enum class mapgen_phase { + terrain, + furniture, + default_, + nested_mapgen, + transform, + faction_ownership, +}; + +inline bool operator<( const mapgen_phase l, const mapgen_phase r ) +{ + return static_cast( l ) < static_cast( r ); +} + /** * Basic mapgen object. It is supposed to place or do something on a specific square on the map. * Inherit from this class and implement the @ref apply function. @@ -153,13 +186,24 @@ class jmapgen_piece protected: jmapgen_piece() : repeat( 1, 1 ) { } public: + virtual bool is_nop() const { + return false; + } + virtual mapgen_phase phase() const { + return mapgen_phase::default_; + } /** Sanity-check this piece */ - virtual void check( const std::string &/*oter_name*/ ) const { } + virtual void check( const std::string &/*oter_name*/, const mapgen_parameters & ) const { } + + virtual void merge_parameters_into( mapgen_parameters &, + const std::string &/*outer_context*/ ) const {} + /** Place something on the map from mapgendata &dat, at (x,y). */ - virtual void apply( mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y ) const = 0; + virtual void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y + ) const = 0; virtual ~jmapgen_piece() = default; jmapgen_int repeat; - virtual bool has_vehicle_collision( mapgendata &/*dat*/, point /*offset*/ ) const { + virtual bool has_vehicle_collision( const mapgendata &, const point &/*offset*/ ) const { return false; } }; @@ -179,8 +223,6 @@ class jmapgen_place jmapgen_int repeat; }; -using palette_id = std::string; - // Strong typedef for strings used as map/palette keys // Each key should be a UTF-8 string displayed in only one column (i.e. // utf8_width of 1) but can contain multiple Unicode code points. @@ -207,10 +249,22 @@ struct hash { }; } // namespace std +template +struct mapgen_constraint { + mapgen_constraint( const std::string &name, const T &val ) + : parameter_name( name ) + , value( val ) + {} + + std::string parameter_name; + T value; +}; + class mapgen_palette { public: palette_id id; + /** * The mapping from character (key) to a list of things that should be placed. This is * similar to objects, but it uses key to get the actual position where to place things @@ -219,8 +273,7 @@ class mapgen_palette using placing_map = std::unordered_map>>; - std::unordered_map format_terrain; - std::unordered_map format_furniture; + std::unordered_set keys_with_terrain; placing_map format_placings; template @@ -233,10 +286,15 @@ class mapgen_palette void check(); + const mapgen_parameters &get_parameters() const { + return parameters; + } + /** * Loads a palette object and returns it. Doesn't save it anywhere. */ - static mapgen_palette load_temp( const JsonObject &jo, const std::string &src ); + static mapgen_palette load_temp( const JsonObject &jo, const std::string &src, + const std::string &context ); /** * Load a palette object and adds it to the global set of palettes. * If "palette" field is specified, those palettes will be loaded recursively. @@ -252,15 +310,37 @@ class mapgen_palette static void check_definitions(); private: - static mapgen_palette load_internal( const JsonObject &jo, const std::string &src, bool require_id, - bool allow_recur ); + mapgen_parameters parameters; + + // These would ideally be mapgen_value but because they get + // transformed into parameters as an implementation detail it's easier + // to just use std::string + std::vector> palettes_used; + + static mapgen_palette load_internal( + const JsonObject &jo, const std::string &src, const std::string &context, + bool require_id, bool allow_recur ); + + struct add_palette_context { + add_palette_context( const std::string &ctx, mapgen_parameters * ); + + std::string context; + std::vector ancestors; + mapgen_parameters *parameters; + std::vector> constraints; + }; /** * Adds a palette to this one. New values take preference over the old ones. * + * The ancestors parameter is a set of ids from all the palettes + * currently being added, when this addition is triggered by the + * addition of another palette which includes rh. This allows for + * detection of loops in palette references. */ - void add( const palette_id &rh ); - void add( const mapgen_palette &rh ); + void add( const mapgen_value &rh, const add_palette_context & ); + void add( const palette_id &rh, const add_palette_context & ); + void add( const mapgen_palette &rh, const add_palette_context & ); }; struct jmapgen_objects { @@ -286,16 +366,19 @@ struct jmapgen_objects { template void load_objects( const JsonObject &jsi, const std::string &member_name ); - void check( const std::string &oter_name ) const; + void check( const std::string &oter_name, const mapgen_parameters & ) const; + void finalize(); + + void merge_parameters_into( mapgen_parameters &, const std::string &outer_context ) const; - void apply( mapgendata &dat ) const; - void apply( mapgendata &dat, point offset ) const; + void apply( const mapgendata &dat ) const; + void apply( const mapgendata &dat, const point &offset ) const; /** * checks if applying these objects to data would cause cause a collision with vehicles * on the same map **/ - bool has_vehicle_collision( mapgendata &dat, point offset ) const; + bool has_vehicle_collision( const mapgendata &dat, const point &offset ) const; private: /** @@ -311,9 +394,11 @@ struct jmapgen_objects { class mapgen_function_json_base { public: + void merge_non_nest_parameters_into( mapgen_parameters &, + const std::string &outer_context ) const; bool check_inbounds( const jmapgen_int &x, const jmapgen_int &y, const JsonObject &jso ) const; - size_t calc_index( point p ) const; - bool has_vehicle_collision( mapgendata &dat, point offset ) const; + size_t calc_index( const point &p ) const; + bool has_vehicle_collision( const mapgendata &dat, const point &offset ) const; private: pimpl jsrcloc; @@ -328,29 +413,32 @@ class mapgen_function_json_base // Returns true if the mapgen qualifies at this point already virtual bool setup_internal( const JsonObject &jo ) = 0; virtual void setup_setmap_internal() { } + void finalize_parameters_common(); void check_common( const std::string &oter_name ) const; - void formatted_set_incredibly_simple( map &m, point offset ) const; + mapgen_arguments get_args( const mapgendata &md, mapgen_parameter_scope ) const; - bool do_format; bool is_ready; point mapgensize; point m_offset; - std::vector format; point total_size; std::vector setmap_points; jmapgen_objects objects; + + mapgen_parameters parameters; }; class mapgen_function_json : public mapgen_function_json_base, public virtual mapgen_function { public: void setup() override; + void finalize_parameters() override; void check( const std::string &oter_name ) const override; void generate( mapgendata & ) override; + mapgen_parameters get_mapgen_params( mapgen_parameter_scope ) const override; mapgen_function_json( const json_source_location &jsrcloc, int w, point grid_offset, point grid_total ); ~mapgen_function_json() override = default; @@ -373,10 +461,11 @@ class update_mapgen_function_json : public mapgen_function_json_base void setup(); bool setup_update( const JsonObject &jo ); + void finalize_parameters(); void check( const std::string &oter_name ) const; bool update_map( const tripoint_abs_omt &omt_pos, point offset, mission *miss, bool verify = false ) const; - bool update_map( mapgendata &md, point offset = point_zero, + bool update_map( const mapgendata &md, const point &offset = point_zero, bool verify = false ) const; protected: @@ -388,11 +477,12 @@ class mapgen_function_json_nested : public mapgen_function_json_base { public: void setup(); + void finalize_parameters(); void check( const std::string &oter_name ) const; explicit mapgen_function_json_nested( const json_source_location &jsrcloc ); ~mapgen_function_json_nested() override = default; - void nest( mapgendata &dat, point offset ) const; + void nest( const mapgendata &md, const point &offset ) const; protected: bool setup_internal( const JsonObject &jo ) override; diff --git a/src/mapgen_functions.h b/src/mapgen_functions.h index 496dffb5fae6..2bbb800e72f4 100644 --- a/src/mapgen_functions.h +++ b/src/mapgen_functions.h @@ -13,6 +13,7 @@ class map; class mapgendata; class mission; +struct mapgen_parameters; struct point; struct tripoint; @@ -81,6 +82,7 @@ bool run_mapgen_update_func( const std::string &update_mapgen_id, mapgendata &da bool run_mapgen_func( const std::string &mapgen_id, mapgendata &dat ); std::pair, std::map> get_changed_ids_from_update( const std::string &update_mapgen_id ); +mapgen_parameters get_map_special_params( const std::string &mapgen_id ); void resolve_regional_terrain_and_furniture( const mapgendata &dat ); diff --git a/src/mapgen_parameter.h b/src/mapgen_parameter.h new file mode 100644 index 000000000000..17e9d873206a --- /dev/null +++ b/src/mapgen_parameter.h @@ -0,0 +1,71 @@ +#pragma once +#ifndef CATA_SRC_MAPGEN_PARAMETER_H +#define CATA_SRC_MAPGEN_PARAMETER_H + +#include "cata_variant.h" +#include "memory_fast.h" + +struct mapgen_arguments; +struct mapgen_parameters; +template class mapgen_value; +class mapgendata; + +enum class mapgen_parameter_scope { + // Should be ordered from most general to most specific + overmap_special, + omt, + nest, + last +}; + +template<> +struct enum_traits { + static constexpr mapgen_parameter_scope last = mapgen_parameter_scope::last; +}; + +inline bool operator<( mapgen_parameter_scope l, mapgen_parameter_scope r ) +{ + return static_cast( l ) < static_cast( r ); +} + +class mapgen_parameter +{ + public: + mapgen_parameter(); + mapgen_parameter( const mapgen_value &def, cata_variant_type, + mapgen_parameter_scope ); + + void deserialize( JsonIn & ); + + mapgen_parameter_scope scope() const { + return scope_; + } + cata_variant_type type() const; + cata_variant get( const mapgendata & ) const; + std::vector all_possible_values( const mapgen_parameters & ) const; + + void check( const mapgen_parameters &, const std::string &context ) const; + void check_consistent_with( const mapgen_parameter &, const std::string &context ) const; + private: + mapgen_parameter_scope scope_; + cata_variant_type type_; + // Using a pointer here mostly to move the definition of mapgen_value to the + // cpp file + shared_ptr_fast> default_; +}; + +struct mapgen_parameters { + std::unordered_map map; + using iterator = std::unordered_map::const_iterator; + + iterator add_unique_parameter( + const std::string &prefix, const mapgen_value &def, cata_variant_type, + mapgen_parameter_scope ); + + mapgen_parameters params_for_scope( mapgen_parameter_scope scope ) const; + mapgen_arguments get_args( const mapgendata &, mapgen_parameter_scope scope ) const; + void check_and_merge( const mapgen_parameters &, const std::string &context, + mapgen_parameter_scope up_to_scope = mapgen_parameter_scope::last ); +}; + +#endif // CATA_SRC_MAPGEN_PARAMETER_H diff --git a/src/mapgendata.cpp b/src/mapgendata.cpp index 240e1f6d3316..9f10689af562 100644 --- a/src/mapgendata.cpp +++ b/src/mapgendata.cpp @@ -6,12 +6,30 @@ #include "map.h" #include "mapdata.h" #include "omdata.h" +#include "overmap_special.h" #include "overmapbuffer.h" #include "point.h" #include "regional_settings.h" static const regional_settings dummy_regional_settings; +void mapgen_arguments::merge( const mapgen_arguments &other ) +{ + for( const std::pair &p : other.map ) { + map[p.first] = p.second; + } +} + +void mapgen_arguments::serialize( JsonOut &jo ) const +{ + jo.write( map ); +} + +void mapgen_arguments::deserialize( JsonIn &ji ) +{ + ji.read( map, true ); +} + mapgendata::mapgendata( map &mp, dummy_settings_t ) : density_( 0 ) , when_( calendar::turn ) @@ -59,6 +77,22 @@ mapgendata::mapgendata( const tripoint_abs_omt &over, map &mp, const float densi joins.emplace( rotated_dir, *join ); } } + if( std::optional *maybe_args = overmap_buffer.mapgen_args( over ) ) { + if( *maybe_args ) { + mapgen_args_ = **maybe_args; + } else { + // We are the first omt from this overmap_special to be generated, + // so now is the time to generate the arguments + if( std::optional s = overmap_buffer.overmap_special_at( over ) ) { + const overmap_special &special = **s; + *maybe_args = special.get_args( *this ); + mapgen_args_ = **maybe_args; + } else { + debugmsg( "mapgen params expected but no overmap special found for terrain %s", + terrain_type_.id().str() ); + } + } + } } mapgendata::mapgendata( const mapgendata &other, const oter_id &other_id ) : mapgendata( other ) @@ -66,6 +100,13 @@ mapgendata::mapgendata( const mapgendata &other, const oter_id &other_id ) : map terrain_type_ = other_id; } +mapgendata::mapgendata( const mapgendata &other, + const mapgen_arguments &mapgen_args ) : + mapgendata( other ) +{ + mapgen_args_.merge( mapgen_args ); +} + void mapgendata::set_dir( int dir_in, int val ) { switch( dir_in ) { @@ -137,12 +178,12 @@ int &mapgendata::dir( int dir_in ) } } -void mapgendata::square_groundcover( point p1, point p2 ) +void mapgendata::square_groundcover( const point &p1, const point &p2 ) const { m.draw_square_ter( default_groundcover, p1, p2 ); } -void mapgendata::fill_groundcover() +void mapgendata::fill_groundcover() const { m.draw_fill_background( default_groundcover ); } @@ -158,7 +199,7 @@ bool mapgendata::is_groundcover( const ter_id &iid ) const return false; } -ter_id mapgendata::groundcover() +ter_id mapgendata::groundcover() const { const ter_id *tid = default_groundcover.pick(); return tid != nullptr ? *tid : t_null; @@ -190,3 +231,34 @@ bool mapgendata::has_join( const cube_direction dir, const std::string &join_id return it != joins.end() && it->second == join_id; } +const oter_id &mapgendata::neighbor_at( direction dir ) const +{ + // TODO: De-uglify, implement proper conversion somewhere + switch( dir ) { + case direction::NORTH: + return north(); + case direction::EAST: + return east(); + case direction::SOUTH: + return south(); + case direction::WEST: + return west(); + case direction::NORTHEAST: + return neast(); + case direction::SOUTHEAST: + return seast(); + case direction::SOUTHWEST: + return swest(); + case direction::NORTHWEST: + return nwest(); + case direction::ABOVECENTER: + return above(); + case direction::BELOWCENTER: + return below(); + default: + break; + } + + debugmsg( "Neighbor not supported for direction %d", io::enum_to_string( dir ) ); + return north(); +} diff --git a/src/mapgendata.h b/src/mapgendata.h index db7b67c87c89..2f02e747ed05 100644 --- a/src/mapgendata.h +++ b/src/mapgendata.h @@ -5,8 +5,10 @@ #include #include "calendar.h" +#include "cata_variant.h" #include "coordinates.h" #include "cube_direction.h" +#include "json.h" #include "type_id.h" #include "weighted_list.h" @@ -20,6 +22,31 @@ namespace om_direction enum class type : int; } // namespace om_direction +struct mapgen_arguments { + std::unordered_map map; + + void merge( const mapgen_arguments & ); + void serialize( JsonOut & ) const; + void deserialize( JsonIn & ); +}; + +namespace mapgendata_detail +{ + +// helper to get a variant value with any variant being extractable as a string +template +inline Result extract_variant_value( const cata_variant &v ) +{ + return v.get(); +} +template<> +inline std::string extract_variant_value( const cata_variant &v ) +{ + return v.get_string(); +} + +} // namespace mapgendata_detail + /** * Contains various information regarding the individual mapgen instance * (generating a specific part of the map), used by the various mapgen @@ -43,6 +70,7 @@ class mapgendata time_point when_; ::mission *mission_; int zlevel_; + mapgen_arguments mapgen_args_; public: oter_id t_nesw[8]; @@ -89,6 +117,11 @@ class mapgendata */ mapgendata( const mapgendata &other, const oter_id &other_id ); + /** + * Creates a copy of this mapgendata, but stores new parameter values. + */ + mapgendata( const mapgendata &other, const mapgen_arguments & ); + const oter_id &terrain_type() const { return terrain_type_; } @@ -140,12 +173,32 @@ class mapgendata return t_below; } const oter_id &neighbor_at( om_direction::type dir ) const; - void fill_groundcover(); - void square_groundcover( point p1, point p2 ); - ter_id groundcover(); + const oter_id &neighbor_at( direction ) const; + void fill_groundcover() const; + void square_groundcover( const point &p1, const point &p2 ) const; + ter_id groundcover() const; bool is_groundcover( const ter_id &iid ) const; bool has_join( const cube_direction, const std::string &join_id ) const; + + template + Result get_arg( const std::string &name ) const { + auto it = mapgen_args_.map.find( name ); + if( it == mapgen_args_.map.end() ) { + debugmsg( "No such parameter \"%s\"", name ); + return Result(); + } + return mapgendata_detail::extract_variant_value( it->second ); + } + + template + Result get_arg_or( const std::string &name, const Result &fallback ) const { + auto it = mapgen_args_.map.find( name ); + if( it == mapgen_args_.map.end() ) { + return fallback; + } + return mapgendata_detail::extract_variant_value( it->second ); + } }; #endif // CATA_SRC_MAPGENDATA_H diff --git a/src/omdata.h b/src/omdata.h index ddcf3d6b78fb..3208e4b52633 100644 --- a/src/omdata.h +++ b/src/omdata.h @@ -20,6 +20,7 @@ #include "coordinates.h" #include "int_id.h" #include "om_direction.h" +#include "mapgen_parameter.h" #include "point.h" #include "string_id.h" #include "translations.h" @@ -31,6 +32,7 @@ struct MonsterGroup; using overmap_land_use_code_id = string_id; class JsonObject; +struct mapgen_arguments; static const overmap_land_use_code_id land_use_code_forest( "forest" ); static const overmap_land_use_code_id land_use_code_wetland( "wetland" ); diff --git a/src/overmap.cpp b/src/overmap.cpp index 090f1077f7b8..056f3da3d164 100644 --- a/src/overmap.cpp +++ b/src/overmap.cpp @@ -36,6 +36,7 @@ #include "map_iterator.h" #include "mapbuffer.h" #include "mapgen.h" +#include "mapgen_functions.h" #include "math_defines.h" #include "messages.h" #include "mongroup.h" @@ -566,6 +567,14 @@ void overmap_specials::finalize() } } +void overmap_specials::finalize_mapgen_parameters() +{ + for( const auto &elem : specials.get_all() ) { + // This cast is ugly, but safe. + const_cast( elem ).finalize_mapgen_parameters(); + } +} + void overmap_specials::check_consistency() { specials.check(); @@ -1201,6 +1210,17 @@ struct mutable_overmap_terrain_join { } }; +void overmap_special::finalize_mapgen_parameters() +{ + // Extract all the map_special-scoped params from the constituent terrains + // and put them here + std::string context = string_format( "overmap_special %s", id.str() ); + for( oter_str_id &t : all_terrains() ) { + std::string mapgen_id = t->get_mapgen_id(); + mapgen_params.check_and_merge( get_map_special_params( mapgen_id ), context ); + } +} + using join_map = std::unordered_map; struct z_constraints { @@ -2483,6 +2503,11 @@ std::vector overmap_special::required_locations() con return result; } +mapgen_arguments overmap_special::get_args( const mapgendata &md ) const +{ + return mapgen_params.get_args( md, mapgen_parameter_scope::overmap_special ); +} + void overmap_special::load( const JsonObject &jo, const std::string &src ) { const bool strict = is_strict_enabled( src ); @@ -2827,6 +2852,15 @@ std::string *overmap::join_used_at( const om_pos_dir &p ) return &it->second; } +std::optional *overmap::mapgen_args( const tripoint_om_omt &p ) +{ + auto it = mapgen_args_index.find( p ); + if( it == mapgen_args_index.end() ) { + return nullptr; + } + return &mapgen_arg_storage[it->second]; +} + bool &overmap::seen( const tripoint_om_omt &p ) { if( !inbounds( p ) ) { @@ -4954,6 +4988,17 @@ bool overmap::check_overmap_special_type( const overmap_special_id &id, return found_id->second == id; } +std::optional overmap::overmap_special_at( const tripoint_om_omt &p ) const +{ + auto it = overmap_special_placements.find( p ); + + if( it == overmap_special_placements.end() ) { + return std::nullopt; + } + + return it->second; +} + void overmap::polish_rivers( const overmap *north, const overmap *east, const overmap *south, const overmap *west ) { @@ -5385,8 +5430,11 @@ std::vector overmap::place_special( } } } + int args_index = mapgen_arg_storage.size(); + mapgen_arg_storage.emplace_back(); // Link grid for( const tripoint_om_omt &location : result ) { + mapgen_args_index[location] = args_index; overmap_special_placements[location] = special.id; if( grid ) { for( size_t i = 0; i < six_cardinal_directions.size(); i++ ) { diff --git a/src/overmap.h b/src/overmap.h index 996f3cbe3402..00ce96ead127 100644 --- a/src/overmap.h +++ b/src/overmap.h @@ -23,6 +23,7 @@ #include "enums.h" #include "enum_conversions.h" #include "game_constants.h" +#include "mapgendata.h" #include "memory_fast.h" #include "mongroup.h" #include "omdata.h" @@ -231,6 +232,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 & ); + std::optional *mapgen_args( const tripoint_om_omt & ); bool &seen( const tripoint_om_omt &p ); bool seen( const tripoint_om_omt &p ) const; bool &explored( const tripoint_om_omt &p ); @@ -370,6 +372,11 @@ class overmap // 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; + // Records mapgen parameters required at the overmap special level + // These are lazily evaluated; empty optional means that they have yet + // to be evaluated. + std::vector> mapgen_arg_storage; + std::unordered_map mapgen_args_index; oter_id get_default_terrain( int z ) const; @@ -458,6 +465,7 @@ class overmap const tripoint_om_omt &p ) const; bool check_overmap_special_type( const overmap_special_id &id, const tripoint_om_omt &location ) const; + std::optional overmap_special_at( const tripoint_om_omt &p ) const; void polish_rivers( const overmap *north, const overmap *east, const overmap *south, const overmap *west ); diff --git a/src/overmap_special.h b/src/overmap_special.h index 8c02f5f971de..d7c7541d5cee 100644 --- a/src/overmap_special.h +++ b/src/overmap_special.h @@ -135,6 +135,11 @@ 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; + const mapgen_parameters &get_params() const { + return mapgen_params; + } + mapgen_arguments get_args( const mapgendata & ) const; + const cata::flat_set &get_flags() const { return flags_; } @@ -170,6 +175,7 @@ class overmap_special bool was_loaded = false; void load( const JsonObject &jo, const std::string &src ); void finalize(); + void finalize_mapgen_parameters(); void check() const; std::vector connections; private: @@ -184,6 +190,7 @@ class overmap_special // These locations are the default values if ones are not specified for the individual OMTs. cata::flat_set default_locations_; + mapgen_parameters mapgen_params; std::unordered_map nested_; }; @@ -192,6 +199,7 @@ namespace overmap_specials void load( const JsonObject &jo, const std::string &src ); void finalize(); +void finalize_mapgen_parameters(); void check_consistency(); void reset(); diff --git a/src/overmap_ui.cpp b/src/overmap_ui.cpp index 48a2355d607e..7a278fb89af3 100644 --- a/src/overmap_ui.cpp +++ b/src/overmap_ui.cpp @@ -1377,6 +1377,17 @@ static void draw_om_sidebar( io::enum_to_string( dir ), *join ); } } + std::optional *args = overmap_buffer.mapgen_args( center ); + if( args ) { + if( *args ) { + for( const std::pair &arg : ( **args ).map ) { + mvwprintz( wbar, point( 1, ++lines ), c_white, "%s = %s", + arg.first, arg.second.get_string() ); + } + } else { + mvwprintz( wbar, point( 1, ++lines ), c_white, _( "args not yet set" ) ); + } + } } if( has_target ) { @@ -1418,6 +1429,7 @@ static void draw_om_sidebar( if( data.debug_editor ) { print_hint( "PLACE_TERRAIN", c_light_blue ); print_hint( "PLACE_SPECIAL", c_light_blue ); + print_hint( "SET_SPECIAL_ARGS", c_light_blue ); ++y; } @@ -1841,6 +1853,46 @@ static void place_ter_or_special( const ui_adaptor &om_ui, tripoint_abs_omt &cur } } +static void set_special_args( tripoint_abs_omt &curs ) +{ + std::optional *maybe_args = overmap_buffer.mapgen_args( curs ); + if( !maybe_args ) { + popup( _( "No overmap special args at this location." ) ); + return; + } + if( *maybe_args ) { + popup( _( "Overmap special args at this location have already been set." ) ); + return; + } + std::optional s = overmap_buffer.overmap_special_at( curs ); + if( !s ) { + popup( _( "No overmap special at this location from which to fetch parameters." ) ); + return; + } + const overmap_special &special = **s; + const mapgen_parameters ¶ms = special.get_params(); + mapgen_arguments args; + for( const std::pair &p : params.map ) { + const std::string param_name = p.first; + const mapgen_parameter ¶m = p.second; + std::vector possible_values = param.all_possible_values( params ); + uilist arg_menu; + arg_menu.title = string_format( _( "Select value for mapgen argument %s: " ), param_name ); + for( size_t i = 0; i != possible_values.size(); ++i ) { + const std::string &v = possible_values[i]; + arg_menu.addentry( i, true, 0, v ); + } + arg_menu.query(); + + if( arg_menu.ret < 0 ) { + return; + } + args.map[param_name] = + cata_variant::from_string( param.type(), std::move( possible_values[arg_menu.ret] ) ); + } + *maybe_args = args; +} + static std::vector get_overmap_path_to( const tripoint_abs_omt dest, bool driving ) { @@ -1972,6 +2024,7 @@ static tripoint_abs_omt display( const tripoint_abs_omt &orig, if( data.debug_editor ) { ictxt.register_action( "PLACE_TERRAIN" ); ictxt.register_action( "PLACE_SPECIAL" ); + ictxt.register_action( "SET_SPECIAL_ARGS" ); } ictxt.register_action( "QUIT" ); std::string action; @@ -2112,6 +2165,8 @@ static tripoint_abs_omt display( const tripoint_abs_omt &orig, } } else if( action == "PLACE_TERRAIN" || action == "PLACE_SPECIAL" ) { place_ter_or_special( ui, curs, action ); + } else if( action == "SET_SPECIAL_ARGS" ) { + set_special_args( curs ); } else if( action == "MISSIONS" ) { g->list_missions(); } diff --git a/src/overmapbuffer.cpp b/src/overmapbuffer.cpp index e0493b9559fe..0d53d23d4b78 100644 --- a/src/overmapbuffer.cpp +++ b/src/overmapbuffer.cpp @@ -678,6 +678,12 @@ std::string *overmapbuffer::join_used_at( const std::pairjoin_used_at( { om_loc.local, p.second } ); } +std::optional *overmapbuffer::mapgen_args( const tripoint_abs_omt &p ) +{ + const overmap_with_local_coords om_loc = get_om_global( p ); + return om_loc.om->mapgen_args( om_loc.local ); +} + bool overmapbuffer::reveal( const point_abs_omt ¢er, int radius, int z ) { return reveal( tripoint_abs_omt( center, z ), radius ); @@ -928,6 +934,13 @@ bool overmapbuffer::check_overmap_special_type_existing( return om_loc.om->check_overmap_special_type( id, om_loc.local ); } +std::optional overmapbuffer::overmap_special_at( + const tripoint_abs_omt &loc ) +{ + const overmap_with_local_coords om_loc = get_om_global( loc ); + return om_loc.om->overmap_special_at( om_loc.local ); +} + bool overmapbuffer::check_ot( const std::string &type, ot_match_type match_type, const tripoint_abs_omt &p ) { diff --git a/src/overmapbuffer.h b/src/overmapbuffer.h index d63ab910a39c..42aa2789d7e9 100644 --- a/src/overmapbuffer.h +++ b/src/overmapbuffer.h @@ -31,6 +31,7 @@ class overmap_special; class overmap_special_batch; class throbber_popup; class vehicle; +struct mapgen_arguments; struct mongroup; struct om_vehicle; struct radio_tower; @@ -191,6 +192,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 & ); + std::optional *mapgen_args( const tripoint_abs_omt & ); /** * Uses global overmap terrain coordinates. */ @@ -564,6 +566,7 @@ class overmapbuffer bool check_ot( const std::string &otype, ot_match_type match_type, const tripoint_abs_omt &p ); bool check_overmap_special_type( const overmap_special_id &id, const tripoint_abs_omt &loc ); + std::optional overmap_special_at( const tripoint_abs_omt & ); /** * These versions of the check_* methods will only check existing overmaps, and diff --git a/src/savegame.cpp b/src/savegame.cpp index b374524a506e..dcf6eb8ae911 100644 --- a/src/savegame.cpp +++ b/src/savegame.cpp @@ -683,6 +683,14 @@ void overmap::unserialize( std::istream &fin, const std::string &file_path ) for( const std::pair &p : flat_index ) { joins_used.insert( p ); } + } else if( name == "mapgen_arg_storage" ) { + jsin.read( mapgen_arg_storage, true ); + } else if( name == "mapgen_arg_index" ) { + std::vector> flat_index; + jsin.read( flat_index, true ); + for( const std::pair &p : flat_index ) { + mapgen_args_index.emplace( p.first, p.second ); + } } else { jsin.skip_value(); } @@ -1103,6 +1111,17 @@ void overmap::serialize( std::ostream &fout ) const std::vector> flattened_joins_used( joins_used.begin(), joins_used.end() ); json.member( "joins_used", flattened_joins_used ); + json.member( "mapgen_arg_storage", mapgen_arg_storage ); + fout << std::endl; + json.member( "mapgen_arg_index" ); + json.start_array(); + for( const std::pair &p : mapgen_args_index ) { + json.start_array(); + json.write( p.first ); + json.write( p.second ); + json.end_array(); + } + json.end_array(); fout << std::endl; json.end_object(); diff --git a/src/string_id.h b/src/string_id.h index 01b3fd8e693d..f13bac82e2d6 100644 --- a/src/string_id.h +++ b/src/string_id.h @@ -343,6 +343,11 @@ class string_id return !is_null(); } + friend std::ostream &operator<<( std::ostream &os, const string_id &s ) { + os << s.str(); + return os; + } + private: // generic_factory version that corresponds to the _cid mutable int64_t _version = INVALID_VERSION; diff --git a/src/string_id_null_ids.cpp b/src/string_id_null_ids.cpp index f272078e8bd4..4bd9362c6cb5 100644 --- a/src/string_id_null_ids.cpp +++ b/src/string_id_null_ids.cpp @@ -22,6 +22,7 @@ MAKE_NULL_ID( map_extra, "" ) MAKE_NULL_ID( Skill, "none" ) MAKE_NULL_ID( SkillDisplayType, "none" ) MAKE_NULL_ID( npc_class, "NC_NONE" ) +MAKE_NULL_ID( npc_template, "null" ) MAKE_NULL_ID( faction, "NULL" ) MAKE_NULL_ID( ammunition_type, "NULL" ) MAKE_NULL_ID( vpart_info, "null" ) @@ -29,10 +30,13 @@ MAKE_NULL_ID( emit, "null" ) MAKE_NULL_ID( anatomy, "null_anatomy" ) MAKE_NULL_ID( martialart, "style_none" ) MAKE_NULL_ID( recipe, "null" ) +MAKE_NULL_ID( ter_furn_transform, "null" ) MAKE_NULL_ID( translation, "null" ) MAKE_NULL_ID( Item_group, "" ) MAKE_NULL_ID( morale_type_data, "" ) MAKE_NULL_ID( json_trait_flag, "null" ) +MAKE_NULL_ID( VehicleGroup, "null" ) +MAKE_NULL_ID( zone_type, "null" ) #define MAKE_NULL_ID2( type, ... ) \ struct type; \ diff --git a/src/to_string_id.h b/src/to_string_id.h new file mode 100644 index 000000000000..963ad2a40373 --- /dev/null +++ b/src/to_string_id.h @@ -0,0 +1,24 @@ +#pragma once +#ifndef CATA_SRC_TO_STRING_ID_H +#define CATA_SRC_TO_STRING_ID_H + +template +class string_id; + +template +class int_id; + +template +struct to_string_id { + using type = Id; +}; + +template +struct to_string_id> { + using type = string_id; +}; + +template +using to_string_id_t = typename to_string_id::type; + +#endif // CATA_SRC_TO_STRING_ID_H diff --git a/src/type_id.h b/src/type_id.h index cc373c40e205..b05ca6a309da 100644 --- a/src/type_id.h +++ b/src/type_id.h @@ -91,6 +91,9 @@ using matype_id = string_id; class ma_technique; using matec_id = string_id; +class mapgen_palette; +using palette_id = string_id; + class material_type; using material_id = string_id; @@ -116,6 +119,9 @@ using mtype_id = string_id; class npc_class; using npc_class_id = string_id; +class npc_template; +using npc_template_id = string_id; + class faction; using faction_id = string_id; diff --git a/src/weighted_list.h b/src/weighted_list.h index f704c4574445..2fa74f7dd762 100644 --- a/src/weighted_list.h +++ b/src/weighted_list.h @@ -2,9 +2,13 @@ #ifndef CATA_SRC_WEIGHTED_LIST_H #define CATA_SRC_WEIGHTED_LIST_H +#include "json.h" +#include "rng.h" + #include #include #include +#include #include template struct weighted_object { @@ -12,6 +16,10 @@ template struct weighted_object { T obj; W weight; + + friend bool operator==( const weighted_object &l, const weighted_object &r ) { + return l.obj == r.obj && l.weight == r.weight; + } }; namespace weighted_list_detail @@ -173,7 +181,23 @@ template struct weighted_list { return objects.empty(); } - void precalc(); + std::string to_debug_string() const { + std::ostringstream os; + os << "[ "; + for( const weighted_object &o : objects ) { + os << o.obj << ":" << o.weight << ", "; + } + os << "]"; + return os.str(); + } + + friend bool operator==( const weighted_list &l, const weighted_list &r ) { + return l.objects == r.objects; + } + + friend bool operator!=( const weighted_list &l, const weighted_list &r ) { + return !( l == r ); + } protected: W total_weight; @@ -248,4 +272,20 @@ template struct weighted_float_list : public weighted_list +void load_weighted_list( const JsonValue &jsv, weighted_list &list, W default_weight ) +{ + for( const JsonValue entry : jsv.get_array() ) { + if( entry.test_array() ) { + std::pair p; + entry.read( p, true ); + list.add( p.first, p.second ); + } else { + T val; + entry.read( val ); + list.add( val, default_weight ); + } + } +} + #endif // CATA_SRC_WEIGHTED_LIST_H diff --git a/tests/cata_variant_test.cpp b/tests/cata_variant_test.cpp index 25ed85231255..c6a66e74138f 100644 --- a/tests/cata_variant_test.cpp +++ b/tests/cata_variant_test.cpp @@ -6,6 +6,7 @@ #include "cata_variant.h" #include "character_id.h" +#include "mutation.h" #include "enum_conversions.h" #include "item.h" #include "json.h" @@ -94,3 +95,34 @@ TEST_CASE( "variant_deserialization", "[variant]" ) v.deserialize( jsin ); CHECK( v == cata_variant( mtype_id( "zombie" ) ) ); } + +TEST_CASE( "variant_from_string" ) +{ + cata_variant v = cata_variant::from_string( cata_variant_type::mtype_id, "mon_zombie" ); + CHECK( v == cata_variant( mtype_id( "mon_zombie" ) ) ); +} + +TEST_CASE( "variant_type_for", "[variant]" ) +{ + CHECK( cata_variant_type_for() == cata_variant_type::bool_ ); + CHECK( cata_variant_type_for() == cata_variant_type::int_ ); + CHECK( cata_variant_type_for() == cata_variant_type::skill_id ); + CHECK( cata_variant_type_for() == cata_variant_type::trait_id ); + CHECK( cata_variant_type_for() == cata_variant_type::ter_id ); +} + +TEST_CASE( "variant_is_valid", "[variant]" ) +{ + // A string_id + CHECK( cata_variant( mtype_id( "mon_zombie" ) ).is_valid() ); + CHECK_FALSE( cata_variant( mtype_id( "This is not a valid id" ) ).is_valid() ); + + // An int_id + CHECK( cata_variant( ter_id( "t_grass" ) ).is_valid() ); + CHECK_FALSE( cata_variant::from_string( cata_variant_type::ter_id, "invalid id" ).is_valid() ); + + // An enum + CHECK( cata_variant( mutagen_technique::consumed_purifier ).is_valid() ); + CHECK_FALSE( cata_variant::from_string( + cata_variant_type::mutagen_technique, "invalid enum" ).is_valid() ); +} diff --git a/tests/json_test.cpp b/tests/json_test.cpp index 930669bfcbe7..3cc38b9d7105 100644 --- a/tests/json_test.cpp +++ b/tests/json_test.cpp @@ -461,3 +461,23 @@ TEST_CASE( "jsonin_get_string", "[json]" ) R"( ar")" "\n" ), R"("foo\nbar")", 5 ); } + +TEST_CASE( "serialize_optional", "[json]" ) +{ + SECTION( "simple_empty_optional" ) { + std::optional o; + test_serialization( o, "null" ); + } + SECTION( "optional_of_int" ) { + std::optional o( 7 ); + test_serialization( o, "7" ); + } + SECTION( "vector_of_empty_optional" ) { + std::vector> v( 3 ); + test_serialization( v, "[null,null,null]" ); + } + SECTION( "vector_of_optional_of_int" ) { + std::vector> v{ { 1 }, { 2 }, { 3 } }; + test_serialization( v, "[1,2,3]" ); + } +} diff --git a/tests/map_test.cpp b/tests/map_test.cpp index baf6daf2418c..cba624eaa16e 100644 --- a/tests/map_test.cpp +++ b/tests/map_test.cpp @@ -91,12 +91,6 @@ static std::ostream &operator<<( std::ostream &os, const ter_id &tid ) return os; } -static std::ostream &operator<<( std::ostream &os, const ter_str_id &tid ) -{ - os << tid.c_str(); - return os; -} - TEST_CASE( "bash_through_roof_can_destroy_multiple_times" ) { clear_all_state(); diff --git a/tools/json_tools/cddatags.py b/tools/json_tools/cddatags.py index 32e86cf6a6da..9d94c75d453f 100755 --- a/tools/json_tools/cddatags.py +++ b/tools/json_tools/cddatags.py @@ -1,4 +1,10 @@ #!/usr/bin/env python3 +"""Update a tags file with locations of the definitions of CDDA json entities. + +If you already have a tags file with some data in, this will only +replace tags in json files, not e.g. cpp files, so it should be safe +to use after running e.g. ctags. +""" import argparse import json @@ -10,26 +16,48 @@ JSON_DIR = os.path.join(TOP_DIR, "data") TAGS_FILE = os.path.join(TOP_DIR, "tags") -def make_tags_line(id_key, id, filename): - pattern = f'/"{id_key}": "{id}"/' - return f"{id}\t{filename}\t{pattern}".encode('utf-8') +def cdda_style_json(v): + if type(v) == str: + return json.dumps(v) + elif type(v) == list: + return f'[ {", ".join(cdda_style_json(e) for e in v)} ]' + else: + raise RuntimeError('Unexpected type') + + +def make_tags_line(id_key, id, full_id, filename): + length_limit = 120 + pattern = f'/"{id_key}": {cdda_style_json(full_id)}/' + if len(pattern) > length_limit - 5: + pattern = f'/"{id}"/' + return '\t'.join((id, filename, pattern)).encode('utf-8') + def is_json_tag_line(line): return b'.json\t' in line -def main(args): - parser = argparse.ArgumentParser(description= - """\ -Update a tags file with locations of the definitions of CDDA json entities. -If you already have a tags file with some data in, this will only replace tags -in json files, not e.g. cpp files, so it should be safe to use after running -e.g. ctags.""") +def main(args): + parser = argparse.ArgumentParser(description=__doc__) parser.parse_args(args) definitions = [] + # JSON keys to generate tags for + id_keys = ( + 'id', 'abstract', 'ident', + 'nested_mapgen_id', 'om_terrain', 'update_mapgen_id', 'result') + + def add_definition(id_key, id, full_id, relative_path): + if not id: + return + if type(id) == str: + definitions.append((id_key, id, full_id, relative_path)) + elif type(id) == list: + for i in id: + add_definition(id_key, i, full_id, relative_path) + for dirpath, dirnames, filenames in os.walk(JSON_DIR): for filename in filenames: if filename.endswith('.json'): @@ -51,12 +79,12 @@ def main(args): "expected a list." % filename) continue + # Check each object in json_data for taggable keys for obj in json_data: - for id_key in ('id', 'abstract', 'ident', 'nested_mapgen_id'): + for id_key in id_keys: if id_key in obj: id = obj[id_key] - if type(id) == str and id: - definitions.append((id_key, id, relative_path)) + add_definition(id_key, id, id, relative_path) json_tags_lines = [make_tags_line(*d) for d in definitions] existing_tags_lines = []