diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c1443daf5d5d..049cc922f6de9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -213,7 +213,13 @@ IF(MSVC) endif() ELSE() SET(CATA_WARNINGS - "-Werror -Wall -Wextra -Woverloaded-virtual -Wpedantic -Wmissing-declarations") + "-Werror -Wall -Wextra \ + -Wmissing-declarations \ + -Wold-style-cast \ + -Woverloaded-virtual \ + -Wpedantic") + # Compact the whitespace in the warning string + string(REGEX REPLACE "[\t ]+" " " CATA_WARNINGS "${CATA_WARNINGS}") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CATA_WARNINGS} -std=c++14") SET(CMAKE_CXX_FLAGS_DEBUG "-Og -g") ENDIF() diff --git a/Makefile b/Makefile index 1995384b5936d..a2b335ea34ade 100644 --- a/Makefile +++ b/Makefile @@ -83,7 +83,12 @@ # PROFILE is for use with gprof or a similar program -- don't bother generally. # RELEASE_FLAGS is flags for release builds. RELEASE_FLAGS = -WARNINGS = -Werror -Wall -Wextra -Woverloaded-virtual -Wpedantic -Wmissing-declarations +WARNINGS = \ + -Werror -Wall -Wextra \ + -Wmissing-declarations \ + -Wold-style-cast \ + -Woverloaded-virtual \ + -Wpedantic # Uncomment below to disable warnings #WARNINGS = -w DEBUGSYMS = -g diff --git a/data/json/bionics.json b/data/json/bionics.json index fda2f009d3171..76c2b2ce07648 100644 --- a/data/json/bionics.json +++ b/data/json/bionics.json @@ -37,7 +37,7 @@ "flags": [ "BIONIC_TOGGLED", "BIONIC_SLEEP_FRIENDLY" ], "act_cost": 1, "react_cost": 1, - "time": 100 + "time": 600 }, { "id": "bio_ankles", @@ -216,7 +216,7 @@ ], "act_cost": 1, "react_cost": 1, - "time": 4, + "time": 24, "flags": [ "BIONIC_TOGGLED" ] }, { @@ -418,7 +418,7 @@ "occupied_bodyparts": [ [ "EYES", 1 ] ], "act_cost": 1, "react_cost": 1, - "time": 25, + "time": 150, "flags": [ "BIONIC_TOGGLED" ] }, { @@ -428,7 +428,7 @@ "description": "A small LED display just beneath your dermal layer can be illuminated on command to show patterns of your choice at dim or bright luminosity. It doesn't provide enough light to comfortably read or craft, but it can suffice if you have nothing else.", "act_cost": 1, "react_cost": 1, - "time": 50, + "time": 300, "flags": [ "BIONIC_TOGGLED" ] }, { @@ -462,7 +462,7 @@ "occupied_bodyparts": [ [ "FOOT_L", 3 ], [ "FOOT_R", 3 ] ], "flags": [ "BIONIC_TOGGLED" ], "act_cost": 5, - "react_cost": 5, + "react_cost": 1, "time": 1 }, { @@ -525,7 +525,7 @@ "occupied_bodyparts": [ [ "EYES", 1 ] ], "flags": [ "BIONIC_TOGGLED" ], "act_cost": 5, - "react_cost": 5, + "react_cost": 1, "time": 1 }, { @@ -571,7 +571,7 @@ "flags": [ "BIONIC_TOGGLED", "BIONIC_SLEEP_FRIENDLY", "BIONIC_NPC_USABLE" ], "act_cost": 10, "react_cost": 1, - "time": 600 + "time": 3600 }, { "id": "bio_lighter", @@ -615,7 +615,7 @@ "flags": [ "BIONIC_TOGGLED", "BIONIC_SLEEP_FRIENDLY" ], "act_cost": 10, "react_cost": 1, - "time": 100 + "time": 600 }, { "id": "bio_metabolics", @@ -661,7 +661,7 @@ "flags": [ "BIONIC_TOGGLED" ], "act_cost": 1, "react_cost": 1, - "time": 10 + "time": 60 }, { "id": "bio_noise", @@ -707,7 +707,7 @@ "flags": [ "BIONIC_TOGGLED", "BIONIC_NPC_USABLE" ], "act_cost": 1, "react_cost": 1, - "time": 2 + "time": 12 }, { "id": "bio_plutdump", @@ -841,7 +841,7 @@ "occupied_bodyparts": [ [ "HEAD", 2 ] ], "flags": [ "BIONIC_TOGGLED" ], "react_cost": 1, - "time": 4 + "time": 24 }, { "id": "bio_resonator", @@ -860,7 +860,7 @@ "flags": [ "BIONIC_TOGGLED" ], "act_cost": 1, "react_cost": 1, - "time": 10 + "time": 60 }, { "id": "bio_scent_vision", @@ -871,7 +871,7 @@ "flags": [ "BIONIC_TOGGLED" ], "act_cost": 1, "react_cost": 1, - "time": 1 + "time": 6 }, { "id": "bio_shakes", diff --git a/data/json/effects.json b/data/json/effects.json index 745b6f2eebe8e..c6ad9f986d391 100644 --- a/data/json/effects.json +++ b/data/json/effects.json @@ -1123,7 +1123,14 @@ "per_mod": [ -1 ], "stamina_min": [ -2 ] }, - "scaling_mods": { "speed_mod": [ 30 ], "str_mod": [ 5 ], "dex_mod": [ 4 ], "int_mod": [ -7 ], "per_mod": [ 2 ], "stamina_min": [ 4 ] } + "scaling_mods": { + "speed_mod": [ 20 ], + "str_mod": [ 1.5 ], + "dex_mod": [ 1.5 ], + "int_mod": [ -2 ], + "per_mod": [ 0.5 ], + "stamina_min": [ 4 ] + } }, { "type": "effect_type", @@ -1164,11 +1171,11 @@ "stamina_min": [ -4 ] }, "scaling_mods": { - "speed_mod": [ 30 ], - "str_mod": [ 4 ], - "dex_mod": [ 4 ], - "int_mod": [ 3 ], - "per_mod": [ 5 ], + "speed_mod": [ 15 ], + "str_mod": [ 1.5 ], + "dex_mod": [ 1 ], + "int_mod": [ 0.5 ], + "per_mod": [ 1 ], "fatigue_min": [ 1 ], "stamina_min": [ 8 ] } @@ -1180,8 +1187,7 @@ "desc": [ "You had a puff or two.", "You smoked too much." ], "max_intensity": 2, "int_dur_factor": 600, - "base_mods": { "int_mod": [ 1 ], "per_mod": [ 1 ] }, - "scaling_mods": { "str_mod": [ -1 ], "dex_mod": [ -1 ], "int_mod": [ -1 ], "per_mod": [ -1 ], "vomit_chance": [ 500 ] } + "scaling_mods": { "vomit_chance": [ 500 ] } }, { "type": "effect_type", @@ -1200,7 +1206,7 @@ "id": "weed_high", "apply_message": "You feel lightheaded.", "miss_messages": [ [ "That critter's jumping around like a jitterbug! It needs to mellow out.", 1 ] ], - "base_mods": { "str_mod": [ -1 ], "dex_mod": [ -1 ], "per_mod": [ -1 ] } + "base_mods": { "per_mod": [ -1 ] } }, { "type": "effect_type", @@ -1225,15 +1231,8 @@ "apply_message": "You feel lightheaded.", "int_dur_factor": 1000, "miss_messages": [ [ "You feel woozy.", 1 ] ], - "base_mods": { "str_mod": [ 1 ], "vomit_chance": [ -43 ], "sleep_chance": [ -1003 ], "sleep_min": [ 2500 ], "sleep_max": [ 3500 ] }, - "scaling_mods": { - "str_mod": [ -0.67 ], - "per_mod": [ -1 ], - "dex_mod": [ -1 ], - "int_mod": [ -1.42 ], - "vomit_chance": [ 21 ], - "sleep_chance": [ 501 ] - } + "base_mods": { "vomit_chance": [ -43 ], "sleep_chance": [ -1003 ], "sleep_min": [ 2500 ], "sleep_max": [ 3500 ] }, + "scaling_mods": { "per_mod": [ -0.5 ], "dex_mod": [ -0.5 ], "int_mod": [ -0.75 ], "vomit_chance": [ 21 ], "sleep_chance": [ 501 ] } }, { "type": "effect_type", @@ -1623,7 +1622,7 @@ "desc": [ "You're very jittery and pumped up, probably from some stimulants." ], "max_intensity": 1000, "int_dur_factor": 1, - "scaling_mods": { "speed_mod": [ 0.33 ], "dex_mod": [ 0.1 ], "int_mod": [ 0.16 ], "per_mod": [ 0.14 ] } + "scaling_mods": { "speed_mod": [ 0.08 ], "per_mod": [ 0.04 ] } }, { "type": "effect_type", @@ -1632,20 +1631,9 @@ "desc": [ "You are under the influence of depressants, and in a bit of a daze." ], "max_intensity": 1000, "int_dur_factor": 1, - "scaling_mods": { "speed_mod": [ -0.25 ], "dex_mod": [ -0.1 ], "int_mod": [ -0.16 ], "per_mod": [ -0.14 ] }, + "scaling_mods": { "speed_mod": [ -0.1 ], "dex_mod": [ -0.05 ] }, "miss_messages": [ [ "You feel woozy.", 1 ] ] }, - { - "type": "effect_type", - "id": "stim_overdose", - "name": [ "Stimulant Overdose" ], - "desc": [ "You can't sit still as you feel your heart pounding out of your chest. You probably overdosed on those stims." ], - "max_intensity": 1000, - "int_dur_factor": 1, - "base_mods": { "dex_mod": [ -1.88 ], "int_mod": [ -1.1 ], "per_mod": [ -1.25 ] }, - "scaling_mods": { "speed_mod": [ -0.25 ], "dex_mod": [ -0.125 ], "int_mod": [ -0.0833 ], "per_mod": [ -0.071 ] }, - "miss_messages": [ [ "You shake with the excess stimulation.", 1 ] ] - }, { "type": "effect_type", "id": "weak_antibiotic_visible", diff --git a/data/json/field_type.json b/data/json/field_type.json new file mode 100644 index 0000000000000..591d0880ba5fb --- /dev/null +++ b/data/json/field_type.json @@ -0,0 +1,632 @@ +[ + { + "id": "fd_null", + "type": "field_type", + "legacy_enum_id": 0, + "intensity_levels": [ { "name": "nothing" } ] + }, + { + "id": "fd_blood", + "type": "field_type", + "legacy_enum_id": 1, + "intensity_levels": [ { "name": "blood splatter", "color": "red" }, { "name": "blood stain" }, { "name": "puddle of blood" } ], + "half_life": "2 days", + "phase": "liquid", + "accelerated_decay": true, + "is_draw_field": true + }, + { + "id": "fd_bile", + "type": "field_type", + "legacy_enum_id": 2, + "intensity_levels": [ { "name": "bile splatter", "color": "pink" }, { "name": "bile stain" }, { "name": "puddle of bile" } ], + "half_life": "1 days", + "phase": "liquid", + "accelerated_decay": true, + "is_draw_field": true + }, + { + "id": "fd_gibs_flesh", + "type": "field_type", + "legacy_enum_id": 3, + "intensity_levels": [ + { "name": "scraps of flesh", "sym": "~", "color": "brown" }, + { "name": "bloody meat chunks", "color": "light_red" }, + { "name": "heap of gore", "color": "red" } + ], + "half_life": "2 days", + "phase": "solid", + "accelerated_decay": true, + "is_draw_field": true + }, + { + "id": "fd_gibs_veggy", + "type": "field_type", + "legacy_enum_id": 4, + "intensity_levels": [ + { "name": "shredded leaves and twigs", "sym": "~", "color": "light_green" }, + { "name": "shattered branches and leaves" }, + { "name": "broken vegetation tangle", "color": "green" } + ], + "half_life": "2 days", + "phase": "solid", + "accelerated_decay": true, + "is_draw_field": true + }, + { + "id": "fd_web", + "type": "field_type", + "legacy_enum_id": 5, + "intensity_levels": [ { "name": "cowebs", "sym": "}" }, { "name": "webs" }, { "name": "thick webs", "transparent": false } ], + "priority": 2, + "phase": "solid", + "do_item": false, + "is_draw_field": true + }, + { + "id": "fd_slime", + "type": "field_type", + "legacy_enum_id": 6, + "intensity_levels": [ + { "name": "slime trail", "color": "light_green" }, + { "name": "slime stain" }, + { "name": "uddle of slime", "color": "green" } + ], + "half_life": "1 days", + "phase": "liquid", + "accelerated_decay": true, + "is_draw_field": true + }, + { + "id": "fd_acid", + "type": "field_type", + "legacy_enum_id": 7, + "intensity_levels": [ + { "name": "acit splatter", "sym": "5", "color": "light_green", "dangerous": true }, + { "name": "acid streak" }, + { "name": "pool of acid", "color": "green" } + ], + "priority": 2, + "half_life": "2 minutes", + "phase": "liquid", + "is_draw_field": true + }, + { + "id": "fd_sap", + "type": "field_type", + "legacy_enum_id": 8, + "intensity_levels": [ + { "name": "sap splatter", "sym": "5", "color": "yellow", "dangerous": true }, + { "name": "glob of sap" }, + { "name": "pool of sap", "color": "brown" } + ], + "priority": 2, + "half_life": "2 minutes", + "phase": "liquid", + "is_draw_field": true + }, + { + "id": "fd_sludge", + "type": "field_type", + "legacy_enum_id": 9, + "intensity_levels": [ + { "name": "thin sludge trail", "sym": "5", "color": "light_gray", "dangerous": true }, + { "name": "sludge trail", "color": "dark_gray" }, + { "name": "thick sludge trail" } + ], + "priority": 2, + "half_life": "6 hours", + "phase": "liquid", + "is_draw_field": true + }, + { + "id": "fd_fire", + "type": "field_type", + "legacy_enum_id": 10, + "intensity_levels": [ + { "name": "small fire", "sym": "4", "color": "yellow", "dangerous": true }, + { "name": "fire", "color": "light_red" }, + { "name": "raging fire", "color": "red" } + ], + "priority": 4, + "half_life": "30 minutes", + "phase": "plasma", + "do_item": false, + "is_draw_field": true + }, + { + "id": "fd_rubble", + "type": "field_type", + "legacy_enum_id": 11, + "intensity_levels": [ { "name": "legacy_rubble", "sym": "#", "color": "dark_gray" } ], + "half_life": "1 turns", + "phase": "solid", + "do_item": false, + "is_draw_field": true + }, + { + "id": "fd_smoke", + "type": "field_type", + "legacy_enum_id": 12, + "intensity_levels": [ + { "name": "thin smoke", "sym": "8", "dangerous": true }, + { "name": "smoke", "color": "light_gray", "transparent": false }, + { "name": "thick smoke", "color": "dark_gray" } + ], + "priority": 8, + "half_life": "2 minutes", + "phase": "gas", + "accelerated_decay": true, + "do_item": false, + "is_draw_field": true + }, + { + "id": "fd_toxic_gas", + "type": "field_type", + "legacy_enum_id": 13, + "intensity_levels": [ + { "name": "hazy cloud", "sym": "8", "dangerous": true }, + { "name": "toxic gas", "color": "light_green", "transparent": false }, + { "name": "thick toxic gas", "color": "green" } + ], + "priority": 8, + "half_life": "10 minutes", + "phase": "gas", + "do_item": false, + "is_draw_field": true + }, + { + "id": "fd_tear_gas", + "type": "field_type", + "legacy_enum_id": 14, + "intensity_levels": [ + { "name": "hazy cloud", "sym": "8", "dangerous": true }, + { "name": "tear gas", "color": "light_green", "transparent": false }, + { "name": "thick tear gas", "color": "green" } + ], + "priority": 8, + "half_life": "5 minutes", + "phase": "gas", + "do_item": false, + "is_draw_field": true + }, + { + "id": "fd_nuke_gas", + "type": "field_type", + "legacy_enum_id": 15, + "intensity_levels": [ + { "name": "hazy cloud", "sym": "8", "dangerous": true }, + { "name": "radioactive gas", "color": "light_green" }, + { "name": "thick radioactive gas", "color": "green", "transparent": false } + ], + "priority": 8, + "half_life": "100 minutes", + "phase": "gas", + "do_item": false, + "is_draw_field": true + }, + { + "id": "fd_gas_vent", + "type": "field_type", + "legacy_enum_id": 16, + "intensity_levels": [ { "name": "gas vent" } ], + "phase": "gas", + "do_item": false, + "is_draw_field": true + }, + { + "id": "fd_fire_vent", + "type": "field_type", + "legacy_enum_id": 17, + "intensity_levels": [ { "name": "fire vent" } ], + "phase": "gas", + "do_item": false, + "is_draw_field": true + }, + { + "id": "fd_flame_burst", + "type": "field_type", + "legacy_enum_id": 18, + "intensity_levels": [ { "name": "fire", "sym": "5", "color": "red", "dangerous": true } ], + "priority": 4, + "phase": "gas", + "do_item": false, + "is_draw_field": true + }, + { + "id": "fd_electricity", + "type": "field_type", + "legacy_enum_id": 19, + "intensity_levels": [ + { "name": "sparks", "sym": "9", "dangerous": true }, + { "name": "electric crackle", "color": "cyan" }, + { "name": "electric cloud", "color": "blue" } + ], + "priority": 4, + "half_life": "2 turns", + "phase": "plasma", + "do_item": false, + "is_draw_field": true + }, + { + "id": "fd_fatigue", + "type": "field_type", + "legacy_enum_id": 20, + "intensity_levels": [ + { "name": "odd ripple", "color": "light_gray", "sym": "*", "dangerous": true }, + { "name": "swirling air", "color": "dark_gray" }, + { "name": "tear in reality", "color": "magenta", "transparent": false } + ], + "priority": 8, + "do_item": false, + "is_draw_field": true + }, + { + "id": "fd_push_items", + "type": "field_type", + "legacy_enum_id": 21, + "intensity_levels": [ { "name": "push items", "sym": "&" } ], + "priority": -1, + "do_item": false, + "is_draw_field": true + }, + { + "id": "fd_shock_vent", + "type": "field_type", + "legacy_enum_id": 22, + "intensity_levels": [ { "name": "shock vent", "sym": "&" } ], + "priority": -1, + "phase": "plasma", + "do_item": false, + "is_draw_field": true + }, + { + "id": "fd_acid_vent", + "type": "field_type", + "legacy_enum_id": 23, + "intensity_levels": [ { "name": "acid vent", "sym": "&" } ], + "priority": -1, + "phase": "liquid", + "do_item": false, + "is_draw_field": true + }, + { + "id": "fd_plasma", + "type": "field_type", + "legacy_enum_id": 24, + "intensity_levels": [ + { "name": "faint plasma", "color": "magenta", "sym": "9" }, + { "name": "glowing plasma", "color": "pink" }, + { "name": "glaring plasma", "color": "white" } + ], + "priority": 4, + "half_life": "2 turns", + "phase": "plasma", + "do_item": false, + "is_draw_field": true + }, + { + "id": "fd_laser", + "type": "field_type", + "legacy_enum_id": 25, + "intensity_levels": [ + { "name": "faint glimmer", "color": "blue", "sym": "9" }, + { "name": "beam of light", "color": "light_blue" }, + { "name": "intense beam of light", "color": "white" } + ], + "priority": 4, + "half_life": "1 turns", + "phase": "plasma", + "do_item": false, + "is_draw_field": true + }, + { + "id": "fd_spotlight", + "type": "field_type", + "legacy_enum_id": 26, + "intensity_levels": [ { "name": "spotlight", "sym": "&" } ], + "priority": 1, + "half_life": "1 turns", + "is_draw_field": true + }, + { + "id": "fd_dazzling", + "type": "field_type", + "legacy_enum_id": 27, + "intensity_levels": [ { "name": "dazzling", "color": "light_red_yellow", "sym": "#" } ], + "priority": 4, + "half_life": "1 turns", + "phase": "plasma", + "do_item": false, + "is_draw_field": true + }, + { + "id": "fd_blood_veggy", + "type": "field_type", + "legacy_enum_id": 28, + "intensity_levels": [ + { "name": "plant sap splatter", "color": "light_green" }, + { "name": "plant sap stain" }, + { "name": "puddle of resin" } + ], + "half_life": "2 days", + "phase": "liquid", + "accelerated_decay": true, + "is_draw_field": true + }, + { + "id": "fd_blood_insect", + "type": "field_type", + "legacy_enum_id": 29, + "intensity_levels": [ { "name": "bug blood splatter", "color": "green" }, { "name": "bug blood stain" }, { "name": "puddle of bug blood" } ], + "half_life": "2 days", + "phase": "liquid", + "accelerated_decay": true, + "is_draw_field": true + }, + { + "id": "fd_blood_invertebrate", + "type": "field_type", + "legacy_enum_id": 30, + "intensity_levels": [ + { "name": "hemolymphd splatter", "color": "light_gray" }, + { "name": "hemolymph stain" }, + { "name": "puddle of hemolymph" } + ], + "half_life": "2 days", + "phase": "liquid", + "accelerated_decay": true, + "is_draw_field": true + }, + { + "id": "fd_gibs_insect", + "type": "field_type", + "legacy_enum_id": 31, + "intensity_levels": [ + { "name": "shards of chitin", "color": "light_green", "sym": "~" }, + { "name": "shattered bug leg", "color": "green" }, + { "name": "torn insect organs", "color": "yellow" } + ], + "half_life": "2 days", + "phase": "solid", + "accelerated_decay": true, + "is_draw_field": true + }, + { + "id": "fd_gibs_invertebrate", + "type": "field_type", + "legacy_enum_id": 32, + "intensity_levels": [ + { "name": "gooey scraps", "color": "light_gray", "sym": "~" }, + { "name": "icky mess" }, + { "name": "heap of squishy gore", "color": "dark_gray" } + ], + "half_life": "2 days", + "phase": "solid", + "accelerated_decay": true, + "is_draw_field": true + }, + { + "id": "fd_cigsmoke", + "type": "field_type", + "legacy_enum_id": 33, + "intensity_levels": [ + { "name": "swirl of tobacco smoke" }, + { "name": "tobacco smoke", "color": "light_gray" }, + { "name": "thick tobacco smoke", "color": "dark_gray" } + ], + "priority": 8, + "half_life": "35 minutes", + "phase": "gas", + "accelerated_decay": true, + "is_draw_field": true + }, + { + "id": "fd_weedsmoke", + "type": "field_type", + "legacy_enum_id": 34, + "intensity_levels": [ + { "name": "swirl of pot smoke" }, + { "name": "pot smoke", "color": "light_gray" }, + { "name": "thick pot smoke", "color": "dark_gray" } + ], + "priority": 8, + "half_life": "325 turns", + "phase": "gas", + "accelerated_decay": true, + "is_draw_field": true + }, + { + "id": "fd_cracksmoke", + "type": "field_type", + "legacy_enum_id": 35, + "intensity_levels": [ + { "name": "swirl of crack smoke" }, + { "name": "crack smoke", "color": "light_gray" }, + { "name": "thick crack smoke", "color": "dark_gray" } + ], + "priority": 8, + "half_life": "225 turns", + "phase": "gas", + "accelerated_decay": true, + "is_draw_field": true + }, + { + "id": "fd_methsmoke", + "type": "field_type", + "legacy_enum_id": 36, + "intensity_levels": [ + { "name": "swirl of meth smoke" }, + { "name": "meth smoke", "color": "light_gray" }, + { "name": "thick meth smoke", "color": "dark_gray" } + ], + "priority": 8, + "half_life": "275 turns", + "phase": "gas", + "accelerated_decay": true, + "is_draw_field": true + }, + { + "id": "fd_bees", + "type": "field_type", + "legacy_enum_id": 37, + "intensity_levels": [ + { "name": "some bees", "sym": "8", "dangerous": true }, + { "name": "swarm of bees", "color": "light_gray" }, + { "name": "angry swarm of bees", "color": "dark_gray" } + ], + "priority": 8, + "half_life": "100 minutes", + "do_item": false, + "is_draw_field": true + }, + { + "id": "fd_incendiary", + "type": "field_type", + "legacy_enum_id": 38, + "intensity_levels": [ + { "name": "smoke", "sym": "8", "dangerous": true }, + { "name": "airborne incendiary", "color": "light_red" }, + { "name": "airborne incendiary", "color": "light_red_red", "transparent": false } + ], + "priority": 8, + "half_life": "50 minutes", + "phase": "gas", + "do_item": false, + "is_draw_field": true + }, + { + "id": "fd_relax_gas", + "type": "field_type", + "legacy_enum_id": 39, + "intensity_levels": [ + { "name": "hazy cloud", "sym": ".", "dangerous": true }, + { "name": "sedative gas", "color": "pink" }, + { "name": "relaxation gas", "color": "cyan" } + ], + "priority": 8, + "half_life": "50 minutes", + "phase": "gas", + "do_item": false, + "is_draw_field": true + }, + { + "id": "fd_fungal_haze", + "type": "field_type", + "legacy_enum_id": 40, + "intensity_levels": [ + { "name": "hazy cloud", "sym": ".", "dangerous": true }, + { "name": "fungal haze", "color": "cyan" }, + { "name": "thick fungal haze", "transparent": false } + ], + "priority": 8, + "half_life": "4 minutes", + "phase": "gas", + "do_item": false, + "is_draw_field": true + }, + { + "id": "fd_cold_air1", + "type": "field_type", + "legacy_enum_id": 41, + "intensity_levels": [ { "name": "cold air 1", "sym": "&" }, { "color": "blue" }, { "color": "cyan" } ], + "priority": -1, + "half_life": "50 minutes", + "phase": "gas", + "is_draw_field": true + }, + { + "id": "fd_cold_air2", + "type": "field_type", + "legacy_enum_id": 42, + "intensity_levels": [ { "name": "cold air 2", "sym": "&" }, { "color": "blue" }, { "color": "cyan" } ], + "priority": -1, + "half_life": "50 minutes", + "phase": "gas", + "is_draw_field": true + }, + { + "id": "fd_cold_air3", + "type": "field_type", + "legacy_enum_id": 43, + "intensity_levels": [ { "name": "cold air 3", "sym": "&" }, { "color": "blue" }, { "color": "cyan" } ], + "priority": -1, + "half_life": "50 minutes", + "phase": "gas", + "is_draw_field": true + }, + { + "id": "fd_cold_air4", + "type": "field_type", + "legacy_enum_id": 44, + "intensity_levels": [ { "name": "cold air 4", "sym": "&" }, { "color": "blue" }, { "color": "cyan" } ], + "priority": -1, + "half_life": "50 minutes", + "phase": "gas", + "is_draw_field": true + }, + { + "id": "fd_hot_air1", + "type": "field_type", + "legacy_enum_id": 45, + "intensity_levels": [ { "name": "hot air 1", "sym": "&" }, { "color": "yellow" }, { "color": "red" } ], + "priority": -1, + "half_life": "50 minutes", + "phase": "gas", + "is_draw_field": true + }, + { + "id": "fd_hot_air2", + "type": "field_type", + "legacy_enum_id": 46, + "intensity_levels": [ { "name": "hot air 2", "sym": "&" }, { "color": "yellow" }, { "color": "red" } ], + "priority": -1, + "half_life": "50 minutes", + "phase": "gas", + "is_draw_field": true + }, + { + "id": "fd_hot_air3", + "type": "field_type", + "legacy_enum_id": 47, + "intensity_levels": [ { "name": "hot air 3", "sym": "&" }, { "color": "yellow" }, { "color": "red" } ], + "priority": -1, + "half_life": "50 minutes", + "phase": "gas", + "is_draw_field": true + }, + { + "id": "fd_hot_air4", + "type": "field_type", + "legacy_enum_id": 48, + "intensity_levels": [ { "name": "hot air 4", "sym": "&" }, { "color": "yellow" }, { "color": "red" } ], + "priority": -1, + "half_life": "50 minutes", + "phase": "gas", + "is_draw_field": true + }, + { + "id": "fd_fungicidal_gas", + "type": "field_type", + "legacy_enum_id": 49, + "intensity_levels": [ + { "name": "hazy cloud", "sym": "8", "dangerous": true }, + { "name": "fungicidal gas", "color": "light_gray" }, + { "name": "thick fungicidal gas", "color": "dark_gray", "transparent": false } + ], + "priority": 8, + "half_life": "90 minutes", + "phase": "gas", + "do_item": false, + "is_draw_field": true + }, + { + "id": "fd_smoke_vent", + "type": "field_type", + "legacy_enum_id": 50, + "intensity_levels": [ { "name": "smoke vent", "dangerous": true } ], + "phase": "gas", + "do_item": false, + "is_draw_field": true + } +] diff --git a/data/json/furniture.json b/data/json/furniture.json index 6a5dea786f998..ca178f716ebcf 100644 --- a/data/json/furniture.json +++ b/data/json/furniture.json @@ -6553,5 +6553,24 @@ ] }, "flags": [ "TRANSPARENT", "FLAMMABLE_ASH", "ORGANIC" ] + }, + { + "id": "f_bitts", + "type": "furniture", + "name": "bitts", + "description": "Paired vertical iron posts mounted on a wharf, pier or quay. They are used to secure mooring lines, ropes, hawsers, or cables.", + "symbol": "B", + "color": [ "light_gray" ], + "move_cost_mod": 2, + "coverage": 30, + "required_str": 0, + "bash": { + "str_min": 80, + "str_max": 200, + "sound": "metal screeching!", + "sound_fail": "clang!", + "items": [ { "item": "steel_chunk", "count": [ 5, 10 ] } ] + }, + "flags": [ "TRANSPARENT", "MOUNTABLE", "SHORT" ] } ] diff --git a/data/json/itemgroups/guns.json b/data/json/itemgroups/guns.json index 35af9f82c0ac7..ef8af4d741dbe 100644 --- a/data/json/itemgroups/guns.json +++ b/data/json/itemgroups/guns.json @@ -7,6 +7,7 @@ { "item": "glock_17", "prob": 35, "charges-min": 0, "charges-max": 15 }, { "item": "glock_19", "prob": 50, "charges-min": 0, "charges-max": 15 }, { "item": "glock_22", "prob": 35, "charges-min": 0, "charges-max": 15 }, + { "item": "glock_31", "prob": 15, "charges-min": 0, "charges-max": 15 }, { "item": "m1911", "prob": 50, "charges-min": 0, "charges-max": 7 }, { "item": "m9", "prob": 30, "charges-min": 0, "charges-max": 15 }, { "item": "ruger_lcr_38", "prob": 10, "charges-min": 0, "charges-max": 5 }, diff --git a/data/json/items/ammo/357mag.json b/data/json/items/ammo/357mag.json new file mode 100644 index 0000000000000..585e3ec19f3df --- /dev/null +++ b/data/json/items/ammo/357mag.json @@ -0,0 +1,32 @@ +[ + { + "id": "357mag_fmj", + "type": "AMMO", + "name": ".357 magnum FMJ", + "description": "Jacketed .357 magnum ammunition. The .357 magnum round is derived from the earlier .38 special, with a marginally longer case and generating greater pressure.", + "weight": 8, + "volume": 1, + "price": 4000, + "material": [ "brass", "powder" ], + "symbol": "=", + "color": "light_gray", + "count": 50, + "stack_size": 50, + "ammo_type": "357mag", + "casing": "357mag_casing", + "range": 16, + "damage": 28, + "pierce": 4, + "dispersion": 30, + "recoil": 700, + "effects": [ "COOKOFF" ] + }, + { + "id": "357mag_jhp", + "copy-from": "357mag_fmj", + "type": "AMMO", + "name": ".357 magnum JHP", + "description": "Jacketed hollow point .357 magnum ammunition. The .357 magnum round is derived from the earlier .38 special, with a marginally longer case and generating greater pressure.", + "relative": { "damage": 4, "pierce": -2 } + } +] diff --git a/data/json/items/ammo/357.json b/data/json/items/ammo/357sig.json similarity index 100% rename from data/json/items/ammo/357.json rename to data/json/items/ammo/357sig.json diff --git a/data/json/items/ammo/380.json b/data/json/items/ammo/380.json index 22813f0c04f9a..13b96e5aa1617 100644 --- a/data/json/items/ammo/380.json +++ b/data/json/items/ammo/380.json @@ -12,7 +12,7 @@ "color": "yellow", "count": 50, "stack_size": 50, - "ammo_type": [ "380", "9mm", "9x18" ], + "ammo_type": "380", "casing": "380_casing", "range": 13, "damage": 16, diff --git a/data/json/items/ammo/45.json b/data/json/items/ammo/45.json index 57c38fada23ed..82f0746544f55 100644 --- a/data/json/items/ammo/45.json +++ b/data/json/items/ammo/45.json @@ -20,7 +20,7 @@ "color": "yellow", "count": 30, "stack_size": 30, - "ammo_type": [ "45", "460" ], + "ammo_type": "45", "casing": "45_casing", "range": 16, "damage": 30, diff --git a/data/json/items/ammo/signal_flare.json b/data/json/items/ammo/signal_flare.json index 00d58b8ed6acd..f547927d1ba81 100644 --- a/data/json/items/ammo/signal_flare.json +++ b/data/json/items/ammo/signal_flare.json @@ -12,7 +12,7 @@ "color": "red", "count": 4, "stack_size": 20, - "ammo_type": [ "signal_flare", "shot" ], + "ammo_type": "signal_flare", "casing": "shot_hull", "range": 30, "damage": 16, diff --git a/data/json/items/ammo_types.json b/data/json/items/ammo_types.json index 59e167428431c..e9913d9948be9 100644 --- a/data/json/items/ammo_types.json +++ b/data/json/items/ammo_types.json @@ -89,6 +89,12 @@ "name": ".357 SIG", "default": "357sig_jhp" }, + { + "type": "ammunition_type", + "id": "357mag", + "name": ".357 magnum", + "default": "357mag_jhp" + }, { "type": "ammunition_type", "id": "9x18", @@ -129,7 +135,7 @@ "type": "ammunition_type", "id": "460", "name": ".460", - "default": "45_acp" + "default": "460_rowland" }, { "type": "ammunition_type", diff --git a/data/json/items/armor/bandolier.json b/data/json/items/armor/bandolier.json index fe683e9035df6..11c1b60d84216 100644 --- a/data/json/items/armor/bandolier.json +++ b/data/json/items/armor/bandolier.json @@ -60,7 +60,7 @@ "coverage": 10, "encumbrance": 5, "material_thickness": 1, - "use_action": { "type": "bandolier", "capacity": 25, "ammo": [ "shot", "20x66mm" ], "draw_cost": 25 }, + "use_action": { "type": "bandolier", "capacity": 25, "ammo": [ "shot", "20x66mm", "shotcanister" ], "draw_cost": 25 }, "flags": [ "WATER_FRIENDLY", "WAIST", "OVERSIZE" ] }, { @@ -78,7 +78,7 @@ "coverage": 12, "encumbrance": 15, "material_thickness": 1, - "use_action": { "type": "bandolier", "capacity": 50, "ammo": [ "shot", "20x66mm" ], "draw_cost": 35 }, + "use_action": { "type": "bandolier", "capacity": 50, "ammo": [ "shot", "20x66mm", "shotcanister" ], "draw_cost": 35 }, "flags": [ "WATER_FRIENDLY", "OVERSIZE", "BELTED" ] }, { diff --git a/data/json/items/fuel.json b/data/json/items/fuel.json index 3c8afc464481b..fe1282d0fd9da 100644 --- a/data/json/items/fuel.json +++ b/data/json/items/fuel.json @@ -30,8 +30,7 @@ "type": "AMMO", "description": "A high-strength ethanol solution mixed with methanol to make it toxic to drink, so as to avoid pre-apocalyptic regulations on ethanol. Intended for use in alcohol-burning stoves and as a solvent.", "price": 10, - "price_postapoc": 300, - "ammo_type": "conc_alcohol" + "price_postapoc": 300 }, { "id": "chem_methanol", @@ -40,8 +39,7 @@ "type": "AMMO", "description": "High purity methanol suitable for use in chemical reactions. Could be used in alcohol-burning stoves. Very toxic.", "price": 5, - "price_postapoc": 100, - "ammo_type": "conc_alcohol" + "price_postapoc": 100 }, { "id": "diesel", @@ -104,7 +102,7 @@ "material": "hydrocarbons", "symbol": "=", "color": "light_red", - "ammo_type": [ "gasoline", "flammable" ], + "ammo_type": "gasoline", "damage": 5, "range": 4, "pierce": 5, diff --git a/data/json/items/generic/casing.json b/data/json/items/generic/casing.json index 41249267ad5df..dc875824bded6 100644 --- a/data/json/items/generic/casing.json +++ b/data/json/items/generic/casing.json @@ -258,6 +258,15 @@ "weight": 3, "volume": "5ml" }, + { + "id": "357mag_casing", + "copy-from": "casing", + "type": "GENERIC", + "name": ".357 magnum casing", + "description": "An empty casing from a .357 magnum round.", + "weight": 3, + "volume": "5ml" + }, { "id": "9x18mm_casing", "copy-from": "casing", diff --git a/data/json/items/gun/357.json b/data/json/items/gun/357sig.json similarity index 61% rename from data/json/items/gun/357.json rename to data/json/items/gun/357sig.json index fc8ee32a8f2d9..143021b849cc6 100644 --- a/data/json/items/gun/357.json +++ b/data/json/items/gun/357sig.json @@ -18,5 +18,14 @@ "durability": 7, "magazine_well": 1, "magazines": [ [ "357sig", [ "p226mag_15rd_357sig" ] ] ] + }, + { + "id": "glock_31", + "copy-from": "glock_22", + "type": "GUN", + "name": "Glock 31", + "description": "A full size .357 SIG Glock pistol. It is extremely similar to the Glock 22, and could be converted to fire .40 S&W by switching the barrel.", + "ammo": [ "357sig" ], + "magazines": [ [ "357sig", [ "glock40mag", "glock40bigmag" ] ] ] } ] diff --git a/data/json/items/gun/38.json b/data/json/items/gun/38.json index fc0b290e0b99b..a1d9025062c6a 100644 --- a/data/json/items/gun/38.json +++ b/data/json/items/gun/38.json @@ -155,7 +155,7 @@ "material": [ "steel", "plastic" ], "symbol": "(", "color": "light_gray", - "ammo": "38", + "ammo": [ "357mag", "38" ], "dispersion": 320, "durability": 10, "clip_size": 7, diff --git a/data/json/items/gun/4570.json b/data/json/items/gun/4570.json index 71e7da9332e87..45f226af4d441 100644 --- a/data/json/items/gun/4570.json +++ b/data/json/items/gun/4570.json @@ -13,7 +13,7 @@ "material": [ "steel", "wood" ], "symbol": "(", "color": "light_blue", - "ammo": "4570", + "ammo": [ "4570" ], "dispersion": 200, "durability": 8, "clip_size": 7, @@ -42,7 +42,7 @@ "material": [ "steel", "plastic" ], "color": "dark_gray", "symbol": "(", - "ammo": "4570", + "ammo": [ "4570" ], "ranged_damage": -15, "dispersion": 180, "durability": 8, @@ -64,7 +64,7 @@ "material": [ "steel", "wood" ], "color": "blue", "symbol": "(", - "ammo": "4570", + "ammo": [ "4570" ], "skill": "rifle", "ranged_damage": 4, "dispersion": 100, diff --git a/data/json/items/magazine/357.json b/data/json/items/magazine/357sig.json similarity index 100% rename from data/json/items/magazine/357.json rename to data/json/items/magazine/357sig.json diff --git a/data/json/items/magazine/40.json b/data/json/items/magazine/40.json index dc82748f7783d..b3a42a2844ed3 100644 --- a/data/json/items/magazine/40.json +++ b/data/json/items/magazine/40.json @@ -17,15 +17,15 @@ { "id": "glock40bigmag", "type": "MAGAZINE", - "name": "Glock .40S&W extended magazine", - "description": "An extended 22-round magazine for use with the Glock 22 .40S&W pistol.", + "name": "Glock 22 extended magazine", + "description": "An extended 22-round magazine for use with Glock pistols chambered for .40 S&W or .357 SIG.", "weight": 200, "volume": 2, "price": 5900, "material": "plastic", "symbol": "#", "color": "light_gray", - "ammo_type": "40", + "ammo_type": [ "40", "357sig" ], "capacity": 22, "reliability": 7, "reload_time": 140, @@ -34,15 +34,15 @@ { "id": "glock40mag", "type": "MAGAZINE", - "name": "Glock .40S&W magazine", - "description": "A compact light-weight polymer magazine for use with the Glock 22 .40S&W pistol.", + "name": "Glock 22 magazine", + "description": "A compact light-weight polymer magazine for use with Glock pistols chambered for .40 S&W or .357 SIG.", "weight": 125, "volume": 1, "price": 3200, "material": "plastic", "symbol": "#", "color": "light_gray", - "ammo_type": "40", + "ammo_type": [ "40", "357sig" ], "capacity": 15, "reliability": 8, "flags": [ "MAG_COMPACT" ] diff --git a/data/json/mapgen/basecamps/modular_field_common.json b/data/json/mapgen/basecamps/modular_field_common.json new file mode 100644 index 0000000000000..a238e7dbff7b8 --- /dev/null +++ b/data/json/mapgen/basecamps/modular_field_common.json @@ -0,0 +1,310 @@ +[ + { + "type": "mapgen", + "update_mapgen_id": "fbmf_0", + "method": "json", + "object": { + "set": [ + { "point": "terrain", "id": "t_dirt", "x": 10, "y": 3 }, + { "point": "furniture", "id": "f_bulletin", "x": 10, "y": 3 } + ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_fireplace_northeast", + "method": "json", + "object": { "set": [ { "point": "furniture", "id": "f_fireplace", "x": 19, "y": 6 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_brazier_northeast", + "method": "json", + "object": { "set": [ { "point": "furniture", "id": "f_brazier", "x": 19, "y": 6 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_stove_northeast", + "method": "json", + "object": { "set": [ { "point": "furniture", "id": "f_woodstove", "x": 19, "y": 6 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_strawbed", + "object": { + "mapgensize": [ 2, 2 ], + "set": [ + { "point": "furniture", "id": "f_straw_bed", "x": 0, "y": 0 }, + { "point": "furniture", "id": "f_straw_bed", "x": 1, "y": 0 } + ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_strawbed1_northeast", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_strawbed" ], "x": 18, "y": 5 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_bedset", + "object": { + "mapgensize": [ 3, 3 ], + "set": [ + { "point": "furniture", "id": "f_bookcase", "x": 0, "y": 0 }, + { "point": "furniture", "id": "f_bed", "x": 1, "y": 0 }, + { "point": "furniture", "id": "f_bed", "x": 2, "y": 0 } + ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_bed1_northeast", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_bedset" ], "x": 17, "y": 5 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_strawbed2_northeast", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_strawbed" ], "x": 18, "y": 7 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_bed2_northeast", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_bedset" ], "x": 17, "y": 7 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_tent_strawbed3_east", + "method": "json", + "object": { + "place_nested": [ { "chunks": [ "fbmf_strawbed" ], "x": 18, "y": 11 }, { "chunks": [ "fbmf_strawbed" ], "x": 18, "y": 13 } ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_tent_bed3_east", + "method": "json", + "object": { + "place_nested": [ { "chunks": [ "fbmf_bedset" ], "x": 17, "y": 11 }, { "chunks": [ "fbmf_bedset" ], "x": 17, "y": 13 } ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room_strawbed3_east", + "method": "json", + "object": { + "place_nested": [ { "chunks": [ "fbmf_strawbed" ], "x": 18, "y": 10 }, { "chunks": [ "fbmf_strawbed" ], "x": 18, "y": 13 } ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room_bed3_east", + "method": "json", + "object": { + "place_nested": [ { "chunks": [ "fbmf_bedset" ], "x": 17, "y": 10 }, { "chunks": [ "fbmf_bedset" ], "x": 17, "y": 13 } ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_tent_strawbed3_southeast", + "method": "json", + "object": { + "place_nested": [ { "chunks": [ "fbmf_strawbed" ], "x": 18, "y": 17 }, { "chunks": [ "fbmf_strawbed" ], "x": 18, "y": 19 } ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_tent_bed3_southeast", + "method": "json", + "object": { + "place_nested": [ { "chunks": [ "fbmf_bedset" ], "x": 17, "y": 17 }, { "chunks": [ "fbmf_bedset" ], "x": 17, "y": 19 } ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room_strawbed3_southeast", + "method": "json", + "object": { + "place_nested": [ { "chunks": [ "fbmf_strawbed" ], "x": 18, "y": 16 }, { "chunks": [ "fbmf_strawbed" ], "x": 18, "y": 19 } ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room_bed3_southeast", + "method": "json", + "object": { + "place_nested": [ { "chunks": [ "fbmf_bedset" ], "x": 17, "y": 16 }, { "chunks": [ "fbmf_bedset" ], "x": 17, "y": 19 } ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_strawbed3_northwest", + "method": "json", + "object": { + "place_nested": [ { "chunks": [ "fbmf_strawbed" ], "x": 4, "y": 5 }, { "chunks": [ "fbmf_strawbed" ], "x": 4, "y": 7 } ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_bed3_northwest", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_bedset" ], "x": 4, "y": 5 }, { "chunks": [ "fbmf_bedset" ], "x": 4, "y": 7 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_tent_strawbed3_west", + "method": "json", + "object": { + "place_nested": [ { "chunks": [ "fbmf_strawbed" ], "x": 4, "y": 11 }, { "chunks": [ "fbmf_strawbed" ], "x": 4, "y": 13 } ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_tent_bed3_west", + "method": "json", + "object": { + "place_nested": [ { "chunks": [ "fbmf_bedset" ], "x": 4, "y": 11 }, { "chunks": [ "fbmf_bedset" ], "x": 4, "y": 13 } ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room_strawbed3_west", + "method": "json", + "object": { + "place_nested": [ { "chunks": [ "fbmf_strawbed" ], "x": 4, "y": 10 }, { "chunks": [ "fbmf_strawbed" ], "x": 4, "y": 13 } ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room_bed3_west", + "method": "json", + "object": { + "place_nested": [ { "chunks": [ "fbmf_bedset" ], "x": 4, "y": 10 }, { "chunks": [ "fbmf_bedset" ], "x": 4, "y": 13 } ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_tent_strawbed3_southwest", + "method": "json", + "object": { + "place_nested": [ { "chunks": [ "fbmf_strawbed" ], "x": 4, "y": 17 }, { "chunks": [ "fbmf_strawbed" ], "x": 4, "y": 19 } ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_tent_bed3_southwest", + "method": "json", + "object": { + "place_nested": [ { "chunks": [ "fbmf_bedset" ], "x": 4, "y": 17 }, { "chunks": [ "fbmf_bedset" ], "x": 4, "y": 19 } ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room_strawbed3_southwest", + "method": "json", + "object": { + "place_nested": [ { "chunks": [ "fbmf_strawbed" ], "x": 4, "y": 16 }, { "chunks": [ "fbmf_strawbed" ], "x": 4, "y": 19 } ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room_bed3_southwest", + "method": "json", + "object": { + "place_nested": [ { "chunks": [ "fbmf_bedset" ], "x": 4, "y": 16 }, { "chunks": [ "fbmf_bedset" ], "x": 4, "y": 19 } ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_kitchen_fireplace_center", + "method": "json", + "object": { + "mapgensize": [ 6, 6 ], + "set": [ + { "point": "furniture", "id": "f_counter", "x": 13, "y": 10 }, + { "point": "furniture", "id": "f_fireplace", "x": 12, "y": 11 } + ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_kitchen_butchery_center", + "method": "json", + "object": { "set": [ { "point": "furniture", "id": "f_butcher_rack", "x": 12, "y": 10 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_kitchen_toolrack_center", + "method": "json", + "object": { "set": [ { "point": "furniture", "id": "f_bookcase", "x": 10, "y": 10 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_core_tableset", + "object": { + "mapgensize": [ 3, 3 ], + "set": [ + { "point": "furniture", "id": "f_chair", "x": 0, "y": 1 }, + { "point": "furniture", "id": "f_table", "x": 1, "y": 1 }, + { "point": "furniture", "id": "f_chair", "x": 2, "y": 1 } + ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_kitchen_table_center", + "method": "json", + "object": { + "place_nested": [ { "chunks": [ "fbmf_core_tableset" ], "x": 11, "y": 12 }, { "chunks": [ "fbmf_core_tableset" ], "x": 11, "y": 13 } ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_kitchen_table_south", + "method": "json", + "object": { + "place_nested": [ { "chunks": [ "fbmf_core_tableset" ], "x": 11, "y": 16 }, { "chunks": [ "fbmf_core_tableset" ], "x": 11, "y": 15 } ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_kitchen_stove_south", + "method": "json", + "object": { + "set": [ + { "point": "furniture", "id": "f_counter", "x": 10, "y": 19 }, + { "point": "furniture", "id": "f_woodstove", "x": 11, "y": 19 } + ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_well_north", + "method": "json", + "object": { "set": [ { "point": "terrain", "id": "t_water_pump", "x": 13, "y": 6 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_root_cellar_north", + "method": "json", + "object": { "set": [ { "point": "terrain", "id": "t_rootcellar", "x": 12, "y": 8 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_radio_tower_north", + "method": "json", + "object": { "set": [ { "point": "terrain", "id": "t_radio_tower", "x": 13, "y": 3 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_radio_console_north", + "method": "json", + "object": { "set": [ { "point": "terrain", "id": "t_radio_controls", "x": 13, "y": 4 } ] } + } +] diff --git a/data/json/mapgen/basecamps/modular_field_defenses.json b/data/json/mapgen/basecamps/modular_field_defenses.json new file mode 100644 index 0000000000000..84388fb9754d3 --- /dev/null +++ b/data/json/mapgen/basecamps/modular_field_defenses.json @@ -0,0 +1,62 @@ +[ + { + "type": "mapgen", + "update_mapgen_id": "fbmf_trench_north", + "method": "json", + "object": { "set": [ { "line": "terrain", "id": "t_pit", "x": 3, "x2": 20, "y": 0, "y2": 0 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_trench_south", + "method": "json", + "object": { "set": [ { "line": "terrain", "id": "t_pit", "x": 3, "x2": 20, "y": 23, "y2": 23 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_trench_corner_northeast", + "method": "json", + "object": { "set": [ { "line": "terrain", "id": "t_pit", "x": 21, "x2": 21, "y": 0, "y2": 4 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_trench_corner_northwest", + "method": "json", + "object": { "set": [ { "line": "terrain", "id": "t_pit", "x": 2, "x2": 2, "y": 0, "y2": 4 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_trench_corner_southeast", + "method": "json", + "object": { "set": [ { "line": "terrain", "id": "t_pit", "x": 21, "x2": 21, "y": 19, "y2": 23 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_trench_corner_southwest", + "method": "json", + "object": { "set": [ { "line": "terrain", "id": "t_pit", "x": 2, "x2": 2, "y": 19, "y2": 23 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_trench_east", + "method": "json", + "object": { + "set": [ + { "line": "terrain", "id": "t_pit", "x": 21, "x2": 22, "y": 0, "y2": 0 }, + { "line": "terrain", "id": "t_pit", "x": 21, "x2": 22, "y": 23, "y2": 23 }, + { "line": "terrain", "id": "t_pit", "x": 23, "x2": 23, "y": 0, "y2": 23 } + ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_trench_west", + "method": "json", + "object": { + "set": [ + { "line": "terrain", "id": "t_pit", "x": 1, "x2": 2, "y": 0, "y2": 0 }, + { "line": "terrain", "id": "t_pit", "x": 1, "x2": 2, "y": 23, "y2": 23 }, + { "line": "terrain", "id": "t_pit", "x": 0, "x2": 0, "y": 0, "y2": 23 } + ] + } + } +] diff --git a/data/json/mapgen/basecamps/modular_field_metal.json b/data/json/mapgen/basecamps/modular_field_metal.json new file mode 100644 index 0000000000000..244895d06a2a8 --- /dev/null +++ b/data/json/mapgen/basecamps/modular_field_metal.json @@ -0,0 +1,428 @@ +[ + { + "type": "palette", + "id": "fbmf_metal_palette", + "terrain": { ";": "t_dirt", ".": "t_scrap_floor", "+": "t_door_metal_c", "v": "t_window_no_curtains", "w": "t_scrap_wall" }, + "furniture": { } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_room0_metal", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + " ", + " ;;ww;", + " ;;..w", + " ;;..w", + " ;;;;;", + " ;;;;;" + ], + "palettes": [ "fbmf_metal_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room0_metal_northeast", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_room0_metal" ], "x": 15, "y": 3 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_room1_metal", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + " ", + " v ", + " w. ", + " . ", + " w...w", + " " + ], + "palettes": [ "fbmf_metal_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room1_metal_northeast", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_room1_metal" ], "x": 15, "y": 3 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_room2_metal", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + " ", + " w w", + " ", + " + ", + " ", + " wwwww" + ], + "palettes": [ "fbmf_metal_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room2_metal_northeast", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_room2_metal" ], "x": 15, "y": 3 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_metal_shack_east", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + "wwwwww", + "w....w", + "+....w", + "w....v", + "w....w", + "w+wwww" + ], + "palettes": [ "fbmf_metal_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_shack4_metal_east", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_metal_shack_east" ], "x": 15, "y": 9 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_metal_room_east", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + "w....w", + "w....w", + "+....w", + "w....v", + "w....w", + "w+wwww" + ], + "palettes": [ "fbmf_metal_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room4_metal_east", + "method": "json", + "object": { + "place_nested": [ { "chunks": [ "fbmf_metal_room_east" ], "x": 15, "y": 9 } ], + "set": [ { "point": "terrain", "id": "t_wall_metal", "x": 15, "y": 8 } ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_shack4_metal_southeast", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_metal_shack_east" ], "x": 15, "y": 15 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room4_metal_southeast", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_metal_room_east" ], "x": 15, "y": 15 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_metal_shack_northwest", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + ";;;;;;", + "wwwww;", + "w...w;", + "v...+;", + "w...w;", + "wwwww;" + ], + "palettes": [ "fbmf_metal_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room4_metal_northwest", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_metal_shack_northwest" ], "x": 3, "y": 3 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_metal_shack_west", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + "wwwwww", + "w....w", + "w....+", + "v....w", + "w....w", + "wwww+w" + ], + "palettes": [ "fbmf_metal_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_shack4_metal_west", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_metal_shack_west" ], "x": 3, "y": 9 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_metal_room_west", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + "w....w", + "w....w", + "w....+", + "v....w", + "w....w", + "wwww+w" + ], + "palettes": [ "fbmf_metal_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room4_metal_west", + "method": "json", + "object": { + "place_nested": [ { "chunks": [ "fbmf_metal_room_west" ], "x": 3, "y": 9 } ], + "set": [ { "point": "terrain", "id": "t_wall_metal", "x": 8, "y": 8 } ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_shack4_metal_southwest", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_metal_shack_west" ], "x": 3, "y": 15 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room4_metal_southwest", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_metal_room_west" ], "x": 3, "y": 15 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_core_shack_ne_metal_center", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + " www", + " ..w", + " ..w", + " ..+", + " ..w", + " ..w" + ], + "palettes": [ "fbmf_metal_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_shack_ne_metal_center", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_core_shack_ne_metal_center" ], "x": 9, "y": 9 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_core_ne_metal_center", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + " www", + " ...", + " ...", + " ...", + " ...", + " ..." + ], + "palettes": [ "fbmf_metal_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_ne_metal_center", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_core_ne_metal_center" ], "x": 9, "y": 9 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_core_shack_nw_metal_center", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + "ww+ ", + "w.. ", + "w.. ", + "+.. ", + "w.. ", + "w.. " + ], + "palettes": [ "fbmf_metal_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_shack_nw_metal_center", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_core_shack_nw_metal_center" ], "x": 9, "y": 9 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_core_nw_metal_center", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + "ww+ ", + "... ", + "... ", + "... ", + "... ", + "... " + ], + "palettes": [ "fbmf_metal_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_nw_metal_center", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_core_nw_metal_center" ], "x": 9, "y": 9 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_metal_center", + "method": "json", + "object": { + "place_nested": [ + { "chunks": [ "fbmf_core_nw_metal_center" ], "x": 9, "y": 9 }, + { "chunks": [ "fbmf_core_ne_metal_center" ], "x": 9, "y": 9 } + ] + } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_core_shack_se_metal_south", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + " ..w", + " ..w", + " ..w", + " ..+", + " ..w", + " +ww" + ], + "palettes": [ "fbmf_metal_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_shack_se_metal_south", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_core_shack_se_metal_south" ], "x": 9, "y": 15 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_core_se_metal_south", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + " ...", + " ...", + " ...", + " ...", + " ...", + " +ww" + ], + "palettes": [ "fbmf_metal_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_se_metal_south", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_core_se_metal_south" ], "x": 9, "y": 15 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_core_shack_sw_metal_south", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + "w.. ", + "w.. ", + "w.. ", + "+.. ", + "w.. ", + "www " + ], + "palettes": [ "fbmf_metal_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_shack_sw_metal_south", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_core_shack_sw_metal_south" ], "x": 9, "y": 15 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_core_sw_metal_south", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + "... ", + "... ", + "... ", + "... ", + "... ", + "www " + ], + "palettes": [ "fbmf_metal_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_sw_metal_south", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_core_sw_metal_south" ], "x": 9, "y": 15 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_metal_south", + "method": "json", + "object": { + "place_nested": [ + { "chunks": [ "fbmf_core_sw_metal_south" ], "x": 9, "y": 15 }, + { "chunks": [ "fbmf_core_se_metal_south" ], "x": 9, "y": 15 } + ] + } + } +] diff --git a/data/json/mapgen/basecamps/modular_field_tent.json b/data/json/mapgen/basecamps/modular_field_tent.json new file mode 100644 index 0000000000000..7045c38c3f06d --- /dev/null +++ b/data/json/mapgen/basecamps/modular_field_tent.json @@ -0,0 +1,78 @@ +[ + { + "type": "palette", + "id": "fbmf_tent_palette", + "terrain": { " ": "t_dirt", ".": "t_dirtfloor", "+": "t_dirtfloor", "w": "t_dirtfloor" }, + "furniture": { "+": "f_canvas_door", "w": "f_canvas_wall" } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_large_tent_east", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + " ", + " wwwww", + " w...w", + " +...w", + " w...w", + " wwwww" + ], + "palettes": [ "fbmf_tent_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_tent_northeast", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_large_tent_east" ], "x": 15, "y": 3 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_tent_east", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_large_tent_east" ], "x": 15, "y": 9 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_tent_southeast", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_large_tent_east" ], "x": 15, "y": 15 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_large_tent_west", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + " ", + "wwwww ", + "w...w ", + "w...+ ", + "w...w ", + "wwwww " + ], + "palettes": [ "fbmf_tent_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_tent_northwest", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_large_tent_west" ], "x": 3, "y": 3 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_tent_west", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_large_tent_west" ], "x": 3, "y": 9 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_tent_southwest", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_large_tent_west" ], "x": 3, "y": 15 } ] } + } +] diff --git a/data/json/mapgen/basecamps/modular_field_wad.json b/data/json/mapgen/basecamps/modular_field_wad.json new file mode 100644 index 0000000000000..6c83e4be8ce3c --- /dev/null +++ b/data/json/mapgen/basecamps/modular_field_wad.json @@ -0,0 +1,428 @@ +[ + { + "type": "palette", + "id": "fbmf_wad_palette", + "terrain": { ";": "t_dirt", ".": "t_floor_primitive", "+": "t_door_makeshift_c", "v": "t_wall_wattle_half", "w": "t_wall_wattle" }, + "furniture": { } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_room0_wad", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + " ", + " ;;ww;", + " ;;..w", + " ;;..w", + " ;;;;;", + " ;;;;;" + ], + "palettes": [ "fbmf_wad_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room0_wad_northeast", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_room0_wad" ], "x": 15, "y": 3 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_room1_wad", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + " ", + " v ", + " w. ", + " . ", + " w...w", + " " + ], + "palettes": [ "fbmf_wad_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room1_wad_northeast", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_room1_wad" ], "x": 15, "y": 3 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_room2_wad", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + " ", + " w w", + " ", + " + ", + " ", + " wwwww" + ], + "palettes": [ "fbmf_wad_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room2_wad_northeast", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_room2_wad" ], "x": 15, "y": 3 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_wad_shack_east", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + "wwwwww", + "w....w", + "+....w", + "w....v", + "w....w", + "w+wwww" + ], + "palettes": [ "fbmf_wad_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_shack4_wad_east", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_wad_shack_east" ], "x": 15, "y": 9 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_wad_room_east", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + "w....w", + "w....w", + "+....w", + "w....v", + "w....w", + "w+wwww" + ], + "palettes": [ "fbmf_wad_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room4_wad_east", + "method": "json", + "object": { + "place_nested": [ { "chunks": [ "fbmf_wad_room_east" ], "x": 15, "y": 9 } ], + "set": [ { "point": "terrain", "id": "t_wall_wattle", "x": 15, "y": 8 } ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_shack4_wad_southeast", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_wad_shack_east" ], "x": 15, "y": 15 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room4_wad_southeast", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_wad_room_east" ], "x": 15, "y": 15 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_wad_shack_northwest", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + ";;;;;;", + "wwwww;", + "w...w;", + "v...+;", + "w...w;", + "wwwww;" + ], + "palettes": [ "fbmf_wad_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room4_wad_northwest", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_wad_shack_northwest" ], "x": 3, "y": 3 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_wad_shack_west", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + "wwwwww", + "w....w", + "w....+", + "v....w", + "w....w", + "wwww+w" + ], + "palettes": [ "fbmf_wad_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_shack4_wad_west", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_wad_shack_west" ], "x": 3, "y": 9 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_wad_room_west", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + "w....w", + "w....w", + "w....+", + "v....w", + "w....w", + "wwww+w" + ], + "palettes": [ "fbmf_wad_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room4_wad_west", + "method": "json", + "object": { + "place_nested": [ { "chunks": [ "fbmf_wad_room_west" ], "x": 3, "y": 9 } ], + "set": [ { "point": "terrain", "id": "t_wall_wattle", "x": 8, "y": 8 } ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_shack4_wad_southwest", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_wad_shack_west" ], "x": 3, "y": 15 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room4_wad_southwest", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_wad_room_west" ], "x": 3, "y": 15 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_core_shack_ne_wad_center", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + " www", + " ..w", + " ..w", + " ..+", + " ..w", + " ..w" + ], + "palettes": [ "fbmf_wad_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_shack_ne_wad_center", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_core_shack_ne_wad_center" ], "x": 9, "y": 9 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_core_ne_wad_center", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + " www", + " ...", + " ...", + " ...", + " ...", + " ..." + ], + "palettes": [ "fbmf_wad_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_ne_wad_center", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_core_ne_wad_center" ], "x": 9, "y": 9 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_core_shack_nw_wad_center", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + "ww+ ", + "w.. ", + "w.. ", + "+.. ", + "w.. ", + "w.. " + ], + "palettes": [ "fbmf_wad_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_shack_nw_wad_center", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_core_shack_nw_wad_center" ], "x": 9, "y": 9 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_core_nw_wad_center", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + "ww+ ", + "... ", + "... ", + "... ", + "... ", + "... " + ], + "palettes": [ "fbmf_wad_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_nw_wad_center", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_core_nw_wad_center" ], "x": 9, "y": 9 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_wad_center", + "method": "json", + "object": { + "place_nested": [ + { "chunks": [ "fbmf_core_nw_wad_center" ], "x": 9, "y": 9 }, + { "chunks": [ "fbmf_core_ne_wad_center" ], "x": 9, "y": 9 } + ] + } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_core_shack_se_wad_south", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + " ..w", + " ..w", + " ..w", + " ..+", + " ..w", + " +ww" + ], + "palettes": [ "fbmf_wad_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_shack_se_wad_south", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_core_shack_se_wad_south" ], "x": 9, "y": 15 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_core_se_wad_south", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + " ...", + " ...", + " ...", + " ...", + " ...", + " +ww" + ], + "palettes": [ "fbmf_wad_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_se_wad_south", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_core_se_wad_south" ], "x": 9, "y": 15 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_core_shack_sw_wad_south", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + "w.. ", + "w.. ", + "w.. ", + "+.. ", + "w.. ", + "www " + ], + "palettes": [ "fbmf_wad_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_shack_sw_wad_south", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_core_shack_sw_wad_south" ], "x": 9, "y": 15 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_core_sw_wad_south", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + "... ", + "... ", + "... ", + "... ", + "... ", + "www " + ], + "palettes": [ "fbmf_wad_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_sw_wad_south", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_core_sw_wad_south" ], "x": 9, "y": 15 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_wad_south", + "method": "json", + "object": { + "place_nested": [ + { "chunks": [ "fbmf_core_sw_wad_south" ], "x": 9, "y": 15 }, + { "chunks": [ "fbmf_core_se_wad_south" ], "x": 9, "y": 15 } + ] + } + } +] diff --git a/data/json/mapgen/basecamps/modular_field_wood.json b/data/json/mapgen/basecamps/modular_field_wood.json new file mode 100644 index 0000000000000..cae2a3cae11e2 --- /dev/null +++ b/data/json/mapgen/basecamps/modular_field_wood.json @@ -0,0 +1,428 @@ +[ + { + "type": "palette", + "id": "fbmf_wood_palette", + "terrain": { ";": "t_dirt", ".": "t_floor", "+": "t_door_c", "v": "t_window_no_curtains", "w": "t_wall_wood" }, + "furniture": { } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_room0_wood", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + " ", + " ;;ww;", + " ;;..w", + " ;;..w", + " ;;;;;", + " ;;;;;" + ], + "palettes": [ "fbmf_wood_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room0_wood_northeast", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_room0_wood" ], "x": 15, "y": 3 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_room1_wood", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + " ", + " v ", + " w. ", + " . ", + " w...w", + " " + ], + "palettes": [ "fbmf_wood_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room1_wood_northeast", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_room1_wood" ], "x": 15, "y": 3 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_room2_wood", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + " ", + " w w", + " ", + " + ", + " ", + " wwwww" + ], + "palettes": [ "fbmf_wood_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room2_wood_northeast", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_room2_wood" ], "x": 15, "y": 3 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_wood_shack_east", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + "wwwwww", + "w....w", + "+....w", + "w....v", + "w....w", + "w+wwww" + ], + "palettes": [ "fbmf_wood_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_shack4_wood_east", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_wood_shack_east" ], "x": 15, "y": 9 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_wood_room_east", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + "w....w", + "w....w", + "+....w", + "w....v", + "w....w", + "w+wwww" + ], + "palettes": [ "fbmf_wood_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room4_wood_east", + "method": "json", + "object": { + "place_nested": [ { "chunks": [ "fbmf_wood_room_east" ], "x": 15, "y": 9 } ], + "set": [ { "point": "terrain", "id": "t_wall_wood", "x": 15, "y": 8 } ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_shack4_wood_southeast", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_wood_shack_east" ], "x": 15, "y": 15 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room4_wood_southeast", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_wood_room_east" ], "x": 15, "y": 15 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_wood_shack_northwest", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + ";;;;;;", + "wwwww;", + "w...w;", + "v...+;", + "w...w;", + "wwwww;" + ], + "palettes": [ "fbmf_wood_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room4_wood_northwest", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_wood_shack_northwest" ], "x": 3, "y": 3 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_wood_shack_west", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + "wwwwww", + "w....w", + "w....+", + "v....w", + "w....w", + "wwww+w" + ], + "palettes": [ "fbmf_wood_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_shack4_wood_west", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_wood_shack_west" ], "x": 3, "y": 9 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_wood_room_west", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + "w....w", + "w....w", + "w....+", + "v....w", + "w....w", + "wwww+w" + ], + "palettes": [ "fbmf_wood_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room4_wood_west", + "method": "json", + "object": { + "place_nested": [ { "chunks": [ "fbmf_wood_room_west" ], "x": 3, "y": 9 } ], + "set": [ { "point": "terrain", "id": "t_wall_wood", "x": 8, "y": 8 } ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_shack4_wood_southwest", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_wood_shack_west" ], "x": 3, "y": 15 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_room4_wood_southwest", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_wood_room_west" ], "x": 3, "y": 15 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_core_shack_ne_wood_center", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + " www", + " ..w", + " ..w", + " ..+", + " ..w", + " ..w" + ], + "palettes": [ "fbmf_wood_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_shack_ne_wood_center", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_core_shack_ne_wood_center" ], "x": 9, "y": 9 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_core_ne_wood_center", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + " www", + " ...", + " ...", + " ...", + " ...", + " ..." + ], + "palettes": [ "fbmf_wood_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_ne_wood_center", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_core_ne_wood_center" ], "x": 9, "y": 9 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_core_shack_nw_wood_center", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + "ww+ ", + "w.. ", + "w.. ", + "+.. ", + "w.. ", + "w.. " + ], + "palettes": [ "fbmf_wood_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_shack_nw_wood_center", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_core_shack_nw_wood_center" ], "x": 9, "y": 9 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_core_nw_wood_center", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + "ww+ ", + "... ", + "... ", + "... ", + "... ", + "... " + ], + "palettes": [ "fbmf_wood_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_nw_wood_center", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_core_nw_wood_center" ], "x": 9, "y": 9 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_wood_center", + "method": "json", + "object": { + "place_nested": [ + { "chunks": [ "fbmf_core_nw_wood_center" ], "x": 9, "y": 9 }, + { "chunks": [ "fbmf_core_ne_wood_center" ], "x": 9, "y": 9 } + ] + } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_core_shack_se_wood_south", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + " ..w", + " ..w", + " ..w", + " ..+", + " ..w", + " +ww" + ], + "palettes": [ "fbmf_wood_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_shack_se_wood_south", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_core_shack_se_wood_south" ], "x": 9, "y": 15 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_core_se_wood_south", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + " ...", + " ...", + " ...", + " ...", + " ...", + " +ww" + ], + "palettes": [ "fbmf_wood_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_se_wood_south", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_core_se_wood_south" ], "x": 9, "y": 15 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_core_shack_sw_wood_south", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + "w.. ", + "w.. ", + "w.. ", + "+.. ", + "w.. ", + "www " + ], + "palettes": [ "fbmf_wood_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_shack_sw_wood_south", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_core_shack_sw_wood_south" ], "x": 9, "y": 15 } ] } + }, + { + "type": "mapgen", + "method": "json", + "nested_mapgen_id": "fbmf_core_sw_wood_south", + "object": { + "mapgensize": [ 6, 6 ], + "rows": [ + "... ", + "... ", + "... ", + "... ", + "... ", + "www " + ], + "palettes": [ "fbmf_wood_palette" ] + } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_sw_wood_south", + "method": "json", + "object": { "place_nested": [ { "chunks": [ "fbmf_core_sw_wood_south" ], "x": 9, "y": 15 } ] } + }, + { + "type": "mapgen", + "update_mapgen_id": "fbmf_core_wood_south", + "method": "json", + "object": { + "place_nested": [ + { "chunks": [ "fbmf_core_sw_wood_south" ], "x": 9, "y": 15 }, + { "chunks": [ "fbmf_core_se_wood_south" ], "x": 9, "y": 15 } + ] + } + } +] diff --git a/data/json/mapgen/marina.json b/data/json/mapgen/marina.json new file mode 100644 index 0000000000000..15162db252fed --- /dev/null +++ b/data/json/mapgen/marina.json @@ -0,0 +1,185 @@ +[ + { + "type": "mapgen", + "method": "json", + "om_terrain": [ + [ "marina_5", "marina_4", "marina_3", "marina_2", "marina_1" ], + [ "marina_10", "marina_9", "marina_8", "marina_7", "marina_6" ], + [ "marina_15", "marina_14", "marina_13", "marina_12", "marina_11" ], + [ "marina_20", "marina_19", "marina_18", "marina_17", "marina_16" ] + ], + "weight": 250, + "object": { + "rows": [ + "````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````", + "````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````", + "````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````", + "````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````", + "````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````", + "````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````", + "!!!`````!!!`````!!!`````!!!`````!!!```````````!!!B!!!!!!!!!!!!!!!!!B!!!!!!!!!!!!!!!B!!!!!````````````````````````````!!!", + "!!!`````!!!`````!!!`````!!!`````!!!```````````!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!````````````````````````````!!!", + "!!!`````B!!`````B!!`````B!!`````B!!```````````!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!````````````````````````````!!!", + "!!!`````!!!`````!!!`````!!!`````!!!```````````!!!`````````````!!!`````!!!`````````````!!!````````````````````````````!!!", + "!!!`````!!!`````!!!`````!!!`````!!!```````````!!!`````````````!!!`````!!!`````````````!!!````````````````````````````!!!", + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!```````````!!B`````````````B!!`````B!B`````````````B!B````````````````````````````B!!", + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!```````````!!!`````````````!!!`````!!!`````````````!!!````````````````````````````!!!", + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!```````````!!!`````````````!!!`````!!!`````````````!!!````````````````````````````!!!", + "!!!`````!!!`````!!!`````!!!`````!!!```````````!!!`````````````````````````````````````!!!````````````````````````````!!!", + "!!!`````!!!`````!!!`````!!!`````!!!```````````!!!!!!!!```````````````````````````!!!!!!!!````````````````````````````!!!", + "!!!`````B!!`````B!!`````B!!`````B!!```````````!!!!!!!!```````````````````````````!!!!!!!!````````````````````````````!!!", + "!!!`````!!!`````!!!`````!!!`````!!!```````````!!!!!B!!```````````````````````````!!B!!!!!````````````````````````````!!!", + "!!!`````!!!`````!!!`````!!!`````!!!```````````!!!`````````````````````````````````````!!!````````````````````````````!!!", + "!!!```````````````````````````````````````````!!!`````````````````````````````````````!!!````````````````````````````!!!", + "!!!```````````````````````````````````````````!!!`````````````````````````````````````!!!````````````````````````````!!!", + "!!!```````````````````````````````````````````!!!`````````````````````````````````````!!!````````````````````````````!!!", + "!!!```````````````````````````````````````````!!!`````````````````````````````````````!!!````````````````````````````!!!", + "!!!```````````````````````````````````````````!!!!!B!!```````````````````````````!!B!!!!B````````````````````````````B!!", + "!!!```````````````````````````````````````````!!!!!!!!```````````````````````````!!!!!!!!````````````````````````````!!!", + "!!!```````````````````````````````````````````!!!!!B!!```````````````````````````!!B!!!!!````````````````````````````!!!", + "!!!```````````````````````````````````````````````````````````````````````````````````!!!````````````````````````````!!!", + "!!!```````````````````````````````````````````````````````````````````````````````````!!!````````````````````````````!!!", + "!!!```````````````````````````````````````````````````````````````````````````````````!!!````````````````````````````!!!", + "!!!`````!!!`````!!!`````!!!`````!!!```````````````````````````````````````````````````!!!````````````````````````````!!!", + "!!!`````!!!`````!!!`````!!!`````!!!```````````````````````````````````````````````````!!!````````````````````````````!!!", + "!!!`````B!!`````B!!`````B!!`````B!!```````````````````````````````````````````````````!!!````````````````````````````!!!", + "!!!`````!!!`````!!!`````!!!`````!!!```````````````````````````````````````````````````!!!````````````````````````````!!!", + "!!!`````!!!`````!!!`````!!!`````!!!```````````````````````````````````````````````````!!!````````````````````````````!!!", + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!```````````!!!!!B!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!B!!!!!!!!!!!!!!B!!!!!!!!``````````!!!", + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!```````````!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!``````````!!!", + "!!!!!!!!!!!!B!!!!!!!!!!!!!!!!B!!!!!```````````!!!!!B!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!B!!!!!!!!!!!!!!B!!!!!!!!``````````!!!", + "!!!```````````````````````````````````````````````````````````````````````````````````!!!````````````````````````````!!!", + "!!!```````````````````````````````````````````````````````````````````````````````````!!!````````````````````````````!!!", + "!!!```````````````````````````````````````````````````````````````````````````````````!!!````````````````````````````!!!", + "!!!```````````````````````````````````````````````````````````````````````````````````!!!````````````````````````````!!!", + "!!!```````````````````````````````````````````````````````````````````````````````````!!!````````````````````````````!!!", + "!!!```````````````````````````````````````````````````````````````````````````````````!!!````````````````````````````!!!", + "!!!```````````````````````````````````````````````````````````````````````````````````!!!````````````````````````````!!!", + "!!!```````````````````````````````````````````````````````````````````````````````````!!!````````````````````````````!!!", + "!!!```````````````````````````````````````````````````````````````````````````````````!!!````````````````````````````!!!", + "!!!```````````````````````````````````````````````````````````````````````````````````!!!````````````````````````````!!!", + "!!!`````!!!`````!!!`````!!!`````!!!```````````!!!`````!!!`````!!!`````!!!`````!!!`````!!!`````!!!`````!!!`````!!!````!!!", + "!!!`````!!!`````!!!`````!!!`````!!!```````````!!!`````!!!`````!!!`````!!!`````!!!`````!!!`````!!!`````!!!`````!!!````!!!", + "!!!`````B!!`````B!!`````B!!`````B!!```````````!!B`````!!B`````!!B`````!!B`````!!B`````!!B`````!!B`````!!B`````!!B````!!!", + "!!!`````!!!`````!!!`````!!!`````!!!```````````!!!`````!!!`````!!!`````!!!`````!!!`````!!!`````!!!`````!!!`````!!!````!!!", + "!!!`````!!!`````!!!`````!!!`````!!!##```````##!!!`````!!!`````!!!`````!!!`````!!!`````!!!`````!!!`````!!!`````!!!````!!!", + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!#.......#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!#.......#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!#.......#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!#.......#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", + "''''''''''''|---w---|''''''''''';;;##.......##;;;'''''''''''''''''''''''^^^^^^''^^^^^^;;;|-w---w---w-|--w---w--|---|---|", + "''%%b''b%%''wp~~~SSSw''%%b''b%%';;;q#.......#q;;;'%%b''b%%b''b%%b''b%%b'^c''ct''''c'q^;;;wSSSpSSSpSSS|ffCCooCss|~*&|~*&|", + "''%%b''b%%''|~~~~~TS|''%%b''b%%';;;'#.......#';;;'%%b''b%%b''b%%b''b%%b'^tc''c''''t''^;;;|ST~~~T~~~TS|~~~~~~~~~|~--|~--|", + "''''''''''''|~~~~~~~+''''''''''';;;'#.......#';;;'''''''''''''''''''''''''''''''''c''';;;+~~~~~~~~~~~|~~~~~~~~Q|~~s|~~s|", + "''''''''''''|~~~~~~~+''''''''''';;;'#.......#';;;''''''''''''''''''''''''''''tc''''''';;;+~~~~~~~~~~e|----+----|*--|*--|", + "''%%b''b%%''|~~~~CCC|''%%b''b%%';;;'#.......#';;;'%%b''b%%b''b%%b''b%%b'^tc''c'''ctc'^;;;|ST~~~T~~~~~~~~~~~~~~~~~~~~~~p|", + "''%%b''b%%''wp~~~~DQw''%%b''b%%';;;q#.......#q;;;'%%b''b%%b''b%%b''b%%b'^c'''''''''''^;;;wSSSpSSSp~~~~~~~~~~~~~~p|-*---|", + "''''''''''''|---w---|''''''''''';;;##.......##;;;'''''''''''''''''''''''^^^^^^''^^^^^^;;;|-w---w---w---w---w---w-|;;;dd|", + ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,,,,,,;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;", + ".....,.....,.....,.....,.....,.....,...........,.....,.....,.....,.....,.....,.....,.....,.....,.....,.....,.....,.....,", + ".....,.....,.....,.....,.....,.....,...........,.....,.....,.....,.....,.....,.....,.....,.....,.....,.....,.....,.....,", + ".....,.....,.....,.....,.....,.....,...........,.....,.....,.....,.....,.....,.....,.....,.....,.....,.....,.....,.....,", + ".....,.....,.....,.....,.....,.....,...........,.....,.....,.....,.....,.....,.....,.....,.....,.....,.....,.....,.....,", + ".....,.....,.....,.....,.....,.....,...........,.....,.....,.....,.....,.....,.....,.....,.....,.....,.....,.....,.....,", + ".....,.....,.....,.....,.....,.....,...........,.....,.....,.....,.....,.....,.....,.....,.....,.....,.....,.....,.....,", + ".....,.....,.....,.....,.....,.....,...........,.....,.....,.....,.....,.....,.....,.....,.....,.....,.....,.....,.....,", + "........................................................................................................................", + "........................................................................................................................", + "........................................................................................................................", + "........................................................................................................................", + "........................................................................................................................", + "........................................................................................................................", + "........................................................................................................................", + "........................................................................................................................", + " ................ ", + " ................ ", + " ................ ", + " ................ ", + " ................ ", + " ................ ", + " ................ ", + " ................ ", + " ................ ", + " ................ ", + " ................ ", + " ................ ", + " ................ ", + " ................ ", + " ................ ", + " ................ " + ], + "terrain": { + "%": [ "t_tree", "t_tree_walnut", "t_tree_chestnut", "t_tree_beech", "t_tree_hazelnut", "t_tree_cottonwood", "t_tree_elm" ], + " ": [ + [ "t_grass", 20 ], + [ "t_grass_dead", 3 ], + [ "t_grass_tall", 5 ], + [ "t_grass_long", 3 ], + [ "t_dirt", 5 ], + [ "t_shrub", 2 ], + [ "t_tree", 1 ] + ], + "^": [ "t_shrub" ], + "~": [ "t_floor" ], + "&": [ "t_floor" ], + "`": [ "t_water_dp" ], + "!": [ "t_dock" ], + "#": [ "t_guardrail" ], + "'": [ "t_concrete" ], + ".": [ "t_pavement" ], + ",": [ "t_pavement_y" ], + ";": [ "t_sidewalk" ], + "|": [ "t_wall_w" ], + "-": [ "t_wall_w" ], + "+": [ "t_door_glass_white_c" ], + "*": [ "t_door_c" ], + "b": [ "t_concrete" ], + "c": [ "t_concrete" ], + "d": [ "t_pavement" ], + "e": [ "t_floor" ], + "f": [ "t_floor" ], + "o": [ "t_floor" ], + "p": [ "t_floor" ], + "q": [ "t_concrete" ], + "s": [ "t_floor" ], + "t": [ "t_concrete" ], + "w": [ "t_window_domestic", "t_curtains" ], + "B": [ "t_dock" ], + "C": [ "t_floor" ], + "D": [ "t_floor" ], + "Q": [ "t_floor" ], + "S": [ "t_floor" ], + "T": [ "t_floor" ] + }, + "furniture": { + "b": [ "f_bench" ], + "c": [ "f_chair" ], + "d": [ "f_dumpster" ], + "e": [ "f_bookcase" ], + "f": [ "f_fridge" ], + "o": [ "f_oven" ], + "p": [ "f_indoor_plant", "f_indoor_plant_y" ], + "q": [ "f_trashcan" ], + "s": [ "f_sink" ], + "t": [ "f_table" ], + "B": [ "f_bitts" ], + "C": [ "f_counter" ], + "D": [ "f_stool" ], + "Q": [ "f_trashcan" ], + "S": [ "f_sofa" ], + "T": [ "f_table" ] + }, + "toilets": { "&": { } }, + "place_vendingmachines": [ { "item_group": "vending_food", "x": 13, "y": 59 }, { "item_group": "vending_drink", "x": 13, "y": 60 } ], + "items": { + "f": { "item": "fridge", "chance": 70, "repeat": [ 1, 3 ] }, + "e": { "item": "novels", "chance": 70, "repeat": [ 2, 4 ] }, + "o": { "item": "oven", "chance": 70 }, + "q": { "item": "trash_cart", "chance": 50, "repeat": [ 2, 3 ] }, + "Q": { "item": "trash_cart", "chance": 50, "repeat": [ 2, 3 ] } + }, + "place_item": [ { "item": "laptop", "x": 18, "y": 61, "chance": 80 } ], + "place_loot": [ { "group": "magazines", "x": 18, "y": 58, "chance": 80, "repeat": [ 1, 3 ] } ] + } + } +] diff --git a/data/json/npcs/appearance_trait_groups.json b/data/json/npcs/appearance_trait_groups.json index e17004634adf8..c4c7a36542022 100644 --- a/data/json/npcs/appearance_trait_groups.json +++ b/data/json/npcs/appearance_trait_groups.json @@ -1,6 +1,6 @@ [ { - "//": "This assigns characters a random appearance based on racial demographics in Massachussets - numbers rounded out a bit.", + "//": "This assigns characters a random appearance based on racial demographics in Massachusetts - numbers rounded out a bit.", "type": "trait_group", "id": "Appearance_demographics", "subtype": "distribution", diff --git a/data/json/npcs/refugee_center/beggars/BEGGAR_2_Dino_Dave.json b/data/json/npcs/refugee_center/beggars/BEGGAR_2_Dino_Dave.json index a9aa76199d58c..2849caa2ee17d 100644 --- a/data/json/npcs/refugee_center/beggars/BEGGAR_2_Dino_Dave.json +++ b/data/json/npcs/refugee_center/beggars/BEGGAR_2_Dino_Dave.json @@ -242,7 +242,7 @@ { "id": "MISSION_BEGGAR_2_BOX_SMALL", "type": "mission_definition", - "name": "Bring Dino Dave small cardboard boxes.", + "name": "Bring 40 small cardboard boxes", "difficulty": 1, "value": 0, "goal": "MGOAL_FIND_ITEM", @@ -265,7 +265,7 @@ { "id": "MISSION_BEGGAR_2_DUCT_TAPE", "type": "mission_definition", - "name": "Bring Dino Dave a roll of duct tape.", + "name": "Bring a roll of duct tape", "difficulty": 1, "value": 0, "goal": "MGOAL_FIND_ITEM", @@ -287,7 +287,7 @@ { "id": "MISSION_BEGGAR_2_BOX_MEDIUM", "type": "mission_definition", - "name": "Bring Dino Dave medium-sized cardboard boxes", + "name": "Bring 10 medium-sized cardboard boxes", "difficulty": 1, "value": 0, "goal": "MGOAL_FIND_ITEM", @@ -310,7 +310,7 @@ { "id": "MISSION_BEGGAR_2_PLASTIC_SHEET", "type": "mission_definition", - "name": "Bring Dino Dave some large plastic sheets", + "name": "Bring 10 large plastic sheets", "difficulty": 1, "value": 0, "goal": "MGOAL_FIND_ITEM", @@ -333,7 +333,7 @@ { "id": "MISSION_BEGGAR_2_BOX_LARGE", "type": "mission_definition", - "name": "Bring Dino Dave large cardboard boxes", + "name": "Bring 5 large cardboard boxes", "difficulty": 1, "value": 0, "goal": "MGOAL_FIND_ITEM", diff --git a/data/json/npcs/refugee_center/beggars/BEGGAR_3_Luo_Meizhen.json b/data/json/npcs/refugee_center/beggars/BEGGAR_3_Luo_Meizhen.json index d60d0ed004c46..66603d4293aa4 100644 --- a/data/json/npcs/refugee_center/beggars/BEGGAR_3_Luo_Meizhen.json +++ b/data/json/npcs/refugee_center/beggars/BEGGAR_3_Luo_Meizhen.json @@ -506,7 +506,7 @@ { "type": "talk_topic", "id": "TALK_REFUGEE_BEGGAR_3_DOCTORATE", - "dynamic_line": "Yeah, yeah, it's all very glamorous. Sure, I trained in the great ivory tower, got my PhD in mycology. Did my dissertation on signaling pathways in hyphae formation, and a postdoc in plant-fungus communication in rhyzomes. Then I got the job at the bookstore because there wasn't a ton of work for a doctor of mycology, although I'd had a few nibbles before things really got crazy. Now, people are just breaking down my door to get my sweet sweet knowledge of mold to help them fight the incoming zombie threat.", + "dynamic_line": "Yeah, yeah, it's all very glamorous. Sure, I trained in the great ivory tower, got my PhD in mycology. Did my dissertation on signaling pathways in hyphae formation, and a postdoc in plant-fungus communication in rhizomes. Then I got the job at the bookstore because there wasn't a ton of work for a doctor of mycology, although I'd had a few nibbles before things really got crazy. Now, people are just breaking down my door to get my sweet sweet knowledge of mold to help them fight the incoming zombie threat.", "responses": [ { "text": "Do you know about the fungal zombies though?", "topic": "TALK_REFUGEE_BEGGAR_3_MYCUS1" }, { "text": "What was it you were saying before?", "topic": "TALK_NONE" }, @@ -516,7 +516,7 @@ { "type": "talk_topic", "id": "TALK_REFUGEE_BEGGAR_3_DOCTORATE1", - "dynamic_line": "Heh. Yeah, that was a great use of my time. As you can see it really helped my employment prospects. Yeah, I have a PhD in mycology. Did my dissertation on signaling pathways in hyphae formation, and a postdoc in plant-fungus communication in rhyzomes. Then I got the job at the bookstore because there wasn't a ton of work for a doctor of mycology, although I'd had a few nibbles before things really got crazy. Now, people are just breaking down my door to get my sweet sweet knowledge of mold to help them fight the incoming zombie threat.", + "dynamic_line": "Heh. Yeah, that was a great use of my time. As you can see it really helped my employment prospects. Yeah, I have a PhD in mycology. Did my dissertation on signaling pathways in hyphae formation, and a postdoc in plant-fungus communication in rhizomes. Then I got the job at the bookstore because there wasn't a ton of work for a doctor of mycology, although I'd had a few nibbles before things really got crazy. Now, people are just breaking down my door to get my sweet sweet knowledge of mold to help them fight the incoming zombie threat.", "responses": [ { "text": "Do you know about the fungal zombies though?", "topic": "TALK_REFUGEE_BEGGAR_3_MYCUS1" }, { "text": "What was it you were saying before?", "topic": "TALK_NONE" }, diff --git a/data/json/npcs/refugee_center/beggars/BEGGAR_5_Yusuke_Taylor.json b/data/json/npcs/refugee_center/beggars/BEGGAR_5_Yusuke_Taylor.json index 704d183df9eeb..8cf7cec66ac32 100644 --- a/data/json/npcs/refugee_center/beggars/BEGGAR_5_Yusuke_Taylor.json +++ b/data/json/npcs/refugee_center/beggars/BEGGAR_5_Yusuke_Taylor.json @@ -170,7 +170,7 @@ { "type": "talk_topic", "id": "TALK_REFUGEE_BEGGAR_5_PAST_RHODE", - "dynamic_line": "You don't know? The governor went nuts, like a lot of people did leading up to the end, only he had a lot more power to work with. One day he just showed up to work with a militia of rowdies and loyalists and staged a coup, taking over the government completely, killing those that opposed him, and moving as many people as he could get behidn him onto the islands. The rumors I've heard is that most of them survived the cataclysm and are still running the show there, but that seems kind of impossible to me.", + "dynamic_line": "You don't know? The governor went nuts, like a lot of people did leading up to the end, only he had a lot more power to work with. One day he just showed up to work with a militia of rowdies and loyalists and staged a coup, taking over the government completely, killing those that opposed him, and moving as many people as he could get behind him onto the islands. The rumors I've heard is that most of them survived the cataclysm and are still running the show there, but that seems kind of impossible to me.", "responses": [ { "text": "Do you think you'd go back and look for your family?", "topic": "TALK_REFUGEE_BEGGAR_5_PAST_RETURN" }, { "text": "What were you saying before?", "topic": "TALK_NONE" }, diff --git a/data/json/npcs/refugee_center/surface_refugees/NPC_Aleesha_Seward.json b/data/json/npcs/refugee_center/surface_refugees/NPC_Aleesha_Seward.json index 0cfacc9d3d92c..e5e2436d7e2b1 100644 --- a/data/json/npcs/refugee_center/surface_refugees/NPC_Aleesha_Seward.json +++ b/data/json/npcs/refugee_center/surface_refugees/NPC_Aleesha_Seward.json @@ -68,7 +68,7 @@ "Hey there.", "Oh, hey, it's you again.", "What's up?", - "You're back, and still alive! Woah.", + "You're back, and still alive! Whoa.", "Aw hey, look who's back." ], "no": "Oh, uh... hi. You look new. I'm Aleesha." diff --git a/data/json/npcs/refugee_center/surface_refugees/NPC_Fatima_Al_Jadir.json b/data/json/npcs/refugee_center/surface_refugees/NPC_Fatima_Al_Jadir.json index 5c72c8c673a37..5acb66a9b16c9 100644 --- a/data/json/npcs/refugee_center/surface_refugees/NPC_Fatima_Al_Jadir.json +++ b/data/json/npcs/refugee_center/surface_refugees/NPC_Fatima_Al_Jadir.json @@ -120,7 +120,7 @@ { "type": "talk_topic", "id": "TALK_REFUGEE_Fatima_Background", - "dynamic_line": "Before I had just finished welding school actually, and was about to start looking for a job. That was fun, being a young Muslim woman in a Massachussets trade college, let me tell you.", + "dynamic_line": "Before I had just finished welding school actually, and was about to start looking for a job. That was fun, being a young Muslim woman in a Massachusetts trade college, let me tell you.", "responses": [ { "text": "Welding seems like a pretty non-traditional occupational choice; is there a story there?", @@ -136,7 +136,7 @@ { "type": "talk_topic", "id": "TALK_REFUGEE_Fatima_Cataclysm", - "dynamic_line": "I was on my way to visit my parents back in Burlington, waiting at a bus station, when the evacuation notices started sounding. I'd been a little out of touch for a bit and didn't realize how bad the rioting was getting. When my bus arrived it got repurposed into an evacuation vehicle, and took me here. I... I didn't used to be very religious, it's just something I was born to, but since this happened I've been thinking a lot more about God, and how grateful I am for His help in surviving. With things still so difficult, it helps to know He's got some plan for me.\n\nAnyway, mine was the second bus to arrive, and they were just getting some triage and processing stuff set up. I was put in charge of helping with the wounded, along with Uyen. Things went a little strange later on... one of the women doing triage and processing had a bit of a hang-up about particular, um, colors of people being allowed into the center. She claimed to have lost our 'papers', along with a lot of other peoples'. Thankfully because we'd helped so many we were able to argue that they could't leave us out, but there was no space left downstairs by the time we got that sorted, so here we are." + "dynamic_line": "I was on my way to visit my parents back in Burlington, waiting at a bus station, when the evacuation notices started sounding. I'd been a little out of touch for a bit and didn't realize how bad the rioting was getting. When my bus arrived it got repurposed into an evacuation vehicle, and took me here. I... I didn't used to be very religious, it's just something I was born to, but since this happened I've been thinking a lot more about God, and how grateful I am for His help in surviving. With things still so difficult, it helps to know He's got some plan for me.\n\nAnyway, mine was the second bus to arrive, and they were just getting some triage and processing stuff set up. I was put in charge of helping with the wounded, along with Uyen. Things went a little strange later on... one of the women doing triage and processing had a bit of a hang-up about particular, um, colors of people being allowed into the center. She claimed to have lost our 'papers', along with a lot of other peoples'. Thankfully because we'd helped so many we were able to argue that they couldn't leave us out, but there was no space left downstairs by the time we got that sorted, so here we are." }, { "type": "talk_topic", @@ -146,7 +146,7 @@ { "id": "MISSION_REFUGEE_Fatima_1", "type": "mission_definition", - "name": "Find a copy of the Quran for Fatima", + "name": "Find a copy of the Quran", "goal": "MGOAL_FIND_ITEM", "difficulty": 2, "value": 0, diff --git a/data/json/npcs/refugee_center/surface_refugees/NPC_Garry_Villeneuve.json b/data/json/npcs/refugee_center/surface_refugees/NPC_Garry_Villeneuve.json index 9b282d2ed3b9b..1e6212e6941f6 100644 --- a/data/json/npcs/refugee_center/surface_refugees/NPC_Garry_Villeneuve.json +++ b/data/json/npcs/refugee_center/surface_refugees/NPC_Garry_Villeneuve.json @@ -123,7 +123,7 @@ "id": "TALK_REFUGEE_Garry_1_firstmeet", "dynamic_line": "Nice to meet you too. Are you staying here, or something?", "responses": [ - { "text": "No, I'm a traveller. What's your story?", "topic": "TALK_REFUGEE_Garry_2_stub" }, + { "text": "No, I'm a traveler. What's your story?", "topic": "TALK_REFUGEE_Garry_2_stub" }, { "text": "Nope, in fact I'm leaving right now.", "topic": "TALK_DONE" } ] }, diff --git a/data/json/npcs/refugee_center/surface_refugees/NPC_Jenny_Forcette.json b/data/json/npcs/refugee_center/surface_refugees/NPC_Jenny_Forcette.json index 0608706a89f86..f54790cf3f2a2 100644 --- a/data/json/npcs/refugee_center/surface_refugees/NPC_Jenny_Forcette.json +++ b/data/json/npcs/refugee_center/surface_refugees/NPC_Jenny_Forcette.json @@ -148,7 +148,7 @@ { "type": "talk_topic", "id": "TALK_REFUGEE_JENNY_Refugees1_veryearly", - "dynamic_line": "I don't know the other folks very well yet. There's Boris, Garry, and Stan, they seem to keep to each other. They've gone through something, but I haven't pried. Dana and her husband lost their baby, that was a big deal right when they arrived. There's that counsellor lady with the unusual name, she's nice enough. Fatima just showed up a little while ago, but I've been trying to get to know her better, I think we've at least got our professional stuff in common a bit. I haven't really spoken much to anyone else.", + "dynamic_line": "I don't know the other folks very well yet. There's Boris, Garry, and Stan, they seem to keep to each other. They've gone through something, but I haven't pried. Dana and her husband lost their baby, that was a big deal right when they arrived. There's that counselor lady with the unusual name, she's nice enough. Fatima just showed up a little while ago, but I've been trying to get to know her better, I think we've at least got our professional stuff in common a bit. I haven't really spoken much to anyone else.", "responses": [ { "text": "What was that you said about living here?", "topic": "TALK_REFUGEE_JENNY_Personal1_veryearly" }, { "text": "I'd better get going.", "topic": "TALK_DONE" } @@ -263,7 +263,7 @@ { "type": "talk_topic", "id": "TALK_REFUGEE_JENNY_FreeMerchants", - "dynamic_line": "They run this place, and they don't run a charity. We get paid for working around the place, maintaining it, what have you, and we trade cash for food. The thing is, supply and demand and all... there's a lot more cash than food around. It's easier to buy a laptop than a piece of beef jerky, and there's no sign of that getting better. The balance is way off right now, a hard day of work barely gets you enough to fill your belly. I shouldn't bitch too much though. I don't know much better way to run it, although rumour is that the folks living downstairs have it a lot easier than we do. I try not to think too much on that.", + "dynamic_line": "They run this place, and they don't run a charity. We get paid for working around the place, maintaining it, what have you, and we trade cash for food. The thing is, supply and demand and all... there's a lot more cash than food around. It's easier to buy a laptop than a piece of beef jerky, and there's no sign of that getting better. The balance is way off right now, a hard day of work barely gets you enough to fill your belly. I shouldn't bitch too much though. I don't know much better way to run it, although rumor is that the folks living downstairs have it a lot easier than we do. I try not to think too much on that.", "responses": [ { "text": "Can you tell me about Fatima?", "topic": "TALK_REFUGEE_JENNY_Refugees2_Fatima" }, { @@ -297,7 +297,7 @@ { "type": "talk_topic", "id": "TALK_REFUGEE_JENNY_Refugees2_Friends", - "dynamic_line": "Well, Dana lost her baby right after , in a bus rollover. She was lucky to make it out alive. She and Pedro had one of the rougher trips here, I guess. We just kinda click as friends, I'm grateful there's someone else here I can really get along with. Her husband, Pedro, is still pretty shellshocked. He doesn't talk much. I like him though, when he opens up he's just hilarious. Draco is just a cantankerous old fart who hasn't actually got old yet, give him twenty years and he'll be there. I like grumpy people. We also have pretty similar taste in music. Aleesha's a sweet kid, and we've all kind of adopted her, but she seems to hang out with me and Dana the most. She's a great artist, and she's full of crazy ideas. I guess I like her because of all of us, she seems to have the most hope that there's a future to be had.", + "dynamic_line": "Well, Dana lost her baby right after , in a bus rollover. She was lucky to make it out alive. She and Pedro had one of the rougher trips here, I guess. We just kinda click as friends, I'm grateful there's someone else here I can really get along with. Her husband, Pedro, is still pretty shell-shocked. He doesn't talk much. I like him though, when he opens up he's just hilarious. Draco is just a cantankerous old fart who hasn't actually got old yet, give him twenty years and he'll be there. I like grumpy people. We also have pretty similar taste in music. Aleesha's a sweet kid, and we've all kind of adopted her, but she seems to hang out with me and Dana the most. She's a great artist, and she's full of crazy ideas. I guess I like her because of all of us, she seems to have the most hope that there's a future to be had.", "responses": [ { "text": "Can you tell me about the Free Merchants?", "topic": "TALK_REFUGEE_JENNY_FreeMerchants" }, { "text": "Can you tell me about Fatima?", "topic": "TALK_REFUGEE_JENNY_Refugees2_Fatima" }, @@ -374,7 +374,7 @@ { "id": "MISSION_REFUGEE_Jenny_GET_MOTOR", "type": "mission_definition", - "name": "Bring Jenny a motor for her compressor.", + "name": "Find an electric motor", "difficulty": 1, "value": 0, "goal": "MGOAL_FIND_ITEM", @@ -396,7 +396,7 @@ { "id": "MISSION_REFUGEE_Jenny_GET_TANK", "type": "mission_definition", - "name": "Bring Jenny a tank for her compressor.", + "name": "Bring back a 60L metal tank", "difficulty": 1, "value": 0, "goal": "MGOAL_FIND_ITEM", diff --git a/data/json/npcs/refugee_center/surface_refugees/NPC_Mangalpreet_Singh.json b/data/json/npcs/refugee_center/surface_refugees/NPC_Mangalpreet_Singh.json index 37f5fec525a77..3d10182337b16 100644 --- a/data/json/npcs/refugee_center/surface_refugees/NPC_Mangalpreet_Singh.json +++ b/data/json/npcs/refugee_center/surface_refugees/NPC_Mangalpreet_Singh.json @@ -127,7 +127,7 @@ "id": "TALK_REFUGEE_Mangalpreet_1_firstmeet", "dynamic_line": "Yes, I am glad to meet you too. Will you be staying with us? I thought they were taking no more refugees.", "responses": [ - { "text": "I'm a traveller actually. Just had some questions.", "topic": "TALK_REFUGEE_Mangalpreet_2_stub" }, + { "text": "I'm a traveler actually. Just had some questions.", "topic": "TALK_REFUGEE_Mangalpreet_2_stub" }, { "text": "Actually I'm just heading out.", "topic": "TALK_DONE" } ] }, diff --git a/data/json/npcs/refugee_center/surface_refugees/NPC_Rhyzaea_Johnny.json b/data/json/npcs/refugee_center/surface_refugees/NPC_Rhyzaea_Johnny.json index b92fd7046c213..3656520dd08d6 100644 --- a/data/json/npcs/refugee_center/surface_refugees/NPC_Rhyzaea_Johnny.json +++ b/data/json/npcs/refugee_center/surface_refugees/NPC_Rhyzaea_Johnny.json @@ -17,7 +17,7 @@ "name": "Refugee", "job_description": "I'm just trying to survive.", "common": false, - "//": "A counsellor from far away, Rhy can offer a lot of supports to player and survivors if she can get herself sorted a little", + "//": "A counselor from far away, Rhy can offer a lot of supports to player and survivors if she can get herself sorted a little", "bonus_str": { "rng": [ -1, 1 ] }, "bonus_dex": { "rng": [ -2, 2 ] }, "bonus_int": { "rng": [ 0, 3 ] }, @@ -101,7 +101,7 @@ "id": "TALK_REFUGEE_Rhyzaea_1_firstmeet", "dynamic_line": "So, what's your story? We don't see a lot of new people back here.", "responses": [ - { "text": "Just a curious traveller. What's up with you?", "topic": "TALK_REFUGEE_Rhyzaea_2" }, + { "text": "Just a curious traveler. What's up with you?", "topic": "TALK_REFUGEE_Rhyzaea_2" }, { "text": "Actually I'm just heading out.", "topic": "TALK_DONE" } ] }, @@ -123,7 +123,7 @@ { "type": "talk_topic", "id": "TALK_REFUGEE_Rhyzaea_Background2", - "dynamic_line": "I was a counsellor actually, I worked for my band, Gitxsan. Did a lot of mental health and addictions for people that had been through some really tough stuff. Maybe not zombies eating your child level tough, but surprisingly not far off. My people have gone through some real messy crap.", + "dynamic_line": "I was a counselor actually, I worked for my band, Gitxsan. Did a lot of mental health and addictions for people that had been through some really tough stuff. Maybe not zombies eating your child level tough, but surprisingly not far off. My people have gone through some real messy crap.", "responses": [ { "text": "Tell me about your family.", "topic": "TALK_REFUGEE_Rhyzaea_Family" }, { "text": "Tell me about your band.", "topic": "TALK_REFUGEE_Rhyzaea_Band" }, diff --git a/data/json/npcs/refugee_center/surface_refugees/NPC_Uyen_Tran.json b/data/json/npcs/refugee_center/surface_refugees/NPC_Uyen_Tran.json index 6cb63a82394e2..1fe00fc3d0ab5 100644 --- a/data/json/npcs/refugee_center/surface_refugees/NPC_Uyen_Tran.json +++ b/data/json/npcs/refugee_center/surface_refugees/NPC_Uyen_Tran.json @@ -103,7 +103,7 @@ "id": "TALK_REFUGEE_Uyen_1_firstmeet", "dynamic_line": "What brings you around here? We don't see a lot of new faces.", "responses": [ - { "text": "Just a traveller. Can I ask you a few things?", "topic": "TALK_REFUGEE_Uyen_2" }, + { "text": "Just a traveler. Can I ask you a few things?", "topic": "TALK_REFUGEE_Uyen_2" }, { "text": "Actually I'm just heading out.", "topic": "TALK_DONE" } ] }, @@ -154,7 +154,7 @@ { "id": "MISSION_REFUGEE_Uyen_2", "type": "mission_definition", - "name": "Find 6 bottles of prozac for Uyen", + "name": "Find 6 bottles of Prozac", "goal": "MGOAL_FIND_ITEM", "difficulty": 2, "value": 0, diff --git a/data/json/npcs/refugee_center/surface_refugees/NPC_Vanessa_Toby.json b/data/json/npcs/refugee_center/surface_refugees/NPC_Vanessa_Toby.json index fd53cc108dcc4..b62abecd2af5f 100644 --- a/data/json/npcs/refugee_center/surface_refugees/NPC_Vanessa_Toby.json +++ b/data/json/npcs/refugee_center/surface_refugees/NPC_Vanessa_Toby.json @@ -221,7 +221,7 @@ { "id": "MISSION_REFUGEE_Vanessa_1", "type": "mission_definition", - "name": "Find a haircut kit for Vanessa", + "name": "Make a makeshift haircut kit", "goal": "MGOAL_FIND_ITEM", "difficulty": 2, "value": 0, diff --git a/data/json/npcs/refugee_center/surface_staff/NPC_free_merchant_shopkeep.json b/data/json/npcs/refugee_center/surface_staff/NPC_free_merchant_shopkeep.json index 767b1e7822809..50908c863fa37 100644 --- a/data/json/npcs/refugee_center/surface_staff/NPC_free_merchant_shopkeep.json +++ b/data/json/npcs/refugee_center/surface_staff/NPC_free_merchant_shopkeep.json @@ -389,7 +389,7 @@ { "id": "TALK_EVAC_MERCHANT_RANCH", "type": "talk_topic", - "dynamic_line": "One of the people that got evacuated here was actually on a charter bus taking him back to his ranch, 'til it was commandeered to be an evacuation vehicle and brought him here. Once the dust shook out we made a deal to get him home and provide him with labour in return for making the ranch into a subsidiary of our dealings here. It worked out pretty well for everyone, most of the people with skills for that kind of work are already out there.", + "dynamic_line": "One of the people that got evacuated here was actually on a charter bus taking him back to his ranch, 'til it was commandeered to be an evacuation vehicle and brought him here. Once the dust shook out we made a deal to get him home and provide him with labor in return for making the ranch into a subsidiary of our dealings here. It worked out pretty well for everyone, most of the people with skills for that kind of work are already out there.", "responses": [ { "text": "Didn't that free up some space for the beggars and people stuck upstairs?", diff --git a/data/json/npcs/refugee_center/surface_visitors/NPC_arsonist.json b/data/json/npcs/refugee_center/surface_visitors/NPC_arsonist.json index 7d7684a537c93..41354c7327a33 100644 --- a/data/json/npcs/refugee_center/surface_visitors/NPC_arsonist.json +++ b/data/json/npcs/refugee_center/surface_visitors/NPC_arsonist.json @@ -34,7 +34,7 @@ { "type": "talk_topic", "id": "TALK_ARSONIST_NEW", - "dynamic_line": "Guess that makes two of us. Well, kind of. I don't think we're open, though. Full up as hell; it's almost a crowd downstairs. Did you see the trader at the enterance? There's the one to ask.", + "dynamic_line": "Guess that makes two of us. Well, kind of. I don't think we're open, though. Full up as hell; it's almost a crowd downstairs. Did you see the trader at the entrance? There's the one to ask.", "responses": [ { "text": "Sucks...", "topic": "TALK_ARSONIST" } ] }, { diff --git a/data/json/overmap/specials.json b/data/json/overmap/specials.json index 30fdfd9b2e5db..2770c160e0f1a 100644 --- a/data/json/overmap/specials.json +++ b/data/json/overmap/specials.json @@ -2646,5 +2646,47 @@ ], "city_sizes": [ 1, 20 ], "occurrences": [ 0, 5 ] + }, + { + "type": "overmap_special", + "id": "marina", + "overmaps": [ + { "point": [ -1, -1, 0 ], "overmap": "lake_surface", "locations": [ "lake_surface" ] }, + { "point": [ 0, -1, 0 ], "overmap": "lake_surface", "locations": [ "lake_surface" ] }, + { "point": [ 1, -1, 0 ], "overmap": "lake_surface", "locations": [ "lake_surface" ] }, + { "point": [ 2, -1, 0 ], "overmap": "lake_surface", "locations": [ "lake_surface" ] }, + { "point": [ 3, -1, 0 ], "overmap": "lake_surface", "locations": [ "lake_surface" ] }, + { "point": [ 4, -1, 0 ], "overmap": "lake_surface", "locations": [ "lake_surface" ] }, + { "point": [ 5, -1, 0 ], "overmap": "lake_surface", "locations": [ "lake_surface" ] }, + { "point": [ 5, 0, 0 ], "overmap": "lake_surface", "locations": [ "lake_surface" ] }, + { "point": [ 5, 1, 0 ], "overmap": "lake_surface", "locations": [ "lake_surface" ] }, + { "point": [ 5, 2, 0 ], "overmap": "lake_shore", "locations": [ "lake_shore" ] }, + { "point": [ -1, 2, 0 ], "overmap": "lake_shore", "locations": [ "lake_shore" ] }, + { "point": [ -1, 1, 0 ], "overmap": "lake_surface", "locations": [ "lake_surface" ] }, + { "point": [ -1, 0, 0 ], "overmap": "lake_surface", "locations": [ "lake_surface" ] }, + { "point": [ 4, 0, 0 ], "overmap": "marina_1_north", "locations": [ "lake_surface" ] }, + { "point": [ 3, 0, 0 ], "overmap": "marina_2_north", "locations": [ "lake_surface" ] }, + { "point": [ 2, 0, 0 ], "overmap": "marina_3_north", "locations": [ "lake_surface" ] }, + { "point": [ 1, 0, 0 ], "overmap": "marina_4_north", "locations": [ "lake_surface" ] }, + { "point": [ 0, 0, 0 ], "overmap": "marina_5_north", "locations": [ "lake_surface" ] }, + { "point": [ 4, 1, 0 ], "overmap": "marina_6_north", "locations": [ "lake_surface" ] }, + { "point": [ 3, 1, 0 ], "overmap": "marina_7_north", "locations": [ "lake_surface" ] }, + { "point": [ 2, 1, 0 ], "overmap": "marina_8_north", "locations": [ "lake_surface" ] }, + { "point": [ 1, 1, 0 ], "overmap": "marina_9_north", "locations": [ "lake_surface" ] }, + { "point": [ 0, 1, 0 ], "overmap": "marina_10_north", "locations": [ "lake_surface" ] }, + { "point": [ 4, 2, 0 ], "overmap": "marina_11_north", "locations": [ "lake_shore" ] }, + { "point": [ 3, 2, 0 ], "overmap": "marina_12_north", "locations": [ "lake_shore" ] }, + { "point": [ 2, 2, 0 ], "overmap": "marina_13_north", "locations": [ "lake_shore" ] }, + { "point": [ 1, 2, 0 ], "overmap": "marina_14_north", "locations": [ "lake_shore" ] }, + { "point": [ 0, 2, 0 ], "overmap": "marina_15_north", "locations": [ "lake_shore" ] }, + { "point": [ 4, 3, 0 ], "overmap": "marina_16_north", "locations": [ "land" ] }, + { "point": [ 3, 3, 0 ], "overmap": "marina_17_north", "locations": [ "land" ] }, + { "point": [ 2, 3, 0 ], "overmap": "marina_18_north", "locations": [ "land" ] }, + { "point": [ 1, 3, 0 ], "overmap": "marina_19_north", "locations": [ "land" ] }, + { "point": [ 0, 3, 0 ], "overmap": "marina_20_north", "locations": [ "land" ] } + ], + "city_sizes": [ 4, 20 ], + "occurrences": [ 0, 1 ], + "connections": [ { "point": [ 2, 4, 0 ], "terrain": "road", "connection": "local_road", "from": [ 2, 3, 0 ] } ] } ] diff --git a/data/json/overmap_terrain_recreational.json b/data/json/overmap_terrain_recreational.json index 080525d84f188..f8c6b204b4444 100644 --- a/data/json/overmap_terrain_recreational.json +++ b/data/json/overmap_terrain_recreational.json @@ -1133,5 +1133,165 @@ "color": "white", "see_cost": 1, "flags": [ "LAKE_SHORE" ] + }, + { + "type": "overmap_terrain", + "id": "marina_1", + "copy-from": "generic_city_building_no_sidewalk", + "name": "marina", + "color": "cyan", + "sym": "~" + }, + { + "type": "overmap_terrain", + "id": "marina_2", + "copy-from": "generic_city_building_no_sidewalk", + "name": "marina", + "color": "cyan", + "sym": "~" + }, + { + "type": "overmap_terrain", + "id": "marina_3", + "copy-from": "generic_city_building_no_sidewalk", + "name": "marina", + "color": "cyan", + "sym": "~" + }, + { + "type": "overmap_terrain", + "id": "marina_4", + "copy-from": "generic_city_building_no_sidewalk", + "name": "marina", + "color": "cyan", + "sym": "~" + }, + { + "type": "overmap_terrain", + "id": "marina_5", + "copy-from": "generic_city_building_no_sidewalk", + "name": "marina", + "color": "cyan", + "sym": "~" + }, + { + "type": "overmap_terrain", + "id": "marina_6", + "copy-from": "generic_city_building_no_sidewalk", + "name": "marina", + "color": "cyan", + "sym": "~" + }, + { + "type": "overmap_terrain", + "id": "marina_7", + "copy-from": "generic_city_building_no_sidewalk", + "name": "marina", + "color": "cyan", + "sym": "~" + }, + { + "type": "overmap_terrain", + "id": "marina_8", + "copy-from": "generic_city_building_no_sidewalk", + "name": "marina", + "color": "cyan", + "sym": "~" + }, + { + "type": "overmap_terrain", + "id": "marina_9", + "copy-from": "generic_city_building_no_sidewalk", + "name": "marina", + "color": "cyan", + "sym": "~" + }, + { + "type": "overmap_terrain", + "id": "marina_10", + "copy-from": "generic_city_building_no_sidewalk", + "name": "marina", + "color": "cyan", + "sym": "~" + }, + { + "type": "overmap_terrain", + "id": "marina_11", + "copy-from": "generic_city_building_no_sidewalk", + "name": "marina", + "color": "cyan", + "sym": "m" + }, + { + "type": "overmap_terrain", + "id": "marina_12", + "copy-from": "generic_city_building_no_sidewalk", + "name": "marina", + "color": "cyan", + "sym": "m" + }, + { + "type": "overmap_terrain", + "id": "marina_13", + "copy-from": "generic_city_building_no_sidewalk", + "name": "marina", + "color": "cyan", + "sym": "m" + }, + { + "type": "overmap_terrain", + "id": "marina_14", + "copy-from": "generic_city_building_no_sidewalk", + "name": "marina", + "color": "cyan", + "sym": "m" + }, + { + "type": "overmap_terrain", + "id": "marina_15", + "copy-from": "generic_city_building_no_sidewalk", + "name": "marina", + "color": "cyan", + "sym": "m" + }, + { + "type": "overmap_terrain", + "id": "marina_16", + "copy-from": "generic_city_building_no_sidewalk", + "name": "marina parking", + "color": "cyan", + "sym": "m" + }, + { + "type": "overmap_terrain", + "id": "marina_17", + "copy-from": "generic_city_building_no_sidewalk", + "name": "marina parking", + "color": "cyan", + "sym": "m" + }, + { + "type": "overmap_terrain", + "id": "marina_18", + "copy-from": "generic_city_building_no_sidewalk", + "name": "marina parking", + "color": "cyan", + "sym": "M" + }, + { + "type": "overmap_terrain", + "id": "marina_19", + "copy-from": "generic_city_building_no_sidewalk", + "name": "marina parking", + "color": "cyan", + "sym": "m" + }, + { + "type": "overmap_terrain", + "id": "marina_20", + "copy-from": "generic_city_building_no_sidewalk", + "name": "marina parking", + "color": "cyan", + "sym": "m" } ] diff --git a/data/json/player_activities.json b/data/json/player_activities.json index 878019a6ca29f..b40ee75d09e91 100644 --- a/data/json/player_activities.json +++ b/data/json/player_activities.json @@ -40,7 +40,8 @@ "verb": "waiting", "rooted": true, "based_on": "time", - "no_resume": true + "no_resume": true, + "refuel_fires": true }, { "id": "ACT_CRAFT", diff --git a/data/json/professions.json b/data/json/professions.json index e97d6a03ef5f5..fb1f5b99601a1 100644 --- a/data/json/professions.json +++ b/data/json/professions.json @@ -1003,8 +1003,8 @@ ], "entries": [ { "group": "charged_cell_phone" }, - { "item": "l_enforcer_45", "ammo-item": "45_acp", "charges": 6, "container-item": "sholster" }, - { "item": "45_acp", "charges": 24 } + { "item": "l_enforcer_45", "ammo-item": "460_rowland", "charges": 6, "container-item": "sholster" }, + { "item": "460_rowland", "charges": 24 } ] }, "male": [ "briefs" ], diff --git a/data/json/recipes/basecamps/recipe_groups.json b/data/json/recipes/basecamps/recipe_groups.json index 48572ecd51ebd..957cc81ed16d0 100644 --- a/data/json/recipes/basecamps/recipe_groups.json +++ b/data/json/recipes/basecamps/recipe_groups.json @@ -1,13 +1,22 @@ [ + { + "type": "recipe_group", + "name": "all_faction_base_types", + "building_type": "NONE", + "recipes": [ + { "id": "faction_base_modular_field_0", "description": "Field Camp", "om_terrains": [ "field" ] }, + { "id": "faction_base_camp_0", "description": "Old Camp", "om_terrains": [ "field" ] } + ] + }, { "type": "recipe_group", "name": "all_faction_base_expansions", "building_type": "NONE", "recipes": [ - { "id": "faction_base_farm_0", "description": "Farm" }, - { "id": "faction_base_garage_0", "description": "Garage" }, - { "id": "faction_base_kitchen_0", "description": "Kitchen" }, - { "id": "faction_base_blacksmith_0", "description": "Blacksmith Shop" } + { "id": "faction_base_farm_0", "description": "Farm", "om_terrains": [ "field" ] }, + { "id": "faction_base_garage_0", "description": "Garage", "om_terrains": [ "field" ] }, + { "id": "faction_base_kitchen_0", "description": "Kitchen", "om_terrains": [ "field" ] }, + { "id": "faction_base_blacksmith_0", "description": "Blacksmith Shop", "om_terrains": [ "field" ] } ] }, { diff --git a/data/json/recipes/basecamps/recipe_modular_field_common.json b/data/json/recipes/basecamps/recipe_modular_field_common.json new file mode 100644 index 0000000000000..511374176bb60 --- /dev/null +++ b/data/json/recipes/basecamps/recipe_modular_field_common.json @@ -0,0 +1,637 @@ +[ + { + "type": "recipe", + "result": "faction_base_modular_field_0", + "description": "We should survey the base site and set up a bulletin board.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "skill_used": "fabrication", + "autolearn": false, + "never_learn": true, + "time": "1 h", + "construction_blueprint": "fbmf_0", + "blueprint_provides": [ + { "id": "gathering" }, + { "id": "primitive_camp_recipes_1" }, + { "id": "firewood" }, + { "id": "foraging" }, + { "id": "sorting" }, + { "id": "logging" } + ], + "blueprint_requires": [ { "id": "not_an_upgrade" } ], + "blueprint_name": "basic survey" + }, + { + "type": "recipe", + "result": "faction_base_modular_field_fireplace_northeast", + "description": "Now that we have some cover, we should build a fireplace in the northeast shack.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_fireplace_northeast", + "blueprint_name": "northeast fireplace", + "blueprint_requires": [ { "id": "fbmf_northeast" } ], + "blueprint_provides": [ { "id": "fbmf_fire_northeast" } ], + "blueprint_excludes": [ { "id": "fbmf_fire_northeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_brazier_northeast", + "description": "Now that we have some cover, we should set up a brazier in the northeast shack.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "skill_used": "fabrication", + "autolearn": false, + "never_learn": true, + "time": "5 m", + "construction_blueprint": "fbmf_brazier_northeast", + "blueprint_name": "northeast brazier", + "blueprint_requires": [ { "id": "fbmf_northeast" } ], + "blueprint_provides": [ { "id": "fbmf_fire_northeast" } ], + "blueprint_excludes": [ { "id": "fbmf_fire_northeast" } ], + "components": [ [ [ "brazier", 1 ] ] ] + }, + { + "type": "recipe", + "result": "faction_base_modular_field_stove_northeast", + "description": "Now that we have some cover, we should build a stove in the northeast shack.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "skill_used": "fabrication", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_stove_northeast", + "blueprint_name": "northeast stove", + "blueprint_requires": [ { "id": "fbmf_northeast" } ], + "blueprint_provides": [ { "id": "fbmf_fire_northeast" } ], + "blueprint_excludes": [ { "id": "fbmf_fire_northeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_strawbed1_northeast", + "description": "A straw bed in the northeast shack will make sleeping easier.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_strawbed1_northeast", + "blueprint_name": "northeast straw bed", + "blueprint_requires": [ { "id": "fbmf_northeast" } ], + "blueprint_provides": [ { "id": "bed" }, { "id": "fbmf_bed1_northeast" } ], + "blueprint_excludes": [ { "id": "fbmf_bed1_northeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_bed1_northeast", + "description": "A proper bed in the northeast shack will give one of us a place to sleep soundly.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_bed1_northeast", + "blueprint_name": "northeast bed", + "blueprint_requires": [ { "id": "fbmf_northeast" } ], + "blueprint_provides": [ { "id": "bed" }, { "id": "fbmf_bed1_northeast" } ], + "blueprint_excludes": [ { "id": "fbmf_bed1_northeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_strawbed2_northeast", + "description": "Another straw bed in the northeast shack will make sleeping easier.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_strawbed2_northeast", + "blueprint_name": "northeast straw bed", + "blueprint_requires": [ { "id": "fbmf_bed1_northeast" }, { "id": "fbmf_northeast", "amount": 2 } ], + "blueprint_provides": [ { "id": "bed" }, { "id": "fbmf_bed2_northeast" } ], + "blueprint_excludes": [ { "id": "fbmf_bed2_northeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_bed2_northeast", + "description": "Another proper bed in the northeast shack will give one of us a place to sleep soundly.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_bed2_northeast", + "blueprint_name": "northeast bed", + "blueprint_requires": [ { "id": "fbmf_bed1_northeast" }, { "id": "fbmf_northeast", "amount": 2 } ], + "blueprint_provides": [ { "id": "bed" }, { "id": "fbmf_bed2_northeast" } ], + "blueprint_excludes": [ { "id": "fbmf_bed2_northeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_tent_strawbed3_east", + "description": "A pair of straw beds in the east tent will allow us to house two more people and expand the camp.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_tent_strawbed3_east", + "blueprint_name": "east straw beds", + "blueprint_requires": [ { "id": "fbmf_tent_east" } ], + "blueprint_provides": [ { "id": "bed", "amount": 2 }, { "id": "fbmf_bed1_east" }, { "id": "fbmf_bed2_east" } ], + "blueprint_excludes": [ { "id": "fbmf_bed1_east" }, { "id": "fbmf_bed2_east" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_tent_bed3_east", + "description": "A pair of proper beds in the east tent will allow us to house two more people and expand the camp.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_tent_bed3_east", + "blueprint_name": "east beds", + "blueprint_requires": [ { "id": "fbmf_tent_east" } ], + "blueprint_provides": [ { "id": "bed", "amount": 2 }, { "id": "fbmf_bed1_east" }, { "id": "fbmf_bed2_east" } ], + "blueprint_excludes": [ { "id": "fbmf_bed1_east" }, { "id": "fbmf_bed2_east" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_strawbed3_east", + "description": "A pair of straw beds in the east room will allow us to house two more people and expand the camp.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room_strawbed3_east", + "blueprint_name": "east straw beds", + "blueprint_requires": [ { "id": "fbmf_east", "amount": 4 } ], + "blueprint_provides": [ { "id": "bed", "amount": 2 }, { "id": "fbmf_bed1_east" }, { "id": "fbmf_bed2_east" } ], + "blueprint_excludes": [ { "id": "fbmf_bed1_east" }, { "id": "fbmf_bed2_east" }, { "id": "fbmf_tent_east" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room_bed3_east", + "description": "A pair of proper beds in the east room will allow us to house two more people and expand the camp.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room_bed3_east", + "blueprint_name": "east beds", + "blueprint_requires": [ { "id": "fbmf_east", "amount": 4 } ], + "blueprint_provides": [ { "id": "bed", "amount": 2 }, { "id": "fbmf_bed1_east" }, { "id": "fbmf_bed2_east" } ], + "blueprint_excludes": [ { "id": "fbmf_bed1_east" }, { "id": "fbmf_bed2_east" }, { "id": "fbmf_tent_east" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_tent_strawbed3_southeast", + "description": "A pair of straw beds in the southeast tent will allow us to house two more people and expand the camp.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_tent_strawbed3_southeast", + "blueprint_name": "southeast straw beds", + "blueprint_requires": [ { "id": "fbmf_tent_southeast" } ], + "blueprint_provides": [ { "id": "bed", "amount": 2 }, { "id": "fbmf_bed1_southeast" }, { "id": "fbmf_bed2_southeast" } ], + "blueprint_excludes": [ { "id": "fbmf_bed1_southeast" }, { "id": "fbmf_bed2_southeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_tent_bed3_southeast", + "description": "A pair of proper beds in the southeast tent will allow us to house two more people and expand the camp.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_tent_bed3_southeast", + "blueprint_name": "southeast beds", + "blueprint_requires": [ { "id": "fbmf_tent_southeast" } ], + "blueprint_provides": [ { "id": "bed", "amount": 2 }, { "id": "fbmf_bed1_southeast" }, { "id": "fbmf_bed2_southeast" } ], + "blueprint_excludes": [ { "id": "fbmf_bed1_southeast" }, { "id": "fbmf_bed2_southeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room_strawbed3_southeast", + "description": "A pair of straw beds in the southeast room will allow us to house two more people and expand the camp.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room_strawbed3_southeast", + "blueprint_name": "southeast straw beds", + "blueprint_requires": [ { "id": "fbmf_southeast", "amount": 4 } ], + "blueprint_provides": [ { "id": "bed", "amount": 2 }, { "id": "fbmf_bed1_southeast" }, { "id": "fbmf_bed2_southeast" } ], + "blueprint_excludes": [ { "id": "fbmf_bed1_southeast" }, { "id": "fbmf_bed2_southeast" }, { "id": "fbmf_tent_southeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room_bed3_southeast", + "description": "A pair of proper beds in the southeast room will allow us to house two more people and expand the camp.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room_bed3_southeast", + "blueprint_name": "southeast beds", + "blueprint_requires": [ { "id": "fbmf_southeast", "amount": 4 } ], + "blueprint_provides": [ { "id": "bed", "amount": 2 }, { "id": "fbmf_bed1_southeast" }, { "id": "fbmf_bed2_southeast" } ], + "blueprint_excludes": [ { "id": "fbmf_bed1_southeast" }, { "id": "fbmf_bed2_southeast" }, { "id": "fbmf_tent_southeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_strawbed3_northwest", + "description": "A pair of straw beds in the northwest building will allow us to house two more people and expand the camp.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_strawbed3_northwest", + "blueprint_name": "northwest straw beds", + "blueprint_requires": [ { "id": "fbmf_northwest", "amount": 4 } ], + "blueprint_provides": [ { "id": "bed", "amount": 2 }, { "id": "fbmf_bed1_northwest" }, { "id": "fbmf_bed2_northwest" } ], + "blueprint_excludes": [ { "id": "fbmf_bed1_northwest" }, { "id": "fbmf_bed2_northwest" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_bed3_northwest", + "description": "A pair of proper beds in the northwest building will allow us to house two more people and expand the camp.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_bed3_northwest", + "blueprint_name": "northwest beds", + "blueprint_requires": [ { "id": "fbmf_northwest", "amount": 4 } ], + "blueprint_provides": [ { "id": "bed", "amount": 2 }, { "id": "fbmf_bed1_northwest" }, { "id": "fbmf_bed2_northwest" } ], + "blueprint_excludes": [ { "id": "fbmf_bed1_northwest" }, { "id": "fbmf_bed2_northwest" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_tent_strawbed3_west", + "description": "A pair of straw beds in the west tent will allow us to house two more people and expand the camp.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_tent_strawbed3_west", + "blueprint_name": "west straw beds", + "blueprint_requires": [ { "id": "fbmf_tent_west" } ], + "blueprint_provides": [ { "id": "bed", "amount": 2 }, { "id": "fbmf_bed1_west" }, { "id": "fbmf_bed2_west" } ], + "blueprint_excludes": [ { "id": "fbmf_bed1_west" }, { "id": "fbmf_bed2_west" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_tent_bed3_west", + "description": "A pair of proper beds in the west tent will allow us to house two more people and expand the camp.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_tent_bed3_west", + "blueprint_name": "west beds", + "blueprint_requires": [ { "id": "fbmf_tent_west" } ], + "blueprint_provides": [ { "id": "bed", "amount": 2 }, { "id": "fbmf_bed1_west" }, { "id": "fbmf_bed2_west" } ], + "blueprint_excludes": [ { "id": "fbmf_bed1_west" }, { "id": "fbmf_bed2_west" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room_strawbed3_west", + "description": "A pair of straw beds in the west room will allow us to house two more people and expand the camp.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room_strawbed3_west", + "blueprint_name": "west straw beds", + "blueprint_requires": [ { "id": "fbmf_west", "amount": 4 } ], + "blueprint_provides": [ { "id": "bed", "amount": 2 }, { "id": "fbmf_bed1_west" }, { "id": "fbmf_bed2_west" } ], + "blueprint_excludes": [ { "id": "fbmf_bed1_west" }, { "id": "fbmf_bed2_west" }, { "id": "fbmf_tent_west" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room_bed3_west", + "description": "A pair of proper beds in the west room will allow us to house two more people and expand the camp.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room_bed3_west", + "blueprint_name": "west beds", + "blueprint_requires": [ { "id": "fbmf_west", "amount": 4 } ], + "blueprint_provides": [ { "id": "bed", "amount": 2 }, { "id": "fbmf_bed1_west" }, { "id": "fbmf_bed2_west" } ], + "blueprint_excludes": [ { "id": "fbmf_bed1_west" }, { "id": "fbmf_bed2_west" }, { "id": "fbmf_tent_west" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_tent_strawbed3_southwest", + "description": "A pair of straw beds in the southwest tent will allow us to house two more people and expand the camp.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_tent_strawbed3_southwest", + "blueprint_name": "southwest straw beds", + "blueprint_requires": [ { "id": "fbmf_tent_southwest" } ], + "blueprint_provides": [ { "id": "bed", "amount": 2 }, { "id": "fbmf_bed1_southwest" }, { "id": "fbmf_bed2_southwest" } ], + "blueprint_excludes": [ { "id": "fbmf_bed1_southwest" }, { "id": "fbmf_bed2_southwest" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_tent_bed3_southwest", + "description": "A pair of proper beds in the southwest tent will allow us to house two more people and expand the camp.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_tent_bed3_southwest", + "blueprint_name": "southwest beds", + "blueprint_requires": [ { "id": "fbmf_tent_southwest" } ], + "blueprint_provides": [ { "id": "bed", "amount": 2 }, { "id": "fbmf_bed1_southwest" }, { "id": "fbmf_bed2_southwest" } ], + "blueprint_excludes": [ { "id": "fbmf_bed1_southwest" }, { "id": "fbmf_bed2_southwest" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room_strawbed3_southwest", + "description": "A pair of straw beds in the southwest room will allow us to house two more people and expand the camp.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room_strawbed3_southwest", + "blueprint_name": "southwest straw beds", + "blueprint_requires": [ { "id": "fbmf_southwest", "amount": 4 } ], + "blueprint_provides": [ { "id": "bed", "amount": 2 }, { "id": "fbmf_bed1_southwest" }, { "id": "fbmf_bed2_southwest" } ], + "blueprint_excludes": [ { "id": "fbmf_bed1_southwest" }, { "id": "fbmf_bed2_southwest" }, { "id": "fbmf_tent_southwest" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room_bed3_southwest", + "description": "A pair of proper beds in the southwest room will allow us to house two more people and expand the camp.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room_bed3_southwest", + "blueprint_name": "southwest beds", + "blueprint_requires": [ { "id": "fbmf_southwest", "amount": 4 } ], + "blueprint_provides": [ { "id": "bed", "amount": 2 }, { "id": "fbmf_bed1_southwest" }, { "id": "fbmf_bed2_southwest" } ], + "blueprint_excludes": [ { "id": "fbmf_bed1_southwest" }, { "id": "fbmf_bed2_southwest" }, { "id": "fbmf_tent_southwest" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_kitchen_fireplace_center", + "description": "A fireplace, counter, and some pots and pans in the central building will allow us to cook simple recipes and organize hunting expeditions.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_kitchen_fireplace_center", + "blueprint_name": "central fireplace", + "blueprint_requires": [ { "id": "fbmf_center", "amount": 2 }, { "id": "fbmf_ne_center" } ], + "blueprint_provides": [ { "id": "trapping" }, { "id": "hunting" }, { "id": "kitchen" }, { "id": "kitchen_recipes_1" } ], + "blueprint_resources": [ "fake_fireplace", "pot" ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_kitchen_butchery_center", + "description": "We need a butchery rack to maximize the harvest from our hunting and trapping efforts.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_kitchen_butchery_center", + "blueprint_name": "central butchery rack", + "blueprint_requires": [ { "id": "fbmf_center", "amount": 2 }, { "id": "fbmf_ne_center" } ], + "blueprint_provides": [ { "id": "trapping" }, { "id": "hunting" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_kitchen_toolrack_center", + "description": "A tool rack in the central building will give us a place to store tools.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_kitchen_toolrack_center", + "blueprint_name": "central tool rack", + "blueprint_requires": [ { "id": "fbmf_center", "amount": 2 }, { "id": "fbmf_nw_center" } ], + "blueprint_provides": [ { "id": "tool_storage" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_kitchen_table_center", + "description": "Setting up some tables and chairs will make the central building into a dining area, and we can also use them as a workspace to organize the camp.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_kitchen_table_center", + "blueprint_name": "central dining hall", + "blueprint_requires": [ { "id": "fbmf_center", "amount": 4 }, { "id": "fbmf_ne_center" }, { "id": "fbmf_nw_center" } ], + "blueprint_provides": [ { "id": "relaying" }, { "id": "walls" }, { "id": "recruiting" }, { "id": "scouting" }, { "id": "patrolling" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_kitchen_table_south", + "description": "Setting up some tables and chairs will make the central building into a dining area, and we can also use them as a workspace to organize the camp.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "skill_used": "fabrication", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_kitchen_table_south", + "blueprint_name": "south dining hall", + "blueprint_requires": [ { "id": "fbmf_south", "amount": 4 }, { "id": "fbmf_se_south" }, { "id": "fbmf_sw_south" } ], + "blueprint_provides": [ { "id": "relaying" }, { "id": "walls" }, { "id": "recruiting" }, { "id": "scouting" }, { "id": "patrolling" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_kitchen_stove_south", + "description": "A wood stove, counter, and some pots and pans in the south half of the central building will allow us to cook simple recipes and organize hunting expeditions. The stove will be more efficient than a fireplace.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_kitchen_stove_south", + "blueprint_name": "south wood stove", + "blueprint_resources": [ "fake_stove" ], + "blueprint_requires": [ { "id": "fbmf_south", "amount": 2 }, { "id": "fbmf_sw_south" } ], + "blueprint_provides": [ { "id": "trapping" }, { "id": "hunting" }, { "id": "kitchen" }, { "id": "kitchen_recipes_1" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_well_north", + "description": "Digging a well will give us easy access to water.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_well_north", + "blueprint_name": "north water well", + "blueprint_requires": [ { "id": "fbmf_northeast", "amount": 4 }, { "id": "fbmf_fire_northeast" }, { "id": "fbmf_bed2_northeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_root_cellar_north", + "description": "Digging a root cellar will give us a way to preserve food.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_root_cellar_north", + "blueprint_name": "north root cellar", + "blueprint_provides": [ { "id": "pantry" } ], + "blueprint_requires": [ { "id": "fbmf_northeast", "amount": 4 }, { "id": "fbmf_fire_northeast" }, { "id": "fbmf_bed2_northeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_radio_tower_north", + "description": "We could build a radio tower to improve the range of our radios.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "skill_used": "fabrication", + "difficulty": 4, + "autolearn": false, + "never_learn": true, + "time": "24 h", + "construction_blueprint": "fbmf_radio_tower_north", + "blueprint_name": "north radio tower", + "blueprint_provides": [ { "id": "fbmf_radio_tower_north" } ], + "blueprint_requires": [ { "id": "fbmf_northeast", "amount": 4 }, { "id": "fbmf_fire_northeast" }, { "id": "fbmf_bed2_northeast" } ], + "qualities": [ + { "id": "HAMMER", "level": 2 }, + { "id": "SAW_M", "level": 1 }, + { "id": "WRENCH", "level": 1 }, + { "id": "SCREW", "level": 1 } + ], + "components": [ + [ + [ "wind_turbine", 4 ], + [ "xl_wind_turbine", 1 ], + [ "solar_panel", 4 ], + [ "reinforced_solar_panel", 4 ], + [ "solar_panel_v2", 2 ], + [ "reinforced_solar_panel_v2", 2 ], + [ "solar_panel_v3", 1 ] + ], + [ [ "storage_battery", 1 ], [ "medium_storage_battery", 4 ], [ "small_storage_battery", 32 ] ], + [ [ "sheet_metal", 2 ], [ "wire", 8 ] ], + [ [ "pipe", 24 ] ] + ] + }, + { + "type": "recipe", + "result": "faction_base_modular_field_radio_console_north", + "description": "Adding a console to control the radio tower will help with recruiting more survivors.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "skill_used": "electronics", + "difficulty": 6, + "autolearn": false, + "never_learn": true, + "time": "24 h", + "construction_blueprint": "fbmf_radio_console_north", + "blueprint_name": "north radio console", + "blueprint_provides": [ { "id": "fbmf_radio_console_north" }, { "id": "radio" } ], + "blueprint_requires": [ { "id": "fbmf_radio_tower_north" } ], + "qualities": [ { "id": "SAW_M", "level": 1 }, { "id": "WRENCH", "level": 1 }, { "id": "SCREW", "level": 1 } ], + "components": [ + [ [ "processor", 2 ] ], + [ [ "RAM", 2 ] ], + [ [ "small_lcd_screen", 1 ] ], + [ [ "e_scrap", 8 ] ], + [ [ "frame", 1 ] ], + [ [ "circuit", 4 ] ], + [ [ "power_supply", 2 ] ], + [ [ "amplifier", 2 ] ], + [ [ "cable", 80 ] ], + [ [ "motor_small", 1 ], [ "motor_tiny", 2 ] ] + ] + }, + { + "type": "recipe", + "result": "faction_base_new_farm_0", + "description": "We should survey the base site and set up a bulletin board.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "skill_used": "fabrication", + "autolearn": false, + "never_learn": true, + "time": "1 m", + "construction_blueprint": "faction_base_modular_field_0", + "blueprint_name": "basic survey", + "blueprint_requires": [ { "id": "not_an_upgrade" } ] + }, + { + "type": "recipe", + "result": "faction_base_new_blacksmith_0", + "description": "We should survey the base site and set up a bulletin board.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "skill_used": "fabrication", + "autolearn": false, + "never_learn": true, + "time": "1 m", + "construction_blueprint": "faction_base_modular_field_0", + "blueprint_name": "basic survey", + "blueprint_requires": [ { "id": "not_an_upgrade" } ] + }, + { + "type": "recipe", + "result": "faction_base_new_garage_0", + "description": "We should survey the base site and set up a bulletin board.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "skill_used": "fabrication", + "autolearn": false, + "never_learn": true, + "time": "1 m", + "construction_blueprint": "faction_base_modular_field_0", + "blueprint_name": "basic survey", + "blueprint_requires": [ { "id": "not_an_upgrade" } ] + }, + { + "type": "recipe", + "result": "faction_base_new_kitchen_0", + "description": "We should survey the base site and set up a bulletin board.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "skill_used": "fabrication", + "autolearn": false, + "never_learn": true, + "time": "1 m", + "construction_blueprint": "faction_base_modular_field_0", + "blueprint_name": "basic survey", + "blueprint_requires": [ { "id": "not_an_upgrade" } ] + } +] diff --git a/data/json/recipes/basecamps/recipe_modular_field_defenses.json b/data/json/recipes/basecamps/recipe_modular_field_defenses.json new file mode 100644 index 0000000000000..200aab48d7a08 --- /dev/null +++ b/data/json/recipes/basecamps/recipe_modular_field_defenses.json @@ -0,0 +1,120 @@ +[ + { + "type": "recipe", + "result": "faction_base_modular_field_trench_north", + "description": "Digging a trench along the north edge of the camp would provide some defense and generate building materials.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_trench_north", + "blueprint_name": "north trench", + "blueprint_provides": [ { "id": "fbmf_trench_north" } ], + "blueprint_requires": [ { "id": "fbmf_northeast", "amount": 4 }, { "id": "fbmf_fire_northeast" }, { "id": "fbmf_bed2_northeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_trench_south", + "description": "Digging a trench along the south edge of the camp would provide some defense and generate building materials.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_trench_south", + "blueprint_name": "south trench", + "blueprint_provides": [ { "id": "fbmf_trench_south" } ], + "blueprint_requires": [ { "id": "fbmf_northeast", "amount": 4 }, { "id": "fbmf_fire_northeast" }, { "id": "fbmf_bed2_northeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_trench_northeast", + "description": "Digging a trench along the northeast corner of the camp would provide some defense and generate building materials. If we have solid buildings all along the east side of the camp, we would only need to dig the trench long enough to reach the buildings.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "skill_used": "fabrication", + "never_learn": true, + "construction_blueprint": "fbmf_trench_corner_northeast", + "blueprint_name": "northeast trench", + "blueprint_provides": [ { "id": "fbmf_trench_northeast" } ], + "blueprint_requires": [ { "id": "fbmf_northeast", "amount": 4 }, { "id": "fbmf_fire_northeast" }, { "id": "fbmf_bed2_northeast" } ], + "blueprint_excludes": [ { "id": "fbmf_trench_northeast" }, { "id": "fbmf_trench_east" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_trench_northwest", + "description": "Digging a trench along the northwest corner of the camp would provide some defense and generate building materials. If we have solid buildings all along the west side of the camp, we would only need to dig the trench long enough to reach the buildings.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_trench_corner_northwest", + "blueprint_name": "northwest trench", + "blueprint_provides": [ { "id": "fbmf_trench_northwest" } ], + "blueprint_requires": [ { "id": "fbmf_northeast", "amount": 4 }, { "id": "fbmf_fire_northeast" }, { "id": "fbmf_bed2_northeast" } ], + "blueprint_excludes": [ { "id": "fbmf_trench_northwest" }, { "id": "fbmf_trench_west" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_trench_southeast", + "description": "Digging a trench along the southeast corner of the camp would provide some defense and generate building materials. If we have solid buildings all along the east side of the camp, we would only need to dig the trench long enough to reach the buildings.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_trench_corner_southeast", + "blueprint_name": "southeast trench", + "blueprint_provides": [ { "id": "fbmf_trench_southeast" } ], + "blueprint_requires": [ { "id": "fbmf_northeast", "amount": 4 }, { "id": "fbmf_fire_northeast" }, { "id": "fbmf_bed2_northeast" } ], + "blueprint_excludes": [ { "id": "fbmf_trench_southeast" }, { "id": "fbmf_trench_east" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_trench_southwest", + "description": "Digging a trench along the southwest corner of the camp would provide some defense and generate building materials. If we have solid buildings all along the west side of the camp, we would only need to dig the trench long enough to reach the buildings.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_trench_corner_southwest", + "blueprint_name": "southwest trench", + "blueprint_provides": [ { "id": "fbmf_trench_southwest" } ], + "blueprint_requires": [ { "id": "fbmf_northeast", "amount": 4 }, { "id": "fbmf_fire_northeast" }, { "id": "fbmf_bed2_northeast" } ], + "blueprint_excludes": [ { "id": "fbmf_trench_southwest" }, { "id": "fbmf_trench_west" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_trench_east", + "description": "Digging a trench along the east edge of the camp would provide some defense and generate building materials. We'll need to run the trench the length of the camp if we don't have solid buildings all along the east side.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_trench_east", + "blueprint_name": "east trench", + "blueprint_provides": [ { "id": "fbmf_trench_east" } ], + "blueprint_requires": [ { "id": "fbmf_northeast", "amount": 4 }, { "id": "fbmf_fire_northeast" }, { "id": "fbmf_bed2_northeast" } ], + "blueprint_excludes": [ { "id": "fbmf_trench_southeast" }, { "id": "fbmf_trench_northeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_trench_west", + "description": "Digging a trench along the west edge of the camp would provide some defense and generate building materials. We'll need to run the trench the length of the camp if we don't have solid buildings all along the west side.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_trench_west", + "blueprint_name": "west trench", + "blueprint_provides": [ { "id": "fbmf_trench_west" } ], + "blueprint_requires": [ { "id": "fbmf_northeast", "amount": 4 }, { "id": "fbmf_fire_northeast" }, { "id": "fbmf_bed2_northeast" } ], + "blueprint_excludes": [ { "id": "fbmf_trench_southwest" }, { "id": "fbmf_trench_northwest" } ], + "blueprint_autocalc": true + } +] diff --git a/data/json/recipes/basecamps/recipe_modular_field_metal.json b/data/json/recipes/basecamps/recipe_modular_field_metal.json new file mode 100644 index 0000000000000..2828f49324962 --- /dev/null +++ b/data/json/recipes/basecamps/recipe_modular_field_metal.json @@ -0,0 +1,337 @@ +[ + { + "type": "recipe", + "result": "faction_base_modular_field_room0_metal_northeast", + "description": "We need some shelter, so build half of a metal shack with a metal roof on the northeast side of the camp", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room0_metal_northeast", + "blueprint_name": "northeast shack", + "blueprint_requires": [ { "id": "faction_base_modular_field_0" } ], + "blueprint_provides": [ { "id": "fbmf_northeast" } ], + "blueprint_excludes": [ { "id": "fbmf_northeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room1_metal_northeast", + "description": "We should use metal to expand the shelter so we have space for another bed.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room1_metal_northeast", + "blueprint_name": "expand northeast shack", + "blueprint_requires": [ { "id": "fbmf_northeast" } ], + "blueprint_provides": [ { "id": "fbmf_northeast" } ], + "blueprint_excludes": [ { "id": "fbmf_northeast", "amount": 2 }, { "id": "fbmf_tent_northeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room2_metal_northeast", + "description": "We should use metal to finish the northeast shack.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room2_metal_northeast", + "blueprint_name": "finish northeast shack", + "blueprint_requires": [ { "id": "fbmf_northeast", "amount": 2 } ], + "blueprint_provides": [ { "id": "fbmf_northeast", "amount": 2 } ], + "blueprint_excludes": [ { "id": "fbmf_northeast", "amount": 4 }, { "id": "fbmf_tent_northeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_shack4_metal_east", + "description": "We should expand our housing by putting up a metal building on the east side, which we can also use as part of the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_shack4_metal_east", + "blueprint_name": "east shack", + "blueprint_requires": [ { "id": "fbmf_tent_northeast" }, { "id": "fbmf_fire_northeast" }, { "id": "fbmf_bed2_northeast" } ], + "blueprint_provides": [ { "id": "fbmf_east", "amount": 4 } ], + "blueprint_excludes": [ { "id": "fbmf_east" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room4_metal_east", + "description": "We should expand our housing by adding a metal room on the east side, which we can also use as part of the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room4_metal_east", + "blueprint_name": "east room", + "blueprint_requires": [ { "id": "fbmf_northeast", "amount": 4 }, { "id": "fbmf_fire_northeast" }, { "id": "fbmf_bed2_northeast" } ], + "blueprint_provides": [ { "id": "fbmf_east", "amount": 4 } ], + "blueprint_excludes": [ { "id": "fbmf_east" }, { "id": "fbmf_tent_northeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_shack4_metal_southeast", + "description": "We should expand our housing by putting up a metal building on the southeast side, which we can also use as part of the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_shack4_metal_southeast", + "blueprint_name": "southeast shack", + "blueprint_requires": [ { "id": "fbmf_tent_east" } ], + "blueprint_provides": [ { "id": "fbmf_southeast", "amount": 4 } ], + "blueprint_excludes": [ { "id": "fbmf_southeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room4_metal_southeast", + "description": "We should expand our housing by adding a metal room on the southeast side, which we can also use as part of the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room4_metal_southeast", + "blueprint_name": "southeast room", + "blueprint_requires": [ { "id": "fbmf_east", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_southeast", "amount": 4 } ], + "blueprint_excludes": [ { "id": "fbmf_southeast" }, { "id": "fbmf_tent_east" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room4_metal_northwest", + "description": "We should expand our housing by putting up a metal building on the northwest side, which we can also use as part of the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room4_metal_northwest", + "blueprint_name": "northwest shack", + "blueprint_requires": [ { "id": "fbmf_northeast", "amount": 4 }, { "id": "fbmf_fire_northeast" }, { "id": "fbmf_bed2_northeast" } ], + "blueprint_provides": [ { "id": "fbmf_northwest", "amount": 4 } ], + "blueprint_excludes": [ { "id": "fbmf_northwest" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_shack4_metal_west", + "description": "We should expand our housing by putting up a metal building on the west side, which we can also use as part of the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_shack4_metal_west", + "blueprint_name": "west shack", + "blueprint_requires": [ { "id": "fbmf_tent_northwest" } ], + "blueprint_provides": [ { "id": "fbmf_west", "amount": 4 } ], + "blueprint_excludes": [ { "id": "fbmf_west" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room4_metal_west", + "description": "We should expand our housing by adding a metal room on the west side, which we can also use as part of the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room4_metal_west", + "blueprint_name": "west room", + "blueprint_requires": [ { "id": "fbmf_northwest", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_west", "amount": 4 } ], + "blueprint_excludes": [ { "id": "fbmf_west" }, { "id": "fbmf_tent_northwest" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_shack4_metal_soutwest", + "description": "We should expand our housing by putting up a metal building on the southwest side, which we can also use as part of the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_shack4_metal_southwest", + "blueprint_name": "southwest shack", + "blueprint_requires": [ { "id": "fbmf_tent_west" } ], + "blueprint_provides": [ { "id": "fbmf_southwest", "amount": 4 } ], + "blueprint_excludes": [ { "id": "fbmf_southwest" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room4_metal_southwest", + "description": "We should expand our housing by adding a metal room on the southwest side, which we can also use as part of the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room4_metal_southwest", + "blueprint_name": "southwest room", + "blueprint_requires": [ { "id": "fbmf_west", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_southwest", "amount": 4 } ], + "blueprint_excludes": [ { "id": "fbmf_southwest" }, { "id": "fbmf_tent_west" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_shack_ne_metal_center", + "description": "A central building can act as a kitchen and dining hall. We should build the northeast quarter of one from metal.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_shack_ne_metal_center", + "blueprint_name": "central building NE corner", + "blueprint_requires": [ { "id": "fbmf_tent_east" } ], + "blueprint_provides": [ { "id": "fbmf_center", "amount": 2 }, { "id": "fbmf_ne_center" } ], + "blueprint_excludes": [ { "id": "fbmf_ne_center" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_ne_metal_center", + "description": "A central building can act as a core and dining hall. We should build out from the east room with metal.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_ne_metal_center", + "blueprint_name": "central building NE corner", + "blueprint_requires": [ { "id": "fbmf_east", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_center", "amount": 2 }, { "id": "fbmf_ne_center" } ], + "blueprint_excludes": [ { "id": "fbmf_ne_center" }, { "id": "fbmf_tent_east" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_shack_nw_metal_center", + "description": "A central building can act as a core and dining hall. We should build the northwest quarter of one from metal.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_shack_nw_metal_center", + "blueprint_name": "central building NW corner", + "blueprint_requires": [ { "id": "fbmf_tent_west" } ], + "blueprint_provides": [ { "id": "fbmf_center", "amount": 2 }, { "id": "fbmf_nw_center" } ], + "blueprint_excludes": [ { "id": "fbmf_nw_center" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_nw_metal_center", + "description": "A central building can act as a core and dining hall. We should build out from the west room with metal.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_nw_metal_center", + "blueprint_name": "central building NW corner", + "blueprint_requires": [ { "id": "fbmf_west", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_center", "amount": 2 }, { "id": "fbmf_nw_center" } ], + "blueprint_excludes": [ { "id": "fbmf_nw_center" }, { "id": "fbmf_tent_west" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_metal_center", + "description": "A central building can act as a core and dining hall. We should build between the east and west rooms with metal.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_metal_center", + "blueprint_name": "central building north half", + "blueprint_requires": [ { "id": "fbmf_east", "amount": 4 }, { "id": "fbmf_west", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_center", "amount": 4 }, { "id": "fbmf_ne_center" }, { "id": "fbmf_nw_center" } ], + "blueprint_excludes": [ { "id": "fbmf_ne_center" }, { "id": "fbmf_nw_center" }, { "id": "fbmf_tent_east" }, { "id": "fbmf_tent_west" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_shack_se_metal_south", + "description": "A central building can act as a core and dining hall. We should build the southeast quarter of one from metal.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_shack_se_metal_south", + "blueprint_name": "central building SE corner", + "blueprint_requires": [ { "id": "fbmf_tent_southeast" } ], + "blueprint_provides": [ { "id": "fbmf_south", "amount": 2 }, { "id": "fbmf_se_south" } ], + "blueprint_excludes": [ { "id": "fbmf_se_south" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_se_metal_south", + "description": "A central building can act as a core and dining hall. We should build out from the southeast room with metal.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_se_metal_south", + "blueprint_name": "central building SE corner", + "blueprint_requires": [ { "id": "fbmf_southeast", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_south", "amount": 2 }, { "id": "fbmf_se_south" } ], + "blueprint_excludes": [ { "id": "fbmf_se_south" }, { "id": "fbmf_tent_southeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_shack_sw_metal_south", + "description": "A central building can act as a core and dining hall. We should build the southwest quarter of one from metal.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_shack_sw_metal_south", + "blueprint_name": "central building SW corner", + "blueprint_requires": [ { "id": "fbmf_tent_southwest" } ], + "blueprint_provides": [ { "id": "fbmf_south", "amount": 2 }, { "id": "fbmf_sw_south" } ], + "blueprint_excludes": [ { "id": "fbmf_sw_south" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_sw_metal_south", + "description": "A central building can act as a core and dining hall. We should build out from the southwest room with metal.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_sw_metal_south", + "blueprint_name": "central building SW corner", + "blueprint_requires": [ { "id": "fbmf_southwest", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_south", "amount": 2 }, { "id": "fbmf_sw_south" } ], + "blueprint_excludes": [ { "id": "fbmf_sw_south" }, { "id": "fbmf_tent_southwest" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_metal_south", + "description": "A central building can act as a core and dining hall. We should build between the southeast and southwest rooms with metal.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_metal_south", + "blueprint_name": "central building south half", + "blueprint_requires": [ { "id": "fbmf_southeast", "amount": 4 }, { "id": "fbmf_southwest", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_south", "amount": 4 }, { "id": "fbmf_se_south" }, { "id": "fbmf_sw_south" } ], + "blueprint_excludes": [ + { "id": "fbmf_se_south" }, + { "id": "fbmf_sw_south" }, + { "id": "fbmf_tent_southeast" }, + { "id": "fbmf_tent_southwest" } + ], + "blueprint_autocalc": true + } +] diff --git a/data/json/recipes/basecamps/recipe_modular_field_tent.json b/data/json/recipes/basecamps/recipe_modular_field_tent.json new file mode 100644 index 0000000000000..708c4b8e8fec2 --- /dev/null +++ b/data/json/recipes/basecamps/recipe_modular_field_tent.json @@ -0,0 +1,104 @@ +[ + { + "type": "recipe", + "result": "faction_base_modular_field_tent_northeast", + "description": "We need some shelter, so set up a tent on the northeast side of the camp.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "skill_used": "fabrication", + "autolearn": false, + "never_learn": true, + "time": "3 h", + "construction_blueprint": "fbmf_tent_northeast", + "blueprint_name": "northeast tent", + "blueprint_requires": [ { "id": "faction_base_modular_field_0" } ], + "blueprint_provides": [ { "id": "fbmf_northeast", "amount": 4 }, { "id": "fbmf_tent_northeast" } ], + "blueprint_excludes": [ { "id": "fbmf_northeast" } ], + "components": [ [ [ "large_tent_kit", 1 ], [ "broketent", 4 ], [ "tent_kit", 3 ], [ "shelter_kit", 4 ] ] ] + }, + { + "type": "recipe", + "result": "faction_base_modular_field_tent_east", + "description": "We should expand our housing by putting up a tent on the east side, though doing so will mean we will need more materials to build the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "skill_used": "fabrication", + "autolearn": false, + "never_learn": true, + "time": "3 h", + "construction_blueprint": "fbmf_tent_east", + "blueprint_name": "east tent", + "blueprint_requires": [ { "id": "fbmf_northeast", "amount": 4 }, { "id": "fbmf_fire_northeast" }, { "id": "fbmf_bed2_northeast" } ], + "blueprint_provides": [ { "id": "fbmf_east", "amount": 4 }, { "id": "fbmf_tent_east" } ], + "blueprint_excludes": [ { "id": "fbmf_east" } ], + "components": [ [ [ "large_tent_kit", 1 ], [ "broketent", 4 ], [ "tent_kit", 3 ], [ "shelter_kit", 4 ] ] ] + }, + { + "type": "recipe", + "result": "faction_base_modular_field_tent_southeast", + "description": "We should expand our housing by putting up a tent on the southeast side, though doing so will mean we will need more materials to build the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "skill_used": "fabrication", + "autolearn": false, + "never_learn": true, + "time": "3 h", + "construction_blueprint": "fbmf_tent_southeast", + "blueprint_name": "southeast tent", + "blueprint_requires": [ { "id": "fbmf_east", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_southeast", "amount": 4 }, { "id": "fbmf_tent_southeast" } ], + "blueprint_excludes": [ { "id": "fbmf_southeast" } ], + "components": [ [ [ "large_tent_kit", 1 ], [ "broketent", 4 ], [ "tent_kit", 3 ], [ "shelter_kit", 4 ] ] ] + }, + { + "type": "recipe", + "result": "faction_base_modular_field_tent_northwest", + "description": "We should expand our housing by putting up a tent on the northwest side, though doing so will mean we will need more materials to build the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "skill_used": "fabrication", + "autolearn": false, + "never_learn": true, + "time": "3 h", + "construction_blueprint": "fbmf_tent_northwest", + "blueprint_name": "northwest tent", + "blueprint_requires": [ { "id": "fbmf_northeast", "amount": 4 }, { "id": "fbmf_fire_northeast" }, { "id": "fbmf_bed2_northeast" } ], + "blueprint_provides": [ { "id": "fbmf_northwest", "amount": 4 }, { "id": "fbmf_tent_northwest" } ], + "blueprint_excludes": [ { "id": "fbmf_northwest" } ], + "components": [ [ [ "large_tent_kit", 1 ], [ "broketent", 4 ], [ "tent_kit", 3 ], [ "shelter_kit", 4 ] ] ] + }, + { + "type": "recipe", + "result": "faction_base_modular_field_tent_northwest", + "description": "We should expand our housing by putting up a tent on the west side, though doing so will mean we will need more materials to build the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "skill_used": "fabrication", + "autolearn": false, + "never_learn": true, + "time": "3 h", + "construction_blueprint": "fbmf_tent_west", + "blueprint_name": "west tent", + "blueprint_requires": [ { "id": "fbmf_northwest", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_west", "amount": 4 }, { "id": "fbmf_tent_west" } ], + "blueprint_excludes": [ { "id": "fbmf_west" } ], + "components": [ [ [ "large_tent_kit", 1 ], [ "broketent", 4 ], [ "tent_kit", 3 ], [ "shelter_kit", 4 ] ] ] + }, + { + "type": "recipe", + "result": "faction_base_modular_field_tent_northwest", + "description": "We should expand our housing by putting up a tent on the southwest side, though doing so will mean we will need more materials to build the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "skill_used": "fabrication", + "autolearn": false, + "never_learn": true, + "time": "3 h", + "construction_blueprint": "fbmf_tent_southwest", + "blueprint_name": "southwest tent", + "blueprint_requires": [ { "id": "fbmf_west", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_southwest", "amount": 4 }, { "id": "fbmf_tent_southwest" } ], + "blueprint_excludes": [ { "id": "fbmf_southwest" } ], + "components": [ [ [ "large_tent_kit", 1 ], [ "broketent", 4 ], [ "tent_kit", 3 ], [ "shelter_kit", 4 ] ] ] + } +] diff --git a/data/json/recipes/basecamps/recipe_modular_field_wad.json b/data/json/recipes/basecamps/recipe_modular_field_wad.json new file mode 100644 index 0000000000000..e5142bd3da24c --- /dev/null +++ b/data/json/recipes/basecamps/recipe_modular_field_wad.json @@ -0,0 +1,341 @@ +[ + { + "type": "recipe", + "result": "faction_base_modular_field_room0_wad_northeast", + "description": "We need some shelter, so build half of a wattle-and-daub shack with a sod roof on the northeast side of the camp", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room0_wad_northeast", + "blueprint_name": "northeast shack", + "blueprint_requires": [ { "id": "faction_base_modular_field_0" } ], + "blueprint_provides": [ { "id": "fbmf_northeast" } ], + "blueprint_excludes": [ { "id": "fbmf_northeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room1_wad_northeast", + "description": "We should use wattle-and-daub to expand the shelter so we have space for another bed.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room1_wad_northeast", + "blueprint_name": "expand northeast shack", + "blueprint_requires": [ { "id": "fbmf_northeast" } ], + "blueprint_provides": [ { "id": "fbmf_northeast" } ], + "blueprint_excludes": [ { "id": "fbmf_northeast", "amount": 2 }, { "id": "fbmf_tent_northeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room2_wad_northeast", + "description": "We should use wattle-and-daub to finish the northeast shack.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "skill_used": "fabrication", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room2_wad_northeast", + "blueprint_name": "finish northeast shack", + "blueprint_requires": [ { "id": "fbmf_northeast", "amount": 2 } ], + "blueprint_provides": [ { "id": "fbmf_northeast", "amount": 2 } ], + "blueprint_excludes": [ { "id": "fbmf_northeast", "amount": 4 }, { "id": "fbmf_tent_northeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_shack4_wad_east", + "description": "We should expand our housing by putting up a wattle-and-daub building on the east side, which we can also use as part of the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_shack4_wad_east", + "blueprint_name": "east shack", + "blueprint_requires": [ { "id": "fbmf_tent_northeast" }, { "id": "fbmf_fire_northeast" }, { "id": "fbmf_bed2_northeast" } ], + "blueprint_provides": [ { "id": "fbmf_east", "amount": 4 } ], + "blueprint_excludes": [ { "id": "fbmf_east" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room4_wad_east", + "description": "We should expand our housing by adding a wattle-and-daub room on the east side, which we can also use as part of the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "skill_used": "fabrication", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room4_wad_east", + "blueprint_name": "east room", + "blueprint_requires": [ { "id": "fbmf_northeast", "amount": 4 }, { "id": "fbmf_fire_northeast" }, { "id": "fbmf_bed2_northeast" } ], + "blueprint_provides": [ { "id": "fbmf_east", "amount": 4 } ], + "blueprint_excludes": [ { "id": "fbmf_east" }, { "id": "fbmf_tent_northeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_shack4_wad_southeast", + "description": "We should expand our housing by putting up a wattle-and-daub building on the southeast side, which we can also use as part of the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_shack4_wad_southeast", + "blueprint_name": "southeast shack", + "blueprint_requires": [ { "id": "fbmf_tent_east" } ], + "blueprint_provides": [ { "id": "fbmf_southeast", "amount": 4 } ], + "blueprint_excludes": [ { "id": "fbmf_southeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room4_wad_southeast", + "description": "We should expand our housing by adding a wattle-and-daub room on the southeast side, which we can also use as part of the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "skills_required": [ [ "survival", 3 ], [ "tailor", 1 ] ], + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room4_wad_southeast", + "blueprint_name": "southeast room", + "blueprint_requires": [ { "id": "fbmf_east", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_southeast", "amount": 4 } ], + "blueprint_excludes": [ { "id": "fbmf_southeast" }, { "id": "fbmf_tent_east" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room4_wad_northwest", + "description": "We should expand our housing by putting up a wattle-and-daub building on the northwest side, which we can also use as part of the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room4_wad_northwest", + "blueprint_name": "northwest shack", + "blueprint_requires": [ { "id": "fbmf_northeast", "amount": 4 }, { "id": "fbmf_fire_northeast" }, { "id": "fbmf_bed2_northeast" } ], + "blueprint_provides": [ { "id": "fbmf_northwest", "amount": 4 } ], + "blueprint_excludes": [ { "id": "fbmf_northwest" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_shack4_wad_west", + "description": "We should expand our housing by putting up a wattle-and-daub building on the west side, which we can also use as part of the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_shack4_wad_west", + "blueprint_name": "west shack", + "blueprint_requires": [ { "id": "fbmf_tent_northwest" } ], + "blueprint_provides": [ { "id": "fbmf_west", "amount": 4 } ], + "blueprint_excludes": [ { "id": "fbmf_west" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room4_wad_west", + "description": "We should expand our housing by adding a wattle-and-daub room on the west side, which we can also use as part of the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room4_wad_west", + "blueprint_name": "west room", + "blueprint_requires": [ { "id": "fbmf_northwest", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_west", "amount": 4 } ], + "blueprint_excludes": [ { "id": "fbmf_west" }, { "id": "fbmf_tent_northwest" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_shack4_wad_southwest", + "description": "We should expand our housing by putting up a wattle-and-daub building on the southwest side, which we can also use as part of the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_shack4_wad_southwest", + "blueprint_name": "southwest shack", + "blueprint_requires": [ { "id": "fbmf_tent_west" } ], + "blueprint_provides": [ { "id": "fbmf_southwest", "amount": 4 } ], + "blueprint_excludes": [ { "id": "fbmf_southwest" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room4_wad_southwest", + "description": "We should expand our housing by adding a wattle-and-daub room on the southwest side, which we can also use as part of the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room4_wad_southwest", + "blueprint_name": "southwest room", + "blueprint_requires": [ { "id": "fbmf_west", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_southwest", "amount": 4 } ], + "blueprint_excludes": [ { "id": "fbmf_southwest" }, { "id": "fbmf_tent_west" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_shack_ne_wad_center", + "description": "A central building can act as a kitchen and dining hall. We should build the northeast quarter of one from wattle-and-daub.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_shack_ne_wad_center", + "blueprint_name": "central building NE corner", + "blueprint_requires": [ { "id": "fbmf_tent_east" } ], + "blueprint_provides": [ { "id": "fbmf_center", "amount": 2 }, { "id": "fbmf_ne_center" } ], + "blueprint_excludes": [ { "id": "fbmf_ne_center" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_ne_wad_center", + "description": "A central building can act as a core and dining hall. We should build out from the east room with wattle-and-daub.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_ne_wad_center", + "blueprint_name": "central building NE corner", + "blueprint_requires": [ { "id": "fbmf_east", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_center", "amount": 2 }, { "id": "fbmf_ne_center" } ], + "blueprint_excludes": [ { "id": "fbmf_ne_center" }, { "id": "fbmf_tent_east" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_shack_nw_wad_center", + "description": "A central building can act as a core and dining hall. We should build the northwest quarter of one from wattle-and-daub.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_shack_nw_wad_center", + "blueprint_name": "central building NW corner", + "blueprint_requires": [ { "id": "fbmf_tent_west" } ], + "blueprint_provides": [ { "id": "fbmf_center", "amount": 2 }, { "id": "fbmf_nw_center" } ], + "blueprint_excludes": [ { "id": "fbmf_nw_center" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_nw_wad_center", + "description": "A central building can act as a core and dining hall. We should build out from the west room with wattle-and-daub.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_nw_wad_center", + "blueprint_name": "central building NW corner", + "blueprint_requires": [ { "id": "fbmf_west", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_center", "amount": 2 }, { "id": "fbmf_nw_center" } ], + "blueprint_excludes": [ { "id": "fbmf_nw_center" }, { "id": "fbmf_tent_west" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_wad_center", + "description": "A central building can act as a core and dining hall. We should build between the east and west rooms with wattle-and-daub.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_wad_center", + "blueprint_name": "central building north half", + "blueprint_requires": [ { "id": "fbmf_east", "amount": 4 }, { "id": "fbmf_west", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_center", "amount": 4 }, { "id": "fbmf_ne_center" }, { "id": "fbmf_nw_center" } ], + "blueprint_excludes": [ { "id": "fbmf_ne_center" }, { "id": "fbmf_nw_center" }, { "id": "fbmf_tent_east" }, { "id": "fbmf_tent_west" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_shack_se_wad_south", + "description": "A central building can act as a core and dining hall. We should build the southeast quarter of one from wattle-and-daub.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_shack_se_wad_south", + "blueprint_name": "central building SE corner", + "blueprint_requires": [ { "id": "fbmf_tent_southeast" } ], + "blueprint_provides": [ { "id": "fbmf_south", "amount": 2 }, { "id": "fbmf_se_south" } ], + "blueprint_excludes": [ { "id": "fbmf_se_south" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_se_wad_south", + "description": "A central building can act as a core and dining hall. We should build out from the southeast room with wattle-and-daub.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "skill_used": "fabrication", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_se_wad_south", + "blueprint_name": "central building SE corner", + "blueprint_requires": [ { "id": "fbmf_southeast", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_south", "amount": 2 }, { "id": "fbmf_se_south" } ], + "blueprint_excludes": [ { "id": "fbmf_se_south" }, { "id": "fbmf_tent_southeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_shack_sw_wad_south", + "description": "A central building can act as a core and dining hall. We should build the southwest quarter of one from wattle-and-daub.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_shack_sw_wad_south", + "blueprint_name": "central building SW corner", + "blueprint_requires": [ { "id": "fbmf_tent_southwest" } ], + "blueprint_provides": [ { "id": "fbmf_south", "amount": 2 }, { "id": "fbmf_sw_south" } ], + "blueprint_excludes": [ { "id": "fbmf_sw_south" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_sw_wad_south", + "description": "A central building can act as a core and dining hall. We should build out from the southwest room with wattle-and-daub.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_sw_wad_south", + "blueprint_name": "central building SW corner", + "blueprint_requires": [ { "id": "fbmf_southwest", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_south", "amount": 2 }, { "id": "fbmf_sw_south" } ], + "blueprint_excludes": [ { "id": "fbmf_sw_south" }, { "id": "fbmf_tent_southwest" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_wad_south", + "description": "A central building can act as a core and dining hall. We should build between the southeast and southwest rooms with wattle-and-daub.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_wad_south", + "blueprint_name": "central building south half", + "blueprint_requires": [ { "id": "fbmf_southeast", "amount": 4 }, { "id": "fbmf_southwest", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_south", "amount": 4 }, { "id": "fbmf_se_south" }, { "id": "fbmf_sw_south" } ], + "blueprint_excludes": [ + { "id": "fbmf_se_south" }, + { "id": "fbmf_sw_south" }, + { "id": "fbmf_tent_southeast" }, + { "id": "fbmf_tent_southwest" } + ], + "blueprint_autocalc": true + } +] diff --git a/data/json/recipes/basecamps/recipe_modular_field_wood.json b/data/json/recipes/basecamps/recipe_modular_field_wood.json new file mode 100644 index 0000000000000..7c10c661bfddb --- /dev/null +++ b/data/json/recipes/basecamps/recipe_modular_field_wood.json @@ -0,0 +1,337 @@ +[ + { + "type": "recipe", + "result": "faction_base_modular_field_room0_wood_northeast", + "description": "We need some shelter, so build half of a wood panel shack with a wooden roof on the northeast side of the camp", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room0_wood_northeast", + "blueprint_name": "northeast shack", + "blueprint_requires": [ { "id": "faction_base_modular_field_0" } ], + "blueprint_provides": [ { "id": "fbmf_northeast" } ], + "blueprint_excludes": [ { "id": "fbmf_northeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room1_wood_northeast", + "description": "We should use wood panel to expand the shelter so we have space for another bed.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room1_wood_northeast", + "blueprint_name": "expand northeast shack", + "blueprint_requires": [ { "id": "fbmf_northeast" } ], + "blueprint_provides": [ { "id": "fbmf_northeast" } ], + "blueprint_excludes": [ { "id": "fbmf_northeast", "amount": 2 }, { "id": "fbmf_tent_northeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room2_wood_northeast", + "description": "We should use wood panel to finish the northeast shack.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room2_wood_northeast", + "blueprint_name": "finish northeast shack", + "blueprint_requires": [ { "id": "fbmf_northeast", "amount": 2 } ], + "blueprint_provides": [ { "id": "fbmf_northeast", "amount": 2 } ], + "blueprint_excludes": [ { "id": "fbmf_northeast", "amount": 4 }, { "id": "fbmf_tent_northeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_shack4_wood_east", + "description": "We should expand our housing by putting up a wood panel building on the east side, which we can also use as part of the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_shack4_wood_east", + "blueprint_name": "east shack", + "blueprint_requires": [ { "id": "fbmf_tent_northeast" }, { "id": "fbmf_fire_northeast" }, { "id": "fbmf_bed2_northeast" } ], + "blueprint_provides": [ { "id": "fbmf_east", "amount": 4 } ], + "blueprint_excludes": [ { "id": "fbmf_east" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room4_wood_east", + "description": "We should expand our housing by adding a wood panel room on the east side, which we can also use as part of the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room4_wood_east", + "blueprint_name": "east room", + "blueprint_requires": [ { "id": "fbmf_northeast", "amount": 4 }, { "id": "fbmf_fire_northeast" }, { "id": "fbmf_bed2_northeast" } ], + "blueprint_provides": [ { "id": "fbmf_east", "amount": 4 } ], + "blueprint_excludes": [ { "id": "fbmf_east" }, { "id": "fbmf_tent_northeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_shack4_wood_southeast", + "description": "We should expand our housing by putting up a wood panel building on the southeast side, which we can also use as part of the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_shack4_wood_southeast", + "blueprint_name": "southeast shack", + "blueprint_requires": [ { "id": "fbmf_tent_east" } ], + "blueprint_provides": [ { "id": "fbmf_southeast", "amount": 4 } ], + "blueprint_excludes": [ { "id": "fbmf_southeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room4_wood_southeast", + "description": "We should expand our housing by adding a wood panel room on the southeast side, which we can also use as part of the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room4_wood_southeast", + "blueprint_name": "southeast room", + "blueprint_requires": [ { "id": "fbmf_east", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_southeast", "amount": 4 } ], + "blueprint_excludes": [ { "id": "fbmf_southeast" }, { "id": "fbmf_tent_east" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room4_wood_northwest", + "description": "We should expand our housing by putting up a wood panel building on the northwest side, which we can also use as part of the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room4_wood_northwest", + "blueprint_name": "northwest shack", + "blueprint_requires": [ { "id": "fbmf_northeast", "amount": 4 }, { "id": "fbmf_fire_northeast" }, { "id": "fbmf_bed2_northeast" } ], + "blueprint_provides": [ { "id": "fbmf_northwest", "amount": 4 } ], + "blueprint_excludes": [ { "id": "fbmf_northwest" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_shack4_wood_west", + "description": "We should expand our housing by putting up a wood panel building on the west side, which we can also use as part of the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_shack4_wood_west", + "blueprint_name": "west shack", + "blueprint_requires": [ { "id": "fbmf_tent_northwest" } ], + "blueprint_provides": [ { "id": "fbmf_west", "amount": 4 } ], + "blueprint_excludes": [ { "id": "fbmf_west" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room4_wood_west", + "description": "We should expand our housing by adding a wood panel room on the west side, which we can also use as part of the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room4_wood_west", + "blueprint_name": "west room", + "blueprint_requires": [ { "id": "fbmf_northwest", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_west", "amount": 4 } ], + "blueprint_excludes": [ { "id": "fbmf_west" }, { "id": "fbmf_tent_northwest" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_shack4_wood_southwest", + "description": "We should expand our housing by putting up a wood panel building on the southwest side, which we can also use as part of the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_shack4_wood_southwest", + "blueprint_name": "southwest shack", + "blueprint_requires": [ { "id": "fbmf_tent_west" } ], + "blueprint_provides": [ { "id": "fbmf_southwest", "amount": 4 } ], + "blueprint_excludes": [ { "id": "fbmf_southwest" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_room4_wood_southwest", + "description": "We should expand our housing by adding a wood panel room on the southwest side, which we can also use as part of the central building.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_room4_wood_southwest", + "blueprint_name": "southwest room", + "blueprint_requires": [ { "id": "fbmf_west", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_southwest", "amount": 4 } ], + "blueprint_excludes": [ { "id": "fbmf_southwest" }, { "id": "fbmf_tent_west" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_shack_ne_wood_center", + "description": "A central building can act as a kitchen and dining hall. We should build the northeast quarter of one from wood panel.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_shack_ne_wood_center", + "blueprint_name": "central building NE corner", + "blueprint_requires": [ { "id": "fbmf_tent_east" } ], + "blueprint_provides": [ { "id": "fbmf_center", "amount": 2 }, { "id": "fbmf_ne_center" } ], + "blueprint_excludes": [ { "id": "fbmf_ne_center" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_ne_wood_center", + "description": "A central building can act as a core and dining hall. We should build out from the east room with wood panel.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_ne_wood_center", + "blueprint_name": "central building NE corner", + "blueprint_requires": [ { "id": "fbmf_east", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_center", "amount": 2 }, { "id": "fbmf_ne_center" } ], + "blueprint_excludes": [ { "id": "fbmf_ne_center" }, { "id": "fbmf_tent_east" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_nw_wood_center", + "description": "A central building can act as a core and dining hall. We should build the northwest quarter of one from wood panel.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_shack_nw_wood_center", + "blueprint_name": "central building NW corner", + "blueprint_requires": [ { "id": "fbmf_tent_west" } ], + "blueprint_provides": [ { "id": "fbmf_center", "amount": 2 }, { "id": "fbmf_nw_center" } ], + "blueprint_excludes": [ { "id": "fbmf_nw_center" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_nw_wood_center", + "description": "A central building can act as a core and dining hall. We should build out from the west room with wood panel.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_nw_wood_center", + "blueprint_name": "central building NW corner", + "blueprint_requires": [ { "id": "fbmf_west", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_center", "amount": 2 }, { "id": "fbmf_nw_center" } ], + "blueprint_excludes": [ { "id": "fbmf_nw_center" }, { "id": "fbmf_tent_west" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_wood_center", + "description": "A central building can act as a core and dining hall. We should build between the east and west rooms with wood panel.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_wood_center", + "blueprint_name": "central building north half", + "blueprint_requires": [ { "id": "fbmf_east", "amount": 4 }, { "id": "fbmf_west", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_center", "amount": 4 }, { "id": "fbmf_ne_center" }, { "id": "fbmf_nw_center" } ], + "blueprint_excludes": [ { "id": "fbmf_ne_center" }, { "id": "fbmf_nw_center" }, { "id": "fbmf_tent_east" }, { "id": "fbmf_tent_west" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_shack_se_wood_south", + "description": "A central building can act as a core and dining hall. We should build the southeast quarter of one from wood panel.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_shack_se_wood_south", + "blueprint_name": "central building SE corner", + "blueprint_requires": [ { "id": "fbmf_tent_southeast" } ], + "blueprint_provides": [ { "id": "fbmf_south", "amount": 2 }, { "id": "fbmf_se_south" } ], + "blueprint_excludes": [ { "id": "fbmf_se_south" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_se_wood_south", + "description": "A central building can act as a core and dining hall. We should build out from the southeast room with wood panel.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_se_wood_south", + "blueprint_name": "central building SE corner", + "blueprint_requires": [ { "id": "fbmf_southeast", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_south", "amount": 2 }, { "id": "fbmf_se_south" } ], + "blueprint_excludes": [ { "id": "fbmf_se_south" }, { "id": "fbmf_tent_southeast" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_shack_sw_wood_south", + "description": "A central building can act as a core and dining hall. We should build the southwest quarter of one from wood panel.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_shack_sw_wood_south", + "blueprint_name": "central building SW corner", + "blueprint_requires": [ { "id": "fbmf_tent_southwest" } ], + "blueprint_provides": [ { "id": "fbmf_south", "amount": 2 }, { "id": "fbmf_sw_south" } ], + "blueprint_excludes": [ { "id": "fbmf_sw_south" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_sw_wood_south", + "description": "A central building can act as a core and dining hall. We should build out from the southwest room with wood panel.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_sw_wood_south", + "blueprint_name": "central building SW corner", + "blueprint_requires": [ { "id": "fbmf_southwest", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_south", "amount": 2 }, { "id": "fbmf_sw_south" } ], + "blueprint_excludes": [ { "id": "fbmf_sw_south" }, { "id": "fbmf_tent_southwest" } ], + "blueprint_autocalc": true + }, + { + "type": "recipe", + "result": "faction_base_modular_field_core_wood_south", + "description": "A central building can act as a core and dining hall. We should build between the southeast and southwest rooms with wood panel.", + "category": "CC_BUILDING", + "subcategory": "CSC_BUILDING_BASES", + "autolearn": false, + "never_learn": true, + "construction_blueprint": "fbmf_core_wood_south", + "blueprint_name": "central building south half", + "blueprint_requires": [ { "id": "fbmf_southeast", "amount": 4 }, { "id": "fbmf_southwest", "amount": 4 } ], + "blueprint_provides": [ { "id": "fbmf_south", "amount": 4 }, { "id": "fbmf_se_south" }, { "id": "fbmf_sw_south" } ], + "blueprint_excludes": [ + { "id": "fbmf_se_south" }, + { "id": "fbmf_sw_south" }, + { "id": "fbmf_tent_southeast" }, + { "id": "fbmf_tent_southwest" } + ], + "blueprint_autocalc": true + } +] diff --git a/data/json/recipes/food/dry.json b/data/json/recipes/food/dry.json index dcd472b85d74d..f7de909027f15 100644 --- a/data/json/recipes/food/dry.json +++ b/data/json/recipes/food/dry.json @@ -12,6 +12,20 @@ "tools": [ [ [ "dehydrator", 25 ], [ "char_smoker", 25 ] ] ], "components": [ [ [ "fish", 1 ] ] ] }, + { + "type": "recipe", + "result": "dry_fish", + "category": "CC_FOOD", + "id_suffix": "frozen_ingredients", + "subcategory": "CSC_FOOD_DRY", + "skill_used": "cooking", + "difficulty": 2, + "time": "20 m", + "autolearn": true, + "batch_time_factors": [ 67, 5 ], + "tools": [ [ [ "dehydrator", 25 ], [ "char_smoker", 25 ] ], [ [ "surface_heat", 5, "LIST" ] ] ], + "components": [ [ [ "fish", 1 ] ] ] + }, { "type": "recipe", "result": "dry_beans", @@ -25,6 +39,20 @@ "tools": [ [ [ "dehydrator", 25 ], [ "char_smoker", 25 ] ] ], "components": [ [ [ "raw_beans", 1 ] ] ] }, + { + "type": "recipe", + "result": "dry_beans", + "category": "CC_FOOD", + "id_suffix": "frozen_ingredients", + "subcategory": "CSC_FOOD_DRY", + "skill_used": "cooking", + "difficulty": 2, + "time": "20 m", + "autolearn": true, + "batch_time_factors": [ 67, 5 ], + "tools": [ [ [ "dehydrator", 25 ], [ "char_smoker", 25 ] ], [ [ "surface_heat", 5, "LIST" ] ] ], + "components": [ [ [ "raw_beans", 1 ] ] ] + }, { "type": "recipe", "result": "dry_tofu", @@ -38,6 +66,20 @@ "tools": [ [ [ "dehydrator", 25 ], [ "char_smoker", 25 ] ] ], "components": [ [ [ "tofu", 1 ] ] ] }, + { + "type": "recipe", + "result": "dry_tofu", + "category": "CC_FOOD", + "id_suffix": "frozen_ingredients", + "subcategory": "CSC_FOOD_DRY", + "skill_used": "cooking", + "difficulty": 2, + "time": "20 m", + "autolearn": true, + "batch_time_factors": [ 67, 5 ], + "tools": [ [ [ "dehydrator", 25 ], [ "char_smoker", 25 ] ], [ [ "surface_heat", 5, "LIST" ] ] ], + "components": [ [ [ "tofu", 1 ] ] ] + }, { "type": "recipe", "result": "dry_fruit", @@ -51,6 +93,20 @@ "tools": [ [ [ "dehydrator", 25 ], [ "char_smoker", 25 ] ] ], "components": [ [ [ "sweet_fruit", 1, "LIST" ], [ "coconut", 1 ], [ "can_coconut", 1 ] ] ] }, + { + "type": "recipe", + "result": "dry_fruit", + "category": "CC_FOOD", + "id_suffix": "frozen_ingredients", + "subcategory": "CSC_FOOD_DRY", + "skill_used": "cooking", + "difficulty": 2, + "time": "20 m", + "autolearn": true, + "batch_time_factors": [ 67, 5 ], + "tools": [ [ [ "dehydrator", 25 ], [ "char_smoker", 25 ] ], [ [ "surface_heat", 5, "LIST" ] ] ], + "components": [ [ [ "sweet_fruit", 1, "LIST" ], [ "coconut", 1 ], [ "can_coconut", 1 ] ] ] + }, { "result": "dry_hflesh", "type": "recipe", @@ -61,10 +117,23 @@ "time": "18 m", "batch_time_factors": [ 67, 5 ], "book_learn": [ [ "cookbook_human", 4 ] ], - "qualities": [ { "id": "CUT", "level": 1 } ], "tools": [ [ [ "dehydrator", 25 ], [ "char_smoker", 25 ] ] ], "components": [ [ [ "human_flesh", 1 ] ] ] }, + { + "result": "dry_hflesh", + "type": "recipe", + "category": "CC_FOOD", + "id_suffix": "frozen_ingredients", + "subcategory": "CSC_FOOD_DRY", + "skill_used": "cooking", + "difficulty": 2, + "time": "20 m", + "batch_time_factors": [ 67, 5 ], + "book_learn": [ [ "cookbook_human", 4 ] ], + "tools": [ [ [ "dehydrator", 25 ], [ "char_smoker", 25 ] ], [ [ "surface_heat", 5, "LIST" ] ] ], + "components": [ [ [ "human_flesh", 1 ] ] ] + }, { "type": "recipe", "result": "dry_meat", @@ -78,6 +147,20 @@ "tools": [ [ [ "dehydrator", 25 ], [ "char_smoker", 25 ] ] ], "components": [ [ [ "meat", 1 ], [ "meat_scrap", 10 ] ] ] }, + { + "type": "recipe", + "result": "dry_meat", + "category": "CC_FOOD", + "id_suffix": "frozen_ingredients", + "subcategory": "CSC_FOOD_DRY", + "skill_used": "cooking", + "difficulty": 2, + "time": "20 m", + "autolearn": true, + "batch_time_factors": [ 67, 5 ], + "tools": [ [ [ "dehydrator", 25 ], [ "char_smoker", 25 ] ], [ [ "surface_heat", 5, "LIST" ] ] ], + "components": [ [ [ "meat", 1 ], [ "meat_scrap", 10 ] ] ] + }, { "result": "dry_meat_tainted", "type": "recipe", @@ -88,10 +171,23 @@ "time": "18 m", "batch_time_factors": [ 67, 5 ], "autolearn": true, - "qualities": [ { "id": "CUT", "level": 1 } ], "tools": [ [ [ "dehydrator", 25 ], [ "char_smoker", 25 ] ] ], "components": [ [ [ "meat_tainted", 1 ] ] ] }, + { + "result": "dry_meat_tainted", + "type": "recipe", + "category": "CC_FOOD", + "id_suffix": "frozen_ingredients", + "subcategory": "CSC_FOOD_DRY", + "skill_used": "cooking", + "difficulty": 2, + "time": "20 m", + "batch_time_factors": [ 67, 5 ], + "autolearn": true, + "tools": [ [ [ "dehydrator", 25 ], [ "char_smoker", 25 ] ], [ [ "surface_heat", 5, "LIST" ] ] ], + "components": [ [ [ "meat_tainted", 1 ] ] ] + }, { "type": "recipe", "result": "dry_mushroom", @@ -105,6 +201,20 @@ "tools": [ [ [ "dehydrator", 25 ], [ "char_smoker", 25 ] ] ], "components": [ [ [ "mushroom", 1 ], [ "mushroom_morel", 1 ] ] ] }, + { + "type": "recipe", + "result": "dry_mushroom", + "category": "CC_FOOD", + "id_suffix": "frozen_ingredients", + "subcategory": "CSC_FOOD_DRY", + "skill_used": "cooking", + "difficulty": 2, + "time": "20 m", + "autolearn": true, + "batch_time_factors": [ 67, 5 ], + "tools": [ [ [ "dehydrator", 25 ], [ "char_smoker", 25 ] ], [ [ "surface_heat", 5, "LIST" ] ] ], + "components": [ [ [ "mushroom", 1 ], [ "mushroom_morel", 1 ] ] ] + }, { "type": "recipe", "result": "dry_veggy", @@ -145,6 +255,47 @@ ] ] }, + { + "type": "recipe", + "result": "dry_veggy", + "category": "CC_FOOD", + "id_suffix": "frozen_ingredients", + "subcategory": "CSC_FOOD_DRY", + "skill_used": "cooking", + "difficulty": 2, + "time": "20 m", + "autolearn": true, + "batch_time_factors": [ 67, 5 ], + "tools": [ [ [ "dehydrator", 25 ], [ "char_smoker", 25 ] ], [ [ "surface_heat", 5, "LIST" ] ] ], + "components": [ + [ + [ "broccoli", 1 ], + [ "irradiated_broccoli", 1 ], + [ "tomato", 1 ], + [ "irradiated_tomato", 1 ], + [ "pumpkin", 1 ], + [ "irradiated_pumpkin", 1 ], + [ "zucchini", 1 ], + [ "irradiated_zucchini", 1 ], + [ "celery", 1 ], + [ "irradiated_celery", 1 ], + [ "potato", 1 ], + [ "irradiated_potato", 1 ], + [ "onion", 1 ], + [ "irradiated_onion", 1 ], + [ "carrot", 1 ], + [ "irradiated_carrot", 2 ], + [ "cabbage", 1 ], + [ "irradiated_cabbage", 1 ], + [ "lettuce", 1 ], + [ "irradiated_lettuce", 1 ], + [ "veggy", 1 ], + [ "veggy_wild", 1 ], + [ "dandelion_cooked", 1 ], + [ "wild_herbs", 40 ] + ] + ] + }, { "result": "dry_veggy_tainted", "type": "recipe", @@ -155,10 +306,23 @@ "time": "18 m", "batch_time_factors": [ 67, 5 ], "autolearn": true, - "qualities": [ { "id": "CUT", "level": 1 } ], "tools": [ [ [ "dehydrator", 25 ], [ "char_smoker", 25 ] ] ], "components": [ [ [ "veggy_tainted", 1 ] ] ] }, + { + "result": "dry_veggy_tainted", + "type": "recipe", + "category": "CC_FOOD", + "id_suffix": "frozen_ingredients", + "subcategory": "CSC_FOOD_DRY", + "skill_used": "cooking", + "difficulty": 2, + "time": "20 m", + "batch_time_factors": [ 67, 5 ], + "autolearn": true, + "tools": [ [ [ "dehydrator", 25 ], [ "char_smoker", 25 ] ], [ [ "surface_heat", 5, "LIST" ] ] ], + "components": [ [ [ "veggy_tainted", 1 ] ] ] + }, { "result": "hflesh_powder", "type": "recipe", @@ -201,6 +365,19 @@ "tools": [ [ [ "dehydrator", 25 ], [ "char_smoker", 25 ] ] ], "components": [ [ [ "milk", 1 ] ] ] }, + { + "type": "recipe", + "result": "milk_powder", + "category": "CC_FOOD", + "subcategory": "CSC_FOOD_DRY", + "skill_used": "cooking", + "difficulty": 2, + "charges": 1, + "time": "20 m", + "autolearn": true, + "tools": [ [ [ "dehydrator", 25 ], [ "char_smoker", 25 ] ], [ [ "surface_heat", 5, "LIST" ] ] ], + "components": [ [ [ "milk", 1 ] ] ] + }, { "type": "recipe", "result": "powder_eggs", @@ -215,6 +392,20 @@ "tools": [ [ [ "dehydrator", 25 ], [ "char_smoker", 25 ] ] ], "components": [ [ [ "eggs_bird", 12, "LIST" ], [ "egg_reptile", 12 ], [ "spider_egg", 4 ], [ "ant_egg", 1 ] ] ] }, + { + "type": "recipe", + "result": "powder_eggs", + "category": "CC_FOOD", + "subcategory": "CSC_FOOD_DRY", + "skill_used": "cooking", + "difficulty": 2, + "charges": 12, + "time": "20 m", + "autolearn": true, + "batch_time_factors": [ 67, 5 ], + "tools": [ [ [ "dehydrator", 25 ], [ "char_smoker", 25 ] ], [ [ "surface_heat", 5, "LIST" ] ] ], + "components": [ [ [ "eggs_bird", 12, "LIST" ], [ "egg_reptile", 12 ], [ "spider_egg", 4 ], [ "ant_egg", 1 ] ] ] + }, { "result": "protein_powder", "type": "recipe", @@ -256,5 +447,18 @@ "batch_time_factors": [ 67, 5 ], "tools": [ [ [ "dehydrator", 25 ], [ "char_smoker", 25 ] ] ], "components": [ [ [ "raw_lentils", 1 ] ] ] + }, + { + "type": "recipe", + "result": "dry_lentils", + "category": "CC_FOOD", + "subcategory": "CSC_FOOD_DRY", + "skill_used": "cooking", + "difficulty": 2, + "time": "20 m", + "autolearn": true, + "batch_time_factors": [ 67, 5 ], + "tools": [ [ [ "dehydrator", 25 ], [ "char_smoker", 25 ] ], [ [ "surface_heat", 5, "LIST" ] ] ], + "components": [ [ [ "raw_lentils", 1 ] ] ] } ] diff --git a/data/json/terrain.json b/data/json/terrain.json index f6a3c9391e534..ee7637272ea8f 100644 --- a/data/json/terrain.json +++ b/data/json/terrain.json @@ -9394,7 +9394,7 @@ "description": "A wooden platform held by a support made of logs dug into the ground.", "symbol": "8", "color": "brown", - "move_cost": 4, + "move_cost": 2, "flags": [ "TRANSPARENT", "FLAT" ], "deconstruct": { "ter_set": "t_water_sh", "items": [ { "item": "nail", "charges": [ 6, 12 ] }, { "item": "2x4", "count": 8 } ] }, "bash": { @@ -9414,7 +9414,7 @@ "looks_like": "t_dock", "symbol": "8", "color": "brown", - "move_cost": 4, + "move_cost": 2, "flags": [ "TRANSPARENT", "FLAT" ], "deconstruct": { "ter_set": "t_water_sh", "items": [ { "item": "nail", "charges": [ 6, 12 ] }, { "item": "2x4", "count": 8 } ] }, "bash": { @@ -9433,7 +9433,7 @@ "description": "A floating temporary bridge, like the ones army used to make to cross rivers.", "symbol": "8", "color": "brown", - "move_cost": 4, + "move_cost": 2, "flags": [ "TRANSPARENT", "FLAT" ], "deconstruct": { "ter_set": "t_water_dp", @@ -9456,7 +9456,7 @@ "looks_like": "t_pontoon", "symbol": "8", "color": "brown", - "move_cost": 4, + "move_cost": 2, "flags": [ "TRANSPARENT", "FLAT" ], "deconstruct": { "ter_set": "t_water_moving_dp", diff --git a/data/json/traps.json b/data/json/traps.json index a3c5270c31456..8b92672ce7382 100644 --- a/data/json/traps.json +++ b/data/json/traps.json @@ -399,7 +399,8 @@ "visibility": 99, "avoidance": 99, "difficulty": 99, - "action": "temple_toggle" + "action": "temple_toggle", + "always_invisible": true }, { "type": "trap", @@ -410,7 +411,8 @@ "visibility": 99, "avoidance": 99, "difficulty": 99, - "action": "glow" + "action": "glow", + "always_invisible": true }, { "type": "trap", @@ -421,7 +423,8 @@ "visibility": 99, "avoidance": 99, "difficulty": 99, - "action": "hum" + "action": "hum", + "always_invisible": true }, { "type": "trap", @@ -432,7 +435,8 @@ "visibility": 99, "avoidance": 99, "difficulty": 99, - "action": "shadow" + "action": "shadow", + "always_invisible": true }, { "type": "trap", @@ -443,7 +447,8 @@ "visibility": 99, "avoidance": 99, "difficulty": 99, - "action": "drain" + "action": "drain", + "always_invisible": true }, { "type": "trap", @@ -454,7 +459,8 @@ "visibility": 99, "avoidance": 99, "difficulty": 99, - "action": "snake" + "action": "snake", + "always_invisible": true }, { "type": "trap", diff --git a/data/mods/Magiclysm/Spells/animist.json b/data/mods/Magiclysm/Spells/animist.json index 01bfa01e2bb17..c179241c8abdc 100644 --- a/data/mods/Magiclysm/Spells/animist.json +++ b/data/mods/Magiclysm/Spells/animist.json @@ -17,5 +17,44 @@ "difficulty": 8, "max_level": 15, "energy_source": "MANA" + }, + { + "id": "recover_mana", + "type": "SPELL", + "name": "Life Conversion", + "description": "You channel your life force itself into your spiritual energy. You spend hp to regain mana.", + "valid_targets": [ "self" ], + "min_damage": 250, + "damage_increment": 50, + "max_damage": 2000, + "max_level": 35, + "effect": "recover_energy", + "effect_str": "MANA", + "spell_class": "ANIMIST", + "energy_source": "HP", + "base_casting_time": 500, + "base_energy_cost": 5, + "energy_increment": 1, + "final_energy_cost": 40, + "difficulty": 3 + }, + { + "id": "recover_pain", + "type": "SPELL", + "name": "Mind over Pain", + "description": "With an intense ritual that resembles crossfit, you manage to put some of your pain at bay.", + "valid_targets": [ "self" ], + "min_damage": 10, + "max_damage": 100, + "damage_increment": 2, + "max_level": 45, + "spell_class": "ANIMIST", + "effect": "recover_energy", + "effect_str": "PAIN", + "energy_source": "STAMINA", + "base_casting_time": 50000, + "base_energy_cost": 500, + "energy_increment": 50, + "final_energy_cost": 1000 } ] diff --git a/data/mods/Magiclysm/Spells/druid.json b/data/mods/Magiclysm/Spells/druid.json index b4c7860935e1f..7413ab4c06150 100644 --- a/data/mods/Magiclysm/Spells/druid.json +++ b/data/mods/Magiclysm/Spells/druid.json @@ -110,5 +110,25 @@ "max_level": 20, "spell_class": "DRUID", "energy_source": "MANA" + }, + { + "id": "recover_fatigue", + "type": "SPELL", + "name": "Nature's Trance", + "description": "Your connection to living things allows you to go into a magical trance. This allows you to recover fatige quickly in exchange for mana.", + "valid_targets": [ "self" ], + "spell_class": "DRUID", + "energy_source": "MANA", + "effect": "recover_energy", + "effect_str": "FATIGUE", + "base_casting_time": 10000, + "min_damage": 50, + "damage_increment": 10, + "max_damage": 300, + "max_level": 25, + "base_energy_cost": 500, + "energy_cost_increment": 25, + "final_energy_cost": 1125, + "difficulty": 4 } ] diff --git a/data/mods/Magiclysm/Spells/earthshaper.json b/data/mods/Magiclysm/Spells/earthshaper.json index 3d1d9a93f9d78..3af195e84af1f 100644 --- a/data/mods/Magiclysm/Spells/earthshaper.json +++ b/data/mods/Magiclysm/Spells/earthshaper.json @@ -8,7 +8,7 @@ "min_damage": 1, "max_damage": 1, "effect": "spawn_item", - "effect_str": "apt_stonefist", + "effect_str": "stonefist", "spell_class": "EARTHSHAPER", "energy_source": "MANA", "difficulty": 2, @@ -46,5 +46,25 @@ "energy_source": "MANA", "difficulty": 2, "damage_type": "bash" + }, + { + "id": "recover_stamina", + "type": "SPELL", + "name": "Stone's Endurance", + "description": "You focus on the stones beneath you and draw from their agelessness. Your mana is converted to stamina.", + "effect": "recover_energy", + "effect_str": "STAMINA", + "valid_targets": [ "self" ], + "min_damage": 150, + "max_damage": 1000, + "damage_increment": 75, + "max_level": 12, + "spell_class": "EARTHSHAPER", + "base_casting_time": 500, + "energy_source": "MANA", + "base_energy_cost": 150, + "energy_increment": 75, + "final_energy_cost": 1000, + "difficulty": 5 } ] diff --git a/data/mods/Magiclysm/Spells/technomancer.json b/data/mods/Magiclysm/Spells/technomancer.json index d8f6fd432cccd..d11acdc84718f 100644 --- a/data/mods/Magiclysm/Spells/technomancer.json +++ b/data/mods/Magiclysm/Spells/technomancer.json @@ -94,5 +94,25 @@ "min_duration": 100000, "max_duration": 1000000, "duration_increment": 2000 + }, + { + "id": "recover_bionic_power", + "type": "SPELL", + "name": "Manatricity", + "description": "You have found a way to convert your spiritual energy into power you can use for your bionics.", + "valid_targets": [ "self" ], + "min_damage": 250, + "damage_increment": 50, + "max_damage": 15000, + "base_energy_cost": 250, + "energy_increment": 50, + "final_energy_cost": 15000, + "max_level": 10, + "spell_class": "TECHNOMANCER", + "effect": "recover_energy", + "effect_str": "BIONIC", + "energy_source": "MANA", + "difficulty": 6, + "base_casting_time": 1000 } ] diff --git a/data/mods/Magiclysm/itemgroups.json b/data/mods/Magiclysm/itemgroups.json index 03facc60a655b..8a6ce56a01b9e 100644 --- a/data/mods/Magiclysm/itemgroups.json +++ b/data/mods/Magiclysm/itemgroups.json @@ -17,7 +17,7 @@ { "type": "item_group", "id": "manuals", - "items": [ [ "wizard_beginner", 3 ], [ "wizard_advanced", 1 ], [ "wizard_utility", 5 ] ] + "items": [ [ "wizard_beginner", 3 ], [ "wizard_advanced", 1 ], [ "wizard_utility", 5 ], [ "recovery_spellbook", 8 ] ] }, { "type": "item_group", @@ -32,7 +32,8 @@ [ "winter_grasp", 6 ], [ "tome_of_storms", 3 ], [ "biomancer_spellbook", 6 ], - [ "druid_spellbook", 6 ] + [ "druid_spellbook", 6 ], + [ "recovery_spellbook", 3 ] ] }, { @@ -46,7 +47,8 @@ [ "winter_grasp", 3 ], [ "tome_of_storms", 3 ], [ "biomancer_spellbook", 2 ], - [ "druid_spellbook", 2 ] + [ "druid_spellbook", 2 ], + [ "recovery_spellbook", 3 ] ] }, { @@ -59,7 +61,8 @@ [ "tome_of_storms", 3 ], [ "priest_advanced", 6 ], [ "biomancer_spellbook", 1 ], - [ "druid_spellbook", 1 ] + [ "druid_spellbook", 1 ], + [ "recovery_spellbook", 3 ] ] }, { @@ -69,5 +72,40 @@ { "item": "wand_magic_missile", "prob": 35, "charges-min": 12, "charges-max": 34 }, { "item": "wand_fireball", "prob": 25, "charges-min": 3, "charges-max": 24 } ] + }, + { + "type": "item_group", + "id": "displays", + "items": [ [ "mana_potion_lesser", 50 ] ] + }, + { + "id": "coffee_display_2", + "type": "item_group", + "item": [ [ "mana_potion_lesser", 10 ] ] + }, + { + "type": "item_group", + "id": "bar_alcohol", + "items": [ [ "mana_potion_lesser", 20 ] ] + }, + { + "type": "item_group", + "id": "bar_fridge", + "items": [ [ "mana_potion_lesser", 5 ] ] + }, + { + "type": "item_group", + "id": "pizza_beer", + "items": [ [ "mana_potion_lesser", 5 ] ] + }, + { + "type": "item_group", + "id": "pizza_soda", + "items": [ [ "mana_potion_lesser", 3 ] ] + }, + { + "id": "groce_softdrink", + "type": "item_group", + "items": [ [ "mana_potion_lesser", 15 ] ] } ] diff --git a/data/mods/Magiclysm/items/cast_spell_items.json b/data/mods/Magiclysm/items/cast_spell_items.json index ea49b7898e074..89e98c0ae4244 100644 --- a/data/mods/Magiclysm/items/cast_spell_items.json +++ b/data/mods/Magiclysm/items/cast_spell_items.json @@ -38,5 +38,27 @@ "use_action": { "type": "cast_spell", "spell_id": "magic_missile", "no_fail": true, "level": 10 }, "magazines": [ [ "crystallized_mana", [ "small_mana_crystal" ] ] ], "magazine_well": 1 + }, + { + "id": "mana_potion_lesser", + "name": "Lesser Mana Potion", + "description": "You can't buy these, so you need to save them until the last boss! Actually, don't even use them there!", + "use_action": { "type": "cast_spell", "spell_id": "recover_mana", "no_fail": true, "level": 0 }, + "type": "COMESTIBLE", + "weight": 265, + "quench": 10, + "calories": 75, + "volume": 1, + "charges": 1, + "fun": -1, + "healthy": 1, + "symbol": "~", + "container": "flask_glass", + "color": "light_blue", + "comestible_type": "DRINK", + "flags": [ "EATEN_COLD" ], + "phase": "liquid", + "price": 2500, + "freezing_point": 40 } ] diff --git a/data/mods/Magiclysm/items/spellbooks.json b/data/mods/Magiclysm/items/spellbooks.json index cd8038add5906..d9894d614998e 100644 --- a/data/mods/Magiclysm/items/spellbooks.json +++ b/data/mods/Magiclysm/items/spellbooks.json @@ -154,5 +154,19 @@ "symbol": "?", "color": "green", "use_action": { "type": "learn_spell", "spells": [ "druid_woodshaft", "druid_veggrasp", "druid_rootstrike", "druid_naturebow1" ] } + }, + { + "id": "recovery_spellbook", + "type": "GENERIC", + "name": "The Utility of Mana as an Energy Source", + "description": "This book details spells that use your mana to recover various physiological effects.", + "weight": 728, + "volume": "3 L", + "symbol": "?", + "color": "light_blue", + "use_action": { + "type": "learn_spell", + "spells": [ "recover_mana", "recover_bionic_power", "recover_pain", "recover_fatigue", "recover_stamina" ] + } } ] diff --git a/data/mods/realguns/armor.json b/data/mods/realguns/armor.json index 3f5464d8a7437..00bec7cc73ad9 100644 --- a/data/mods/realguns/armor.json +++ b/data/mods/realguns/armor.json @@ -11,11 +11,18 @@ "draw_cost": 20 } }, + { + "id": "torso_bandolier_shotgun", + "copy-from": "torso_bandolier_shotgun", + "type": "ARMOR", + "name": "torso shotgun bandolier", + "use_action": { "type": "bandolier", "capacity": 50, "ammo": [ "410", "shot", "20x66mm", "shotcanister" ], "draw_cost": 35 } + }, { "id": "bandolier_shotgun", "copy-from": "bandolier_shotgun", "type": "ARMOR", - "name": "shotgun bandolier", - "use_action": { "type": "bandolier", "capacity": 12, "ammo": [ "410", "shot", "20x66mm" ], "draw_cost": 20 } + "name": "waist shotgun bandolier", + "use_action": { "type": "bandolier", "capacity": 12, "ammo": [ "410", "shot", "20x66mm", "shotcanister" ], "draw_cost": 20 } } ] diff --git a/doc/BASECAMP.md b/doc/BASECAMP.md index a228ef00c1bbb..5a9ea97e3190d 100644 --- a/doc/BASECAMP.md +++ b/doc/BASECAMP.md @@ -1,13 +1,11 @@ # Adding alternate basecamp upgrade paths -This doesn't work yet. - A basecamp upgrade path is a series of basecamp upgrade missions that upgrade the camp. Upgrade missions are generally performed sequentially, but there is an option to have them branch. Branched missions optionally can have further missions that require missions from other branches. Bascamp upgrade paths are defined by several related files: * The recipe JSONs that define what the material, tool, and skill requirements to perform an upgrade mission and the blueprint mapgen, blueprint requirements, blueprint provides, and blueprint resources associated with each upgrade mission. * The mapgen_update JSONs that define how the map will change when the upgrade mission is complete. These may include shared instances of nested mapgen, such a standard room or tent. -* The recipe_group JSONs that define what recipes can be crafted after completing the upgrade mission. +* The recipe_group JSONs that define what recipes can be crafted after completing the upgrade mission and what camps and expansions are avialable. ## recipe JSONs The recipe JSONs are standard recipe JSONs, with the addition of a few fields. @@ -31,6 +29,10 @@ These are arbitrary strings and can be used to control the branching of the upgr provides `"id"` | meaning -- | -- `"bed"` | every 2 `"amount"`' of `"bed"` allows another expansion in the camp, to a maximum of 8, not include the camp center. +`"tool_storage"` | after this upgrade mission is complete, the Store Tools mission will be available +. +`"radio"` | after this upgrade mission is complete, two way radios communicating to the camp have extended range. +`"pantry"` | after this upgrade mission is complete, the Distribute Food mission is more efficient when dealing with short term spoilage items. `"gathering"` | after this upgrade mission is complete, the Gather Materials, Distribute Food, and Reset Sort Points basecamp missions will be available. `"firewood"` | after this upgrade mission is complete, the Gather Firewood basecamp mission will be available. `"sorting"` | after this upgrade mission is complete, the Menial Labor basecamp mission will be available. @@ -107,6 +109,31 @@ These are standard mapgen_update JSON; see doc/MAPGEN.md for more details. Each This mapgen_update places a large tent in the west central portion of the camp. The `"place_nested"` references a standard map used in the primitive field camp. +## Recipe groups +Recipe groups serve two purposes: they indicate what recipes can produced by the camp after an upgrade mission is completed, and they indicate what upgrade paths are available and where camps can be placed. + +### Upgrade Paths and Expansions +There are two special recipe groups, `"all_faction_base_types"` and `"all_faction_base_expansions"`. They both look like this: +```json + { + "type": "recipe_group", + "name": "all_faction_base_expansions", + "building_type": "NONE", + "recipes": [ + { "id": "faction_base_farm_0", "description": "Farm", "om_terrains": [ "field" ] }, + { "id": "faction_base_garage_0", "description": "Garage", "om_terrains": [ "field" ] }, + { "id": "faction_base_kitchen_0", "description": "Kitchen", "om_terrains": [ "field" ] }, + { "id": "faction_base_blacksmith_0", "description": "Blacksmith Shop", "om_terrains": [ "field" ] } + ] + }, +``` + +Each entry in the `"recipes"` array must be a dictionary with the `"id"`, `"description"`, and `"om_terrains"` fields. `"id"` is the recipe `"id"` of the recipe that starts that basecamp or basecamp expansion upgrade path. `"description"` is a short name of the basecamp or basecamp expansion. `"om_terrains"` is a list of overmap terrain ids which can be used as the basis for the basecamp or basecamp expansion. + +All recipes that start an upgrade path or expansion should have a blueprint requirement that can never be met, such as "not_an_upgrade", to prevent them from showing up as available upgrades. + +If the player attempts to start a basecamp on an overmap terrain that has two or more valid basecamp expansion paths, she will allowed to choose which path to start. + ## Sample basecamp upgrade path The primitive field camp has the following upgrade path: @@ -124,3 +151,35 @@ or `"faction_base_camp_19"` - building the camp's radio tower, which can be foll 7. `"faction_base_camp_17"` - adding better doors to camp wall and the central building must be built after `"faction_base_camp_14"` and `"faction_base_camp_16"`. After setting up the first tent, the player has a lot of options: they can build additional tents to enable expansions, they can build the well for water, they can build as much or as little of the central kitchen as they desire, or add the wall to give the camp defenses, or the radio tower to improve the range of their two-way radios. + +## Modular Basecamp conventions +The modular basecamp is a structure for designing basecamp upgrade paths. You don't have to use it, but elements of the design might get more code support in the future. + +### Layout +A modular camp is laid out on a 24x24 overmap tile. The outer 3 map squares on each side are reserved for fortifications and movement corridors, and the inner 18x18 map squares are divided into a 3x3 grid of 6x6 areas. + +Area | upper left position | lower right position +-- | -- | -- +northwest | 3, 3 | 8, 8 +north | 9, 3 | 14, 8 +northeast | 15, 3 | 20, 8 +west | 3, 9 | 8, 14 +center | 9, 9 | 14, 14 +east | 15, 9, | 20, 14 +southwest | 3, 15 | 8, 20 +south | 9, 15 | 14, 20 +southeast | 15, 15 | 20, 20 + +Ideally, nested mapgen chunks should fit entirely into a 6x6 area. + +### Naming scheme +Modular bases use the following naming scheme for recipes. Each element is separated by an `_` + +* `"faction_base"` <-- encouraged for all basecamp recipes, _and_ +* `"modular"` <-- indicates a modular construction, _and_ +* overmap terrain id <-- the base terrain that this modular base is built upon, _and_ +* DESCRIPTOR <-- arbitrary string that describes what is being built. For buildings, "room" is used to mean a construction that is intended to be part of a larger building and might share walls with other parts of the building in adjacent areas; "shack" means a free standing building. Numbers indicate stages of construction, with 4 usually meaning a complete structure, with walls and roof, _and_ +* MATERIAL <-- the type of material used in construction, such as metal, wood, brick, or wad (short for wattle-and-daub), _and_ +* AREA <-- the area in the 3x3 grid of the modular camp layout. + +blueprint keywords follow a similar scheme, but `"faction_base_modular"` is collapsed into `"fbm"` and the overmap terrain id is collapsed into a short identifier. ie, `"fbmf"` is the keyword identifier for elements of the modular field base. diff --git a/doc/JSON_INFO.md b/doc/JSON_INFO.md index e21b25601b118..8cdf3103d077e 100644 --- a/doc/JSON_INFO.md +++ b/doc/JSON_INFO.md @@ -867,16 +867,16 @@ See also VEHICLE_JSON.md ### Magazine ```C++ -"type": "MAGAZINE", // Defines this as a MAGAZINE -... // same entries as above for the generic item. - // additional some magazine specific entries: -"ammo_type": "223", // What type of ammo this magazine can be loaded with -"capacity" : 15, // Capacity of magazine (in equivalent units to ammo charges) -"count" : 0, // Default amount of ammo contained by a magazine (set this for ammo belts) -"default_ammo": "556",// If specified override the default ammo (optionally set this for ammo belts) -"reliability" : 8, // How reliable this this magazine on a range of 0 to 10? (see GAME_BALANCE.md) -"reload_time" : 100, // How long it takes to load each unit of ammo into the magazine -"linkage" : "ammolink"// If set one linkage (of given type) is dropped for each unit of ammo consumed (set for disintegrating ammo belts) +"type": "MAGAZINE", // Defines this as a MAGAZINE +... // same entries as above for the generic item. + // additional some magazine specific entries: +"ammo_type": [ "40", "357sig" ], // What types of ammo this magazine can be loaded with +"capacity" : 15, // Capacity of magazine (in equivalent units to ammo charges) +"count" : 0, // Default amount of ammo contained by a magazine (set this for ammo belts) +"default_ammo": "556", // If specified override the default ammo (optionally set this for ammo belts) +"reliability" : 8, // How reliable this this magazine on a range of 0 to 10? (see GAME_BALANCE.md) +"reload_time" : 100, // How long it takes to load each unit of ammo into the magazine +"linkage" : "ammolink" // If set one linkage (of given type) is dropped for each unit of ammo consumed (set for disintegrating ammo belts) ``` ### Armor @@ -1071,27 +1071,27 @@ It could also be written as a generic item ("type": "GENERIC") with "armor_data" Guns can be defined like this: ```C++ -"type": "GUN", // Defines this as a GUN -... // same entries as above for the generic item. - // additional some gun specific entries: -"skill": "pistol", // Skill used for firing -"ammo": "nail", // Ammo type accepted for reloading -"ranged_damage": 0, // Ranged damage when fired -"range": 0, // Range when fired -"dispersion": 32, // Inaccuracy of gun, measured in quarter-degrees +"type": "GUN", // Defines this as a GUN +... // same entries as above for the generic item. + // additional some gun specific entries: +"skill": "pistol", // Skill used for firing +"ammo": [ "357", "38" ], // Ammo types accepted for reloading +"ranged_damage": 0, // Ranged damage when fired +"range": 0, // Range when fired +"dispersion": 32, // Inaccuracy of gun, measured in quarter-degrees // When sight_dispersion and aim_speed are present in a gun mod, the aiming system picks the "best" // sight to use for each aim action, which is the fastest sight with a dispersion under the current // aim threshold. -"sight_dispersion": 10, // Inaccuracy of gun derived from the sight mechanism, also in quarter-degrees -"aim_speed": 3, // A measure of how quickly the player can aim, in moves per point of dispersion. -"recoil": 0, // Recoil caused when firing, in quarter-degrees of dispersion. -"durability": 8, // Resistance to damage/rusting, also determines misfire chance -"burst": 5, // Number of shots fired in burst mode -"clip_size": 100, // Maximum amount of ammo that can be loaded -"ups_charges": 0, // Additionally to the normal ammo (if any), a gun can require some charges from an UPS. This also works on mods. Attaching a mod with ups_charges will add/increase ups drain on the weapon. -"reload": 450, // Amount of time to reload, 100 = 1 second = 1 "turn" +"sight_dispersion": 10, // Inaccuracy of gun derived from the sight mechanism, also in quarter-degrees +"aim_speed": 3, // A measure of how quickly the player can aim, in moves per point of dispersion. +"recoil": 0, // Recoil caused when firing, in quarter-degrees of dispersion. +"durability": 8, // Resistance to damage/rusting, also determines misfire chance +"burst": 5, // Number of shots fired in burst mode +"clip_size": 100, // Maximum amount of ammo that can be loaded +"ups_charges": 0, // Additionally to the normal ammo (if any), a gun can require some charges from an UPS. This also works on mods. Attaching a mod with ups_charges will add/increase ups drain on the weapon. +"reload": 450, // Amount of time to reload, 100 = 1 second = 1 "turn" "built_in_mods": ["m203"], //An array of mods that will be integrated in the weapon using the IRREMOVABLE tag. -"default_mods": ["m203"] //An array of mods that will be added to a weapon on spawn. +"default_mods": ["m203"] //An array of mods that will be added to a weapon on spawn. ``` Alternately, every item (book, tool, armor, even food) can be used as gun if it has gun_data: ```json @@ -1116,7 +1116,7 @@ Gun mods can be defined like this: "mod_targets": [ "crossbow" ], // Mandatory. What kind of weapons can this gunmod be used with? "acceptable_ammo": [ "9mm" ], // Optional filter restricting mod to guns with those base (before modifiers) ammo types "install_time": "30 s", // Optional time installation takes. Installation is instantaneous if unspecified. An integer will be read as moves or a time string can be used. -"ammo_modifier": "57", // Optional field which if specified modifies parent gun to use this ammo type +"ammo_modifier": [ "57" ], // Optional field which if specified modifies parent gun to use these ammo types "burst_modifier": 3, // Optional field increasing or decreasing base gun burst size "damage_modifier": -1, // Optional field increasing or decreasing base gun damage "dispersion_modifier": 15, // Optional field increasing or decreasing base gun dispersion @@ -1153,7 +1153,7 @@ Gun mods can be defined like this: "charge_factor": 5, // this tool uses charge_factor charges for every charge required in a recipe; intended for tools that have a "sub" field but use a different ammo that the original tool "charges_per_use": 1, // Charges consumed per tool use "turns_per_charge": 20, // Charges consumed over time -"ammo": "NULL", // Ammo type used for reloading +"ammo": [ "NULL" ], // Ammo types used for reloading "revert_to": "torch_done", // Transforms into item when charges are expended "use_action": "firestarter" // Action performed when tool is used, see special definition below ``` diff --git a/doc/MAGIC.md b/doc/MAGIC.md index f69e005550afe..269c99e3fcad6 100644 --- a/doc/MAGIC.md +++ b/doc/MAGIC.md @@ -74,6 +74,13 @@ Any aoe will manifest as a circular area centered on the target, and will only d * "teleport_random" - teleports the player randomly range spaces with aoe variation +* "recover_energy" - recovers an energy source (defined in the effect_str, shown below) equal to damage of the spell +- "MANA" +- "STAMINA" +- "FATIGUE" +- "PAIN" +- "BIONIC" + ##### For Spells that have an attack type, these are the available damage types: * "fire" * "acid" diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index c7ec8870049ed..5cca2eb540304 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -87,7 +87,7 @@ class npc; -#define dbg(x) DebugLog((DebugLevel)(x),D_GAME) << __FILE__ << ":" << __LINE__ << ": " +#define dbg(x) DebugLog((x),D_GAME) << __FILE__ << ":" << __LINE__ << ": " const skill_id skill_survival( "survival" ); const skill_id skill_firstaid( "firstaid" ); @@ -1806,6 +1806,7 @@ void activity_handlers::reload_finish( player_activity *act, player *p ) } item &reloadable = *act->targets[ 0 ]; + item &ammo = *act->targets[1]; const int qty = act->index; const bool is_speedloader = act->targets[ 1 ]->has_flag( "SPEEDLOADER" ); @@ -1821,7 +1822,7 @@ void activity_handlers::reload_finish( player_activity *act, player *p ) if( reloadable.has_flag( "RELOAD_ONE" ) && !is_speedloader ) { for( int i = 0; i != qty; ++i ) { - if( reloadable.ammo_type() == ammotype( "bolt" ) ) { + if( ammo.ammo_type() == ammotype( "bolt" ) ) { msg = _( "You insert a bolt into the %s." ); } else { msg = _( "You insert a cartridge into the %s." ); @@ -3927,6 +3928,8 @@ void activity_handlers::spellcasting_finish( player_activity *act, player *p ) spell_effect::teleport( casting.range(), casting.range() + casting.aoe() ); } else if( fx == "spawn_item" ) { spell_effect::spawn_ethereal_item( casting ); + } else if( fx == "recover_energy" ) { + spell_effect::recover_energy( casting, target ); } else { debugmsg( "ERROR: Spell effect not defined properly." ); } diff --git a/src/advanced_inv.cpp b/src/advanced_inv.cpp index 465d8231a81be..7cd164b1f0dd7 100644 --- a/src/advanced_inv.cpp +++ b/src/advanced_inv.cpp @@ -351,7 +351,7 @@ void advanced_inventory::print_items( advanced_inventory_pane &pane, bool active stolen = true; } } - if( it.ammo_type() == "money" ) { + if( it.ammo_types().count( ammotype( "money" ) ) ) { //Count charges // TODO: transition to the item_location system used for the normal inventory unsigned long charges_total = 0; diff --git a/src/artifact.cpp b/src/artifact.cpp index 1ad07d3a8ab6d..b7aca7f62e067 100644 --- a/src/artifact.cpp +++ b/src/artifact.cpp @@ -1165,7 +1165,12 @@ void it_artifact_tool::deserialize( JsonObject &jo ) tool->charges_per_use = jo.get_int( "charges_per_use" ); tool->turns_per_charge = jo.get_int( "turns_per_charge" ); - tool->ammo_id = ammotype( jo.get_string( "ammo" ) ); + + JsonArray atypes = jo.get_array( "ammo" ); + for( size_t i = 0; i < atypes.size(); ++i ) { + tool->ammo_id.insert( ammotype( atypes.get_string( i ) ) ); + } + tool->revert_to.emplace( jo.get_string( "revert_to", "null" ) ); if( *tool->revert_to == "null" ) { tool->revert_to.reset(); diff --git a/src/avatar_action.cpp b/src/avatar_action.cpp index f2351b051a379..3ac993015f2e9 100644 --- a/src/avatar_action.cpp +++ b/src/avatar_action.cpp @@ -26,7 +26,7 @@ #include "vpart_position.h" #include "vpart_reference.h" -#define dbg(x) DebugLog((DebugLevel)(x),D_SDL) << __FILE__ << ":" << __LINE__ << ": " +#define dbg(x) DebugLog((x),D_SDL) << __FILE__ << ":" << __LINE__ << ": " static const trait_id trait_BURROW( "BURROW" ); static const trait_id trait_SHELL2( "SHELL2" ); @@ -706,6 +706,10 @@ bool avatar_action::fire( avatar &you, map &m, item &weapon, int bp_cost ) if( !gun ) { add_msg( m_info, _( "The %s can't be fired in its current state." ), weapon.tname() ); return false; + } else if( !weapon.ammo_types().count( weapon.ammo_data()->ammo->type ) ) { + add_msg( m_info, _( "The %s can't be fired while loaded with incompatible ammunition %s" ), + weapon.tname(), weapon.ammo_current() ); + return false; } targeting_data args = { diff --git a/src/basecamp.cpp b/src/basecamp.cpp index 26e67b0dd9e86..bbea03cd6a4b4 100644 --- a/src/basecamp.cpp +++ b/src/basecamp.cpp @@ -23,6 +23,7 @@ #include "player.h" #include "npc.h" #include "recipe.h" +#include "recipe_dictionary.h" #include "recipe_groups.h" #include "requirements.h" #include "rng.h" @@ -131,7 +132,20 @@ void basecamp::add_expansion( const std::string &terrain, const tripoint &new_po directions.push_back( dir ); } -void basecamp::define_camp( npc &p ) +void basecamp::add_expansion( const std::string &bldg, const tripoint &new_pos, + const std::string &dir ) +{ + expansion_data e; + e.type = base_camps::faction_decode( bldg ); + e.cur_level = -1; + e.pos = new_pos; + expansions[ dir ] = e; + directions.push_back( dir ); + update_provides( bldg, expansions[ dir ] ); + update_resources( bldg ); +} + +void basecamp::define_camp( npc &p, const std::string &camp_type ) { query_new_name(); omt_pos = p.global_omt_location(); @@ -144,8 +158,8 @@ void basecamp::define_camp( npc &p ) const std::string om_cur = omt_ref.id().c_str(); if( om_cur.find( base_camps::prefix ) == std::string::npos ) { expansion_data e; - e.type = "camp"; - e.cur_level = 0; + e.type = base_camps::faction_decode( camp_type ); + e.cur_level = -1; e.pos = omt_pos; expansions[ base_camps::base_dir ] = e; omt_ref = oter_id( "faction_base_camp_0" ); @@ -245,9 +259,9 @@ const std::vector basecamp::available_upgrades( const std::str auto e = expansions.find( dir ); if( e != expansions.end() ) { expansion_data &e_data = e->second; - for( int number = 1; number < base_camps::max_upgrade_by_type( e_data.type ); number++ ) { - const std::string &bldg = base_camps::faction_encode_abs( e_data, number ); - const recipe &recp = recipe_id( bldg ).obj(); + for( const recipe *recp_p : recipe_dict.all_blueprints() ) { + const recipe &recp = *recp_p; + const std::string &bldg = recp.result().c_str(); // skip buildings that are completed if( e_data.provides.find( bldg ) != e_data.provides.end() ) { continue; @@ -303,19 +317,19 @@ const std::vector basecamp::available_upgrades( const std::str // recipes and craft support functions std::map basecamp::recipe_deck( const std::string &dir ) const { - if( dir == "ALL" || dir == "COOK" || dir == "BASE" || dir == "FARM" || dir == "SMITH" ) { - return recipe_group::get_recipes( dir ); + std::map recipes = recipe_group::get_recipes_by_bldg( dir ); + if( !recipes.empty() ) { + return recipes; } - std::map cooking_recipes; const auto &e = expansions.find( dir ); if( e == expansions.end() ) { - return cooking_recipes; + return recipes; } for( const auto &provides : e->second.provides ) { - std::map test_s = recipe_group::get_recipes( provides.first ); - cooking_recipes.insert( test_s.begin(), test_s.end() ); + const auto &test_s = recipe_group::get_recipes_by_id( provides.first ); + recipes.insert( test_s.begin(), test_s.end() ); } - return cooking_recipes; + return recipes; } const std::string basecamp::get_gatherlist() const @@ -329,7 +343,6 @@ const std::string basecamp::get_gatherlist() const } } return "forest"; - } void basecamp::add_resource( const itype_id &camp_resource ) @@ -622,19 +635,13 @@ std::string basecamp::expansion_tab( const std::string &dir ) const if( dir == base_camps::base_dir ) { return _( "Base Missions" ); } - auto e = expansions.find( dir ); + const auto &expansion_types = recipe_group::get_recipes_by_id( "all_faction_base_expansions" ); + + const auto &e = expansions.find( dir ); if( e != expansions.end() ) { - if( e->second.type == "garage" ) { - return _( "Garage Expansion" ); - } - if( e->second.type == "kitchen" ) { - return _( "Kitchen Expansion" ); - } - if( e->second.type == "blacksmith" ) { - return _( "Blacksmith Expansion" ); - } - if( e->second.type == "farm" ) { - return _( "Farm Expansion" ); + const auto e_type = expansion_types.find( base_camps::faction_encode_abs( e->second, 0 ) ); + if( e_type != expansion_types.end() ) { + return e_type->second + _( "Expansion" ); } } return _( "Empty Expansion" ); diff --git a/src/basecamp.h b/src/basecamp.h index 645dcfe9a6342..f82a4a561da23 100644 --- a/src/basecamp.h +++ b/src/basecamp.h @@ -112,7 +112,9 @@ class basecamp void set_name( const std::string &new_name ); void query_new_name(); void add_expansion( const std::string &terrain, const tripoint &new_pos ); - void define_camp( npc &p ); + void add_expansion( const std::string &bldg, const tripoint &new_pos, + const std::string &dir ); + void define_camp( npc &p, const std::string &camp_type = "default" ); std::string expansion_tab( const std::string &dir ) const; diff --git a/src/cata_tiles.cpp b/src/cata_tiles.cpp index 9ee5f050c5f18..ad52cbe309379 100644 --- a/src/cata_tiles.cpp +++ b/src/cata_tiles.cpp @@ -67,7 +67,7 @@ #include "translations.h" #include "type_id.h" -#define dbg(x) DebugLog((DebugLevel)(x),D_SDL) << __FILE__ << ":" << __LINE__ << ": " +#define dbg(x) DebugLog((x),D_SDL) << __FILE__ << ":" << __LINE__ << ": " static const std::string ITEM_HIGHLIGHT( "highlight_item" ); diff --git a/src/catacharset.h b/src/catacharset.h index c118ac9c619a8..c4ab1c7beb41a 100644 --- a/src/catacharset.h +++ b/src/catacharset.h @@ -8,6 +8,7 @@ #define ANY_LENGTH 5 #define NULL_UNICODE 0x0000 +#define PERCENT_SIGN_UNICODE 0x0025 #define UNKNOWN_UNICODE 0xFFFD class utf8_wrapper; diff --git a/src/character.cpp b/src/character.cpp index 9b9a57e1a23f5..0f0b6b28801c0 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -1045,7 +1045,7 @@ void Character::remove_mission_items( int mission_id ) std::vector Character::get_ammo( const ammotype &at ) const { return items_with( [at]( const item & it ) { - return it.is_ammo() && it.type->ammo->type.count( at ); + return it.ammo_type() == at; } ); } @@ -1081,7 +1081,7 @@ void find_ammo_helper( T &src, const item &obj, bool empty, Output out, bool nes } if( obj.magazine_integral() ) { // find suitable ammo excluding that already loaded in magazines - ammotype ammo = obj.ammo_type(); + std::set ammo = obj.ammo_types(); const auto mags = obj.magazine_compatible(); src.visit_items( [&src, &nested, &out, &mags, ammo]( item * node ) { @@ -1095,13 +1095,18 @@ void find_ammo_helper( T &src, const item &obj, bool empty, Output out, bool nes } if( node->is_ammo_container() && !node->contents.empty() && !node->contents_made_of( SOLID ) ) { - if( node->contents.front().type->ammo->type.count( ammo ) ) { - out = item_location( src, node ); + for( const ammotype &at : ammo ) { + if( node->contents.front().ammo_type() == at ) { + out = item_location( src, node ); + } } return VisitResponse::SKIP; } - if( node->is_ammo() && node->type->ammo->type.count( ammo ) ) { - out = item_location( src, node ); + + for( const ammotype &at : ammo ) { + if( node->ammo_type() == at ) { + out = item_location( src, node ); + } } if( node->is_magazine() && node->has_flag( "SPEEDLOADER" ) ) { if( mags.count( node->typeId() ) && node->ammo_remaining() ) { diff --git a/src/construction.cpp b/src/construction.cpp index 36b309a2c213d..3bacc697f7342 100644 --- a/src/construction.cpp +++ b/src/construction.cpp @@ -267,10 +267,10 @@ void construction_menu() const inventory &total_inv = g->u.crafting_inventory(); input_context ctxt( "CONSTRUCTION" ); - ctxt.register_action( "UP", _( "Move cursor up" ) ); - ctxt.register_action( "DOWN", _( "Move cursor down" ) ); - ctxt.register_action( "RIGHT", _( "Move tab right" ) ); - ctxt.register_action( "LEFT", _( "Move tab left" ) ); + ctxt.register_action( "UP", translate_marker( "Move cursor up" ) ); + ctxt.register_action( "DOWN", translate_marker( "Move cursor down" ) ); + ctxt.register_action( "RIGHT", translate_marker( "Move tab right" ) ); + ctxt.register_action( "LEFT", translate_marker( "Move tab left" ) ); ctxt.register_action( "PAGE_UP" ); ctxt.register_action( "PAGE_DOWN" ); ctxt.register_action( "CONFIRM" ); diff --git a/src/consumption.cpp b/src/consumption.cpp index 592034d45557f..1fedb97b98284 100644 --- a/src/consumption.cpp +++ b/src/consumption.cpp @@ -1158,7 +1158,7 @@ bool player::can_feed_battery_with( const item &it ) const return false; } - return it.type->ammo->type.count( ammotype( "battery" ) ); + return it.ammo_type() == ammotype( "battery" ); } bool player::feed_battery_with( item &it ) @@ -1205,7 +1205,7 @@ bool player::can_feed_reactor_with( const item &it ) const } return std::any_of( acceptable.begin(), acceptable.end(), [ &it ]( const ammotype & elem ) { - return it.type->ammo->type.count( elem ); + return it.ammo_type() == elem; } ); } diff --git a/src/crafting.cpp b/src/crafting.cpp index 30f56fcabeaa4..56cf7613b1ca9 100644 --- a/src/crafting.cpp +++ b/src/crafting.cpp @@ -1933,7 +1933,7 @@ void player::complete_disassemble( int item_pos, const tripoint &loc, // If the recipe has a `FULL_MAGAZINE` flag, spawn any magazines full of ammo if( newit.is_magazine() && dis.has_flag( "FULL_MAGAZINE" ) ) { - newit.ammo_set( newit.type->magazine->type.obj().default_ammotype(), newit.ammo_capacity() ); + newit.ammo_set( newit.ammo_default(), newit.ammo_capacity() ); } for( ; compcount > 0; compcount-- ) { @@ -2048,10 +2048,10 @@ void remove_ammo( item &dis_item, player &p ) drop_or_handle( ammodrop, p ); dis_item.charges = 0; } - if( dis_item.is_tool() && dis_item.charges > 0 && dis_item.ammo_type() ) { - item ammodrop( dis_item.ammo_type()->default_ammotype(), calendar::turn ); + if( dis_item.is_tool() && dis_item.charges > 0 && dis_item.ammo_current() != "null" ) { + item ammodrop( dis_item.ammo_current(), calendar::turn ); ammodrop.charges = dis_item.charges; - if( dis_item.ammo_type() == ammotype( "plutonium" ) ) { + if( dis_item.ammo_current() == "plutonium" ) { ammodrop.charges /= PLUTONIUM_CHARGES; } drop_or_handle( ammodrop, p ); diff --git a/src/creature_tracker.cpp b/src/creature_tracker.cpp index 2ef1b0b8f8144..da6517829354f 100644 --- a/src/creature_tracker.cpp +++ b/src/creature_tracker.cpp @@ -12,7 +12,7 @@ #include "string_formatter.h" #include "type_id.h" -#define dbg(x) DebugLog((DebugLevel)(x),D_GAME) << __FILE__ << ":" << __LINE__ << ": " +#define dbg(x) DebugLog((x),D_GAME) << __FILE__ << ":" << __LINE__ << ": " Creature_tracker::Creature_tracker() = default; diff --git a/src/debug.cpp b/src/debug.cpp index 316620a9958cb..e1ee30764d434 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -588,14 +588,15 @@ void debug_write_backtrace( std::ostream &out ) for( USHORT i = 0; i < num_bt; ++i ) { DWORD64 off; out << "\n\t("; - if( SymFromAddr( proc, ( DWORD64 ) bt[i], &off, &sym ) ) { + if( SymFromAddr( proc, reinterpret_cast( bt[i] ), &off, &sym ) ) { out << sym.Name << "+0x" << std::hex << off << std::dec; } out << "@" << bt[i]; - DWORD64 mod_base = SymGetModuleBase64( proc, ( DWORD64 ) bt[i] ); + DWORD64 mod_base = SymGetModuleBase64( proc, reinterpret_cast( bt[i] ) ); if( mod_base ) { out << "["; - DWORD mod_len = GetModuleFileName( ( HMODULE ) mod_base, mod_path, module_path_len ); + DWORD mod_len = GetModuleFileName( reinterpret_cast( mod_base ), mod_path, + module_path_len ); // mod_len == module_path_len means insufficient buffer if( mod_len > 0 && mod_len < module_path_len ) { const char *mod_name = mod_path + mod_len; @@ -605,7 +606,8 @@ void debug_write_backtrace( std::ostream &out ) } else { out << "0x" << std::hex << mod_base << std::dec; } - out << "+0x" << std::hex << ( uintptr_t ) bt[i] - mod_base << std::dec << "]"; + out << "+0x" << std::hex << reinterpret_cast( bt[i] ) - mod_base << + std::dec << "]"; } out << "), "; } diff --git a/src/debug.h b/src/debug.h index 2da05a4520fd6..f2d012028ce0e 100644 --- a/src/debug.h +++ b/src/debug.h @@ -33,7 +33,7 @@ * (e.g. mapgen.cpp contains only messages for D_MAP_GEN, npcmove.cpp only D_NPC). * Those files contain a macro at top: @code -#define dbg(x) DebugLog((DebugLevel)(x), D_NPC) << __FILE__ << ":" << __LINE__ << ": " +#define dbg(x) DebugLog((x), D_NPC) << __FILE__ << ":" << __LINE__ << ": " @endcode * It allows to call the debug system and just supply the debug level, the debug * class is automatically inserted as it is the same for the whole file. Also this @@ -123,6 +123,12 @@ enum DebugLevel { DL_ALL = ( 1 << 5 ) - 1 }; +inline DebugLevel operator|( DebugLevel l, DebugLevel r ) +{ + return static_cast( + static_cast>( l ) | r ); +} + /** * Debugging areas can be enabled for each of those areas separately. * If you add an entry, add an entry in that function: diff --git a/src/debug_menu.cpp b/src/debug_menu.cpp index 4150124e2caa0..483e5bb55cf7b 100644 --- a/src/debug_menu.cpp +++ b/src/debug_menu.cpp @@ -69,7 +69,7 @@ #include "sdl_wrappers.h" #endif -#define dbg(x) DebugLog((DebugLevel)(x),D_GAME) << __FILE__ << ":" << __LINE__ << ": " +#define dbg(x) DebugLog((x),D_GAME) << __FILE__ << ":" << __LINE__ << ": " const efftype_id effect_riding( "riding" ); namespace debug_menu { diff --git a/src/defense.cpp b/src/defense.cpp index 673291872a93b..d349f160792ec 100644 --- a/src/defense.cpp +++ b/src/defense.cpp @@ -499,11 +499,11 @@ void defense_game::setup() refresh_setup( w, selection ); input_context ctxt( "DEFENSE_SETUP" ); - ctxt.register_action( "UP", _( "Previous option" ) ); - ctxt.register_action( "DOWN", _( "Next option" ) ); - ctxt.register_action( "LEFT", _( "Cycle option value" ) ); - ctxt.register_action( "RIGHT", _( "Cycle option value" ) ); - ctxt.register_action( "CONFIRM", _( "Toggle option" ) ); + ctxt.register_action( "UP", translate_marker( "Previous option" ) ); + ctxt.register_action( "DOWN", translate_marker( "Next option" ) ); + ctxt.register_action( "LEFT", translate_marker( "Cycle option value" ) ); + ctxt.register_action( "RIGHT", translate_marker( "Cycle option value" ) ); + ctxt.register_action( "CONFIRM", translate_marker( "Toggle option" ) ); ctxt.register_action( "NEXT_TAB" ); ctxt.register_action( "PREV_TAB" ); ctxt.register_action( "START" ); diff --git a/src/dump.cpp b/src/dump.cpp index 6487ac76e8bde..8e72e4fc24d38 100644 --- a/src/dump.cpp +++ b/src/dump.cpp @@ -30,6 +30,7 @@ #include "units.h" #include "material.h" #include "string_id.h" +#include "output.h" bool game::dump_stats( const std::string &what, dump_mode mode, const std::vector &opts ) @@ -74,23 +75,20 @@ bool game::dump_stats( const std::string &what, dump_mode mode, "Range", "Dispersion", "Recoil", "Damage", "Pierce", "Damage multiplier" }; auto dump = [&rows]( const item & obj ) { - // a common task is comparing ammo by type so ammo has multiple repeat the entry - for( const auto &e : obj.type->ammo->type ) { - std::vector r; - r.push_back( obj.tname( 1, false ) ); - r.push_back( e.str() ); - r.push_back( to_string( obj.volume() / units::legacy_volume_factor ) ); - r.push_back( to_string( to_gram( obj.weight() ) ) ); - r.push_back( to_string( obj.type->stack_size ) ); - r.push_back( to_string( obj.type->ammo->range ) ); - r.push_back( to_string( obj.type->ammo->dispersion ) ); - r.push_back( to_string( obj.type->ammo->recoil ) ); - damage_instance damage = obj.type->ammo->damage; - r.push_back( to_string( damage.total_damage() ) ); - r.push_back( to_string( damage.empty() ? 0 : ( *damage.begin() ).res_pen ) ); - r.push_back( obj.type->ammo->prop_damage ? to_string( *obj.type->ammo->prop_damage ) : "---" ); - rows.push_back( r ); - } + std::vector r; + r.push_back( obj.tname( 1, false ) ); + r.push_back( obj.ammo_type().str() ); + r.push_back( to_string( obj.volume() / units::legacy_volume_factor ) ); + r.push_back( to_string( to_gram( obj.weight() ) ) ); + r.push_back( to_string( obj.type->stack_size ) ); + r.push_back( to_string( obj.type->ammo->range ) ); + r.push_back( to_string( obj.type->ammo->dispersion ) ); + r.push_back( to_string( obj.type->ammo->recoil ) ); + damage_instance damage = obj.type->ammo->damage; + r.push_back( to_string( damage.total_damage() ) ); + r.push_back( to_string( damage.empty() ? 0 : ( *damage.begin() ).res_pen ) ); + r.push_back( obj.type->ammo->prop_damage ? to_string( *obj.type->ammo->prop_damage ) : "---" ); + rows.push_back( r ); }; for( const itype *e : item_controller->all() ) { if( e->ammo ) { @@ -187,7 +185,11 @@ bool game::dump_stats( const std::string &what, dump_mode mode, auto dump = [&rows, &locations]( const standard_npc & who, const item & obj ) { std::vector r; r.push_back( obj.tname( 1, false ) ); - r.push_back( obj.ammo_type() ? obj.ammo_type().str() : "" ); + const std::set atypes = obj.ammo_types(); + r.push_back( !atypes.empty() ? enumerate_as_string( atypes.begin(), + atypes.end(), []( const ammotype & at ) { + return at.str(); + }, enumeration_conjunction::none ) : "" ); r.push_back( to_string( obj.volume() / units::legacy_volume_factor ) ); r.push_back( to_string( to_gram( obj.weight() ) ) ); r.push_back( to_string( obj.ammo_capacity() ) ); @@ -213,7 +215,7 @@ bool game::dump_stats( const std::string &what, dump_mode mode, if( !gun.magazine_integral() ) { gun.emplace_back( gun.magazine_default() ); } - gun.ammo_set( gun.ammo_type()->default_ammotype(), gun.ammo_capacity() ); + gun.ammo_set( gun.ammo_default( false ), gun.ammo_capacity() ); dump( test_npcs[ "S1" ], gun ); diff --git a/src/enums.h b/src/enums.h index 14fd7d2b017b0..88039e4ca683e 100644 --- a/src/enums.h +++ b/src/enums.h @@ -2,6 +2,7 @@ #ifndef ENUMS_H #define ENUMS_H +#include #include #include #include @@ -356,6 +357,18 @@ inline bool operator<( const tripoint &a, const tripoint &b ) return false; } +static const std::array eight_horizontal_neighbors = { { + { -1, -1, 0 }, + { 0, -1, 0 }, + { +1, -1, 0 }, + { -1, 0, 0 }, + { +1, 0, 0 }, + { -1, +1, 0 }, + { 0, +1, 0 }, + { +1, +1, 0 }, + } +}; + struct rectangle { point p_min; point p_max; diff --git a/src/explosion.cpp b/src/explosion.cpp index b53417a2514f0..2a4a43ae1ee2b 100644 --- a/src/explosion.cpp +++ b/src/explosion.cpp @@ -720,7 +720,7 @@ void emp_blast( const tripoint &p ) } // Drain any items of their battery charge for( auto &it : g->m.i_at( x, y ) ) { - if( it.is_tool() && it.ammo_type() == ammotype( "battery" ) ) { + if( it.is_tool() && it.ammo_current() == "battery" ) { it.charges = 0; } } diff --git a/src/faction_camp.cpp b/src/faction_camp.cpp index 3001aa4050732..2d03187ee035b 100644 --- a/src/faction_camp.cpp +++ b/src/faction_camp.cpp @@ -106,6 +106,9 @@ struct miss_data { std::string ret_desc; }; +std::string select_camp_option( const std::map &pos_options, + const std::string &option ); + // enventually this will move to JSON std::map miss_info = {{ { @@ -421,7 +424,7 @@ static bool update_time_fixed( std::string &entry, const comp_list &npc_list, return avail; } -static cata::optional get_basecamp( npc &p ) +static cata::optional get_basecamp( npc &p, const std::string &camp_type = "default" ) { tripoint omt_pos = p.global_omt_location(); @@ -435,46 +438,79 @@ static cata::optional get_basecamp( npc &p ) return cata::nullopt; } basecamp *temp_camp = *bcp; - temp_camp->define_camp( p ); + temp_camp->define_camp( p, camp_type ); return temp_camp; } +std::string base_camps::select_camp_option( const std::map &pos_options, + const std::string &option ) +{ + std::vector pos_name_ids; + std::vector pos_names; + for( const auto &it : pos_options ) { + pos_names.push_back( it.first ); + pos_name_ids.push_back( it.second ); + } + + if( pos_name_ids.size() == 1 ) { + return pos_name_ids.front(); + } + + const int choice = uilist( _( option ), pos_names ); + if( choice < 0 || static_cast( choice ) >= pos_name_ids.size() ) { + popup( _( "You choose to wait..." ) ); + return std::string(); + } + return pos_name_ids[choice]; +} + void talk_function::start_camp( npc &p ) { const tripoint omt_pos = p.global_omt_location(); oter_id &omt_ref = overmap_buffer.ter( omt_pos ); - if( omt_ref.id() != "field" ) { - popup( _( "You must build your camp in an empty field." ) ); + const auto &pos_camps = recipe_group::get_recipes_by_id( "all_faction_base_types", + omt_ref.id().c_str() ); + if( pos_camps.empty() ) { + popup( _( "You cannot build a camp here." ) ); + return; + } + const std::string &camp_type = base_camps::select_camp_option( pos_camps, + _( "Select a camp type:" ) ); + if( camp_type.empty() ) { return; } std::vector> om_region = om_building_region( omt_pos, 1 ); + int near_fields = 0; for( const auto &om_near : om_region ) { - if( om_near.first != "field" && om_near.first != "forest" && - om_near.first != "forest_thick" && om_near.first != "forest_water" && - om_near.first.find( "river_" ) == std::string::npos ) { - popup( _( "You need more room for camp expansions!" ) ); - return; + const oter_id &om_type = oter_id( om_near.first ); + if( is_ot_subtype( "field", om_type ) ) { + near_fields += 1; } } + if( near_fields < 4 ) { + popup( _( "You need more at least 4 adjacent for camp expansions!" ) ); + return; + } std::vector> om_region_ext = om_building_region( omt_pos, 3 ); int forests = 0; int waters = 0; int swamps = 0; int fields = 0; for( const auto &om_near : om_region_ext ) { - if( om_near.first.find( "faction_base_camp" ) != std::string::npos ) { + const oter_id &om_type = oter_id( om_near.first ); + if( is_ot_subtype( "faction_base", om_type ) ) { popup( _( "You are too close to another camp!" ) ); return; } - if( om_near.first == "forest" || om_near.first == "forest_thick" ) { + if( is_ot_type( "forest_water", om_type ) ) { + swamps++; + } else if( is_ot_subtype( "forest", om_type ) ) { forests++; - } else if( om_near.first.find( "river_" ) != std::string::npos ) { + } else if( is_ot_subtype( "river", om_type ) ) { waters++; - } else if( om_near.first == "forest_water" ) { - swamps++; - } else if( om_near.first == "field" ) { + } else if( is_ot_subtype( "field", om_type ) ) { fields++; } } @@ -499,10 +535,12 @@ void talk_function::start_camp( npc &p ) if( display && !query_yn( _( "%s \nAre you sure you wish to continue? " ), buffer ) ) { return; } - if( !run_mapgen_update_func( "faction_base_field_camp_0", omt_pos ) ) { + const recipe &making = recipe_id( camp_type ).obj(); + if( !run_mapgen_update_func( making.get_blueprint(), omt_pos ) ) { + popup( _( "%s failed to start the %s basecamp." ), p.disp_name(), making.get_blueprint() ); return; } - get_basecamp( p ); + get_basecamp( p, camp_type ); } void talk_function::recover_camp( npc &p ) @@ -2115,12 +2153,15 @@ bool basecamp::upgrade_return( const std::string &dir, const std::string &miss, const tripoint upos = e->second.pos; const recipe &making = recipe_id( bldg ).obj(); - npc_ptr comp = companion_choose_return( miss, making.batch_duration() ); + time_duration work_days = base_camps::to_workdays( making.batch_duration() ); + npc_ptr comp = companion_choose_return( miss, work_days ); if( comp == nullptr ) { return false; } if( !run_mapgen_update_func( making.get_blueprint(), upos ) ) { + popup( _( "%s failed to build the %s upgrade." ), comp->disp_name(), + making.get_blueprint() ); return false; } update_provides( bldg, e->second ); @@ -2419,27 +2460,13 @@ bool basecamp::survey_return() if( comp == nullptr ) { return false; } - std::vector pos_expansion_name_id; - std::vector pos_expansion_name; - std::map pos_expansions = - recipe_group::get_recipes( "all_faction_base_expansions" ); - for( std::map::const_iterator it = pos_expansions.begin(); - it != pos_expansions.end(); ++it ) { - pos_expansion_name.push_back( it->first ); - pos_expansion_name_id.push_back( it->second ); - } - - const int expan = uilist( _( "Select an expansion:" ), pos_expansion_name ); - if( expan < 0 || static_cast( expan ) >= pos_expansion_name_id.size() ) { - popup( _( "You choose to wait..." ) ); - return false; - } popup( _( "Select a tile up to %d tiles away." ), 1 ); const tripoint where( ui::omap::choose_point() ); if( where == overmap::invalid_tripoint ) { return false; } + int dist = rl_dist( where.x, where.y, omt_pos.x, omt_pos.y ); if( dist != 1 ) { popup( _( "You must select a tile within %d range of the camp" ), 1 ); @@ -2449,18 +2476,29 @@ bool basecamp::survey_return() popup( _( "Expansions must be on the same level as the camp" ) ); return false; } + const std::string dir = talk_function::om_simple_dir( omt_pos, where ); + if( expansions.find( dir ) != expansions.end() ) { + popup( _( "You already have an expansion at that location" ) ); + return false; + } oter_id &omt_ref = overmap_buffer.ter( where ); - if( omt_ref.id() != "field" ) { - popup( _( "You must construct expansions in fields." ) ); + const auto &pos_expansions = recipe_group::get_recipes_by_id( "all_faction_base_expansions", + omt_ref.id().c_str() ); + if( pos_expansions.empty() ) { + popup( _( "You can't build any expansions in a %s." ), omt_ref.id().c_str() ); return false; } - if( !run_mapgen_update_func( pos_expansion_name_id[expan], where ) ) { + const std::string &expansion_type = base_camps::select_camp_option( pos_expansions, + _( "Select an expansion:" ) ); + + if( !run_mapgen_update_func( expansion_type, where ) ) { + popup( _( "%s failed to add the %s expansion" ), comp->disp_name(), expansion_type ); return false; } - omt_ref = oter_id( pos_expansion_name_id[expan] ); - add_expansion( pos_expansion_name_id[expan], where ); + omt_ref = oter_id( expansion_type ); + add_expansion( expansion_type, where, dir ); const std::string msg = _( "returns from surveying for the expansion." ); finish_return( *comp, true, msg, "construction", 2 ); return true; diff --git a/src/field.cpp b/src/field.cpp index ea0d2fe9393d7..346a2bb30e17e 100644 --- a/src/field.cpp +++ b/src/field.cpp @@ -1,77 +1,16 @@ #include "field.h" -#include #include #include -#include -#include -#include -#include -#include -#include #include -#include -#include "avatar.h" #include "calendar.h" -#include "cata_utility.h" -#include "coordinate_conversions.h" #include "debug.h" #include "emit.h" #include "enums.h" -#include "fire.h" -#include "fungal_effects.h" -#include "game.h" #include "itype.h" -#include "map.h" -#include "map_iterator.h" -#include "mapdata.h" -#include "material.h" -#include "messages.h" -#include "monster.h" -#include "mtype.h" -#include "npc.h" -#include "overmapbuffer.h" -#include "rng.h" -#include "scent_map.h" -#include "submap.h" #include "translations.h" -#include "vehicle.h" -#include "vpart_position.h" -#include "weather.h" -#include "bodypart.h" -#include "creature.h" -#include "damage.h" -#include "int_id.h" -#include "item.h" -#include "line.h" -#include "math_defines.h" #include "optional.h" -#include "player.h" -#include "pldata.h" -#include "string_id.h" -#include "units.h" -#include "type_id.h" - -const species_id FUNGUS( "FUNGUS" ); - -const efftype_id effect_badpoison( "badpoison" ); -const efftype_id effect_blind( "blind" ); -const efftype_id effect_corroding( "corroding" ); -const efftype_id effect_fungus( "fungus" ); -const efftype_id effect_onfire( "onfire" ); -const efftype_id effect_poison( "poison" ); -const efftype_id effect_relax_gas( "relax_gas" ); -const efftype_id effect_sap( "sap" ); -const efftype_id effect_smoke( "smoke" ); -const efftype_id effect_stung( "stung" ); -const efftype_id effect_stunned( "stunned" ); -const efftype_id effect_teargas( "teargas" ); -const efftype_id effect_webbed( "webbed" ); - -static const trait_id trait_ELECTRORECEPTORS( "ELECTRORECEPTORS" ); -static const trait_id trait_M_SKIN2( "M_SKIN2" ); -static const trait_id trait_M_SKIN3( "M_SKIN3" ); /** ID, {name}, symbol, priority, {color}, {transparency}, {dangerous}, half-life, {move_cost}, phase_id (of matter), accelerated_decay (decay outside of reality bubble) **/ const std::array fieldlist = { { @@ -538,2087 +477,6 @@ field_id field_from_ident( const std::string &field_ident ) return fd_null; } -void map::create_burnproducts( const tripoint &p, const item &fuel, const units::mass &burned_mass ) -{ - std::vector all_mats = fuel.made_of(); - if( all_mats.empty() ) { - return; - } - //Items that are multiple materials are assumed to be equal parts each. - units::mass by_weight = burned_mass / all_mats.size(); - for( auto &mat : all_mats ) { - for( auto &bp : mat->burn_products() ) { - itype_id id = bp.first; - // Spawning the same item as the one that was just burned is pointless - // and leads to infinite recursion. - if( fuel.typeId() == id ) { - continue; - } - float eff = bp.second; - int n = floor( eff * ( by_weight / item::find_type( id )->weight ) ); - - if( n <= 0 ) { - continue; - } - spawn_item( p, id, n, 1, calendar::turn ); - } - } -} - -bool map::process_fields() -{ - bool dirty_transparency_cache = false; - const int minz = zlevels ? -OVERMAP_DEPTH : abs_sub.z; - const int maxz = zlevels ? OVERMAP_HEIGHT : abs_sub.z; - for( int z = minz; z <= maxz; z++ ) { - bool zlev_dirty = false; - for( int x = 0; x < my_MAPSIZE; x++ ) { - for( int y = 0; y < my_MAPSIZE; y++ ) { - submap *const current_submap = get_submap_at_grid( { x, y, z } ); - if( current_submap->field_count > 0 ) { - const bool cur_dirty = process_fields_in_submap( current_submap, x, y, z ); - zlev_dirty |= cur_dirty; - } - } - } - - if( zlev_dirty ) { - // For now, just always dirty the transparency cache - // when a field might possibly be changed. - // TODO: check if there are any fields(mostly fire) - // that frequently change, if so set the dirty - // flag, otherwise only set the dirty flag if - // something actually changed - set_transparency_cache_dirty( z ); - dirty_transparency_cache = true; - } - } - - return dirty_transparency_cache; -} - -bool ter_furn_has_flag( const ter_t &ter, const furn_t &furn, const ter_bitflags flag ) -{ - return ter.has_flag( flag ) || furn.has_flag( flag ); -} - -static int ter_furn_movecost( const ter_t &ter, const furn_t &furn ) -{ - if( ter.movecost == 0 ) { - return 0; - } - - if( furn.movecost < 0 ) { - return 0; - } - - return ter.movecost + furn.movecost; -} - -static const std::array eight_horizontal_neighbors = { { - { -1, -1, 0 }, - { 0, -1, 0 }, - { +1, -1, 0 }, - { -1, 0, 0 }, - { +1, 0, 0 }, - { -1, +1, 0 }, - { 0, +1, 0 }, - { +1, +1, 0 }, - } -}; - -/* -Function: process_fields_in_submap -Iterates over every field on every tile of the given submap given as parameter. -This is the general update function for field effects. This should only be called once per game turn. -If you need to insert a new field behavior per unit time add a case statement in the switch below. -*/ -bool map::process_fields_in_submap( submap *const current_submap, - const int submap_x, const int submap_y, const int submap_z ) -{ - const auto get_neighbors = [this]( const tripoint & pt ) { - // Wrapper to allow skipping bound checks except at the edges of the map - const auto maptile_has_bounds = [this]( const tripoint & pt, const bool bounds_checked ) { - if( bounds_checked ) { - // We know that the point is in bounds - return maptile_at_internal( pt ); - } - - return maptile_at( pt ); - }; - - // Find out which edges are in the bubble - // Where possible, do just one bounds check for all the neighbors - const bool west = pt.x > 0; - const bool north = pt.y > 0; - const bool east = pt.x < SEEX * my_MAPSIZE - 1; - const bool south = pt.y < SEEY * my_MAPSIZE - 1; - return std::array< maptile, 8 > { { - maptile_has_bounds( pt + eight_horizontal_neighbors[0], west &&north ), - maptile_has_bounds( pt + eight_horizontal_neighbors[1], north ), - maptile_has_bounds( pt + eight_horizontal_neighbors[2], east &&north ), - maptile_has_bounds( pt + eight_horizontal_neighbors[3], west ), - maptile_has_bounds( pt + eight_horizontal_neighbors[4], east ), - maptile_has_bounds( pt + eight_horizontal_neighbors[5], west &&south ), - maptile_has_bounds( pt + eight_horizontal_neighbors[6], south ), - maptile_has_bounds( pt + eight_horizontal_neighbors[7], east &&south ), - } - }; - }; - - const auto spread_gas = [this, &get_neighbors]( - field_entry & cur, const tripoint & p, field_id curtype, - int percent_spread, const time_duration & outdoor_age_speedup ) { - const oter_id &cur_om_ter = overmap_buffer.ter( ms_to_omt_copy( g->m.getabs( p ) ) ); - bool sheltered = g->is_sheltered( p ); - int winddirection = g->weather.winddirection; - int windpower = get_local_windpower( g->weather.windspeed, cur_om_ter, p, winddirection, - sheltered ); - // Reset nearby scents to zero - for( const tripoint &tmp : points_in_radius( p, 1 ) ) { - g->scent.set( tmp, 0 ); - } - - const int current_density = cur.getFieldDensity(); - const time_duration current_age = cur.getFieldAge(); - // Dissipate faster outdoors. - if( is_outside( p ) ) { - cur.setFieldAge( current_age + outdoor_age_speedup ); - } - - // Bail out if we don't meet the spread chance or required density. - if( current_density <= 1 || rng( 1, ( 100 - windpower ) ) > percent_spread ) { - return; - } - - const auto can_spread_to = [&]( const maptile & dst, field_id curtype ) { - const field_entry *tmpfld = dst.get_field().findField( curtype ); - const auto &ter = dst.get_ter_t(); - const auto &frn = dst.get_furn_t(); - // Candidates are existing weaker fields or navigable/flagged tiles with no field. - return ( ter_furn_movecost( ter, frn ) > 0 || ter_furn_has_flag( ter, frn, TFLAG_PERMEABLE ) ) && - ( tmpfld == nullptr || tmpfld->getFieldDensity() < cur.getFieldDensity() ); - }; - - const auto spread_to = [&]( maptile & dst ) { - field_entry *candidate_field = dst.find_field( curtype ); - // Nearby gas grows thicker, and ages are shared. - const time_duration age_fraction = current_age / current_density ; - if( candidate_field != nullptr ) { - candidate_field->setFieldDensity( candidate_field->getFieldDensity() + 1 ); - cur.setFieldDensity( current_density - 1 ); - candidate_field->setFieldAge( candidate_field->getFieldAge() + age_fraction ); - cur.setFieldAge( current_age - age_fraction ); - // Or, just create a new field. - } else if( dst.add_field( curtype, 1, 0_turns ) ) { - dst.find_field( curtype )->setFieldAge( age_fraction ); - cur.setFieldDensity( current_density - 1 ); - cur.setFieldAge( current_age - age_fraction ); - } - }; - - // First check if we can fall - // TODO: Make fall and rise chances parameters to enable heavy/light gas - if( zlevels && p.z > -OVERMAP_DEPTH ) { - tripoint down{p.x, p.y, p.z - 1}; - maptile down_tile = maptile_at_internal( down ); - if( can_spread_to( down_tile, curtype ) && valid_move( p, down, true, true ) ) { - spread_to( down_tile ); - return; - } - } - - auto neighs = get_neighbors( p ); - size_t end_it = static_cast( rng( 0, neighs.size() - 1 ) ); - std::vector spread; - std::vector neighbour_vec; - // Then, spread to a nearby point. - // If not possible (or randomly), try to spread up - // Wind direction will block the field spreading into the wind. - spread.reserve( 8 ); - // Start at end_it + 1, then wrap around until all elements have been processed. - for( size_t i = ( end_it + 1 ) % neighs.size(), count = 0 ; - count != neighs.size(); - i = ( i + 1 ) % neighs.size(), count++ ) { - const auto &neigh = neighs[i]; - if( can_spread_to( neigh, curtype ) ) { - spread.push_back( i ); - } - } - auto maptiles = get_wind_blockers( winddirection, p ); - maptile remove_tile = std::get<0>( maptiles ); - maptile remove_tile2 = std::get<1>( maptiles ); - maptile remove_tile3 = std::get<2>( maptiles ); - // three map tiles that are facing th wind direction. - if( !zlevels || one_in( spread.size() ) ) { - // Construct the destination from offset and p - if( g->is_sheltered( p ) || windpower < 5 ) { - spread_to( neighs[ random_entry( spread ) ] ); - } else { - end_it = static_cast( rng( 0, neighs.size() - 1 ) ); - // Start at end_it + 1, then wrap around until all elements have been processed. - for( size_t i = ( end_it + 1 ) % neighs.size(), count = 0 ; - count != neighs.size(); - i = ( i + 1 ) % neighs.size(), count++ ) { - const auto &neigh = neighs[i]; - if( ( neigh.x != remove_tile.x && neigh.y != remove_tile.y ) || - ( neigh.x != remove_tile2.x && neigh.y != remove_tile2.y ) || - ( neigh.x != remove_tile3.x && neigh.y != remove_tile3.y ) ) { - neighbour_vec.push_back( neigh ); - } else if( x_in_y( 1, std::max( 2, windpower ) ) ) { - neighbour_vec.push_back( neigh ); - } - } - if( !neighbour_vec.empty() ) { - spread_to( neighbour_vec[rng( 0, neighbour_vec.size() - 1 )] ); - } - } - } else if( zlevels && p.z < OVERMAP_HEIGHT ) { - tripoint up{p.x, p.y, p.z + 1}; - maptile up_tile = maptile_at_internal( up ); - if( can_spread_to( up_tile, curtype ) && valid_move( p, up, true, true ) ) { - spread_to( up_tile ); - } - } - }; - /* - Function: create_hot_air - Helper function that encapsulates the logic involved in creating hot air. - */ - const auto create_hot_air = [this]( const tripoint & p, int density ) { - field_id hot_air; - switch( density ) { - case 1: - hot_air = fd_hot_air1; - break; - case 2: - hot_air = fd_hot_air2; - break; - case 3: - hot_air = fd_hot_air3; - break; - case 4: - hot_air = fd_hot_air4; - break; - default: - debugmsg( "Tried to spread hot air with density %d", density ); - return; - } - - for( int counter = 0; counter < 5; counter++ ) { - tripoint dst( p.x + rng( -1, 1 ), p.y + rng( -1, 1 ), p.z ); - add_field( dst, hot_air, 1 ); - } - }; - - // This should be true only when the field changes transparency - // More correctly: not just when the field is opaque, but when it changes state - // to a more/less transparent one, or creates a non-transparent field nearby - bool dirty_transparency_cache = false; - //Holds m.field_at(x,y).findField(fd_some_field) type returns. - // Just to avoid typing that long string for a temp value. - field_entry *tmpfld = nullptr; - - tripoint thep; - thep.z = submap_z; - - // Initialize the map tile wrapper - maptile map_tile( current_submap, 0, 0 ); - size_t &locx = map_tile.x; - size_t &locy = map_tile.y; - //Loop through all tiles in this submap indicated by current_submap - for( locx = 0; locx < SEEX; locx++ ) { - for( locy = 0; locy < SEEY; locy++ ) { - // This is a translation from local coordinates to submap coordinates. - // All submaps are in one long 1d array. - thep.x = locx + submap_x * SEEX; - thep.y = locy + submap_y * SEEY; - // A const reference to the tripoint above, so that the code below doesn't accidentally change it - const tripoint &p = thep; - // Get a reference to the field variable from the submap; - // contains all the pointers to the real field effects. - field &curfield = current_submap->fld[locx][locy]; - for( auto it = curfield.begin(); it != curfield.end(); ) { - //Iterating through all field effects in the submap's field. - field_entry &cur = it->second; - // The field might have been killed by processing a neighbor field - if( !cur.isAlive() ) { - if( !fieldlist[cur.getFieldType()].transparent[cur.getFieldDensity() - 1] ) { - dirty_transparency_cache = true; - } - current_submap->field_count--; - curfield.removeField( it++ ); - continue; - } - - //Holds cur.getFieldType() as that is what the old system used before rewrite. - field_id curtype = cur.getFieldType(); - // Again, legacy support in the event someone Mods setFieldDensity to allow more values. - if( cur.getFieldDensity() > 3 || cur.getFieldDensity() < 1 ) { - debugmsg( "Whoooooa density of %d", cur.getFieldDensity() ); - } - - // Don't process "newborn" fields. This gives the player time to run if they need to. - if( cur.getFieldAge() == 0_turns ) { - curtype = fd_null; - } - - int part; - switch( curtype ) { - case fd_null: - case num_fields: - break; // Do nothing, obviously. OBVIOUSLY. - - case fd_blood: - case fd_blood_veggy: - case fd_blood_insect: - case fd_blood_invertebrate: - case fd_bile: - case fd_gibs_flesh: - case fd_gibs_veggy: - case fd_gibs_insect: - case fd_gibs_invertebrate: - // Dissipate faster in water - if( map_tile.get_ter_t().has_flag( TFLAG_SWIMMABLE ) ) { - cur.setFieldAge( cur.getFieldAge() + 25_minutes ); - } - break; - - case fd_acid: { - const auto &ter = map_tile.get_ter_t(); - if( ter.has_flag( TFLAG_SWIMMABLE ) ) { // Dissipate faster in water - cur.setFieldAge( cur.getFieldAge() + 2_minutes ); - } - - // Try to fall by a z-level - if( !zlevels || p.z <= -OVERMAP_DEPTH ) { - break; - } - - tripoint dst{p.x, p.y, p.z - 1}; - if( valid_move( p, dst, true, true ) ) { - maptile dst_tile = maptile_at_internal( dst ); - field_entry *acid_there = dst_tile.find_field( fd_acid ); - if( acid_there == nullptr ) { - dst_tile.add_field( fd_acid, cur.getFieldDensity(), cur.getFieldAge() ); - } else { - // Math can be a bit off, - // but "boiling" falling acid can be allowed to be stronger - // than acid that just lies there - const int sum_density = cur.getFieldDensity() + acid_there->getFieldDensity(); - const int new_density = std::min( 3, sum_density ); - // No way to get precise elapsed time, let's always reset - // Allow falling acid to last longer than regular acid to show it off - const time_duration new_age = -1_minutes * ( sum_density - new_density ); - acid_there->setFieldDensity( new_density ); - acid_there->setFieldAge( new_age ); - } - - // Set ourselves up for removal - cur.setFieldDensity( 0 ); - } - - // TODO: Allow spreading to the sides if age < 0 && density == 3 - } - break; - - // Use the normal aging logic below this switch - case fd_web: - break; - case fd_sap: - break; - case fd_sludge: - break; - case fd_slime: - if( g->scent.get( p ) < cur.getFieldDensity() * 10 ) { - g->scent.set( p, cur.getFieldDensity() * 10 ); - } - break; - case fd_plasma: - case fd_laser: - dirty_transparency_cache = true; - break; - - // TODO: MATERIALS use fire resistance - case fd_fire: { - // Entire objects for ter/frn for flags - const oter_id &cur_om_ter = overmap_buffer.ter( ms_to_omt_copy( g->m.getabs( p ) ) ); - bool sheltered = g->is_sheltered( p ); - int winddirection = g->weather.winddirection; - int windpower = get_local_windpower( g->weather.windspeed, cur_om_ter, p, winddirection, - sheltered ); - const auto &ter = map_tile.get_ter_t(); - const auto &frn = map_tile.get_furn_t(); - - // We've got ter/furn cached, so let's use that - const bool is_sealed = ter_furn_has_flag( ter, frn, TFLAG_SEALED ) && - !ter_furn_has_flag( ter, frn, TFLAG_ALLOW_FIELD_EFFECT ); - // Smoke generation probability, consumed items count - int smoke = 0; - int consumed = 0; - // How much time to add to the fire's life due to burned items/terrain/furniture - time_duration time_added = 0_turns; - // Checks if the fire can spread - // If the flames are in furniture with fire_container flag like brazier or oven, - // they're fully contained, so skip consuming terrain - const bool can_spread = !ter_furn_has_flag( ter, frn, TFLAG_FIRE_CONTAINER ); - // The huge indent below should probably be somehow moved away from here - // without forcing the function to use i_at( p ) for fires without items - if( !is_sealed && map_tile.get_item_count() > 0 ) { - auto items_here = i_at( p ); - std::vector new_content; - for( auto explosive = items_here.begin(); explosive != items_here.end(); ) { - if( explosive->will_explode_in_fire() ) { - // We need to make a copy because the iterator validity is not predictable - item copy = *explosive; - explosive = items_here.erase( explosive ); - if( copy.detonate( p, new_content ) ) { - // Need to restart, iterators may not be valid - explosive = items_here.begin(); - } - } else { - ++explosive; - } - } - - fire_data frd( cur.getFieldDensity(), !can_spread ); - // The highest # of items this fire can remove in one turn - int max_consume = cur.getFieldDensity() * 2; - - for( auto fuel = items_here.begin(); fuel != items_here.end() && consumed < max_consume; ) { - // `item::burn` modifies the charges in order to simulate some of them getting - // destroyed by the fire, this changes the item weight, but may not actually - // destroy it. We need to spawn products anyway. - const units::mass old_weight = fuel->weight( false ); - bool destroyed = fuel->burn( frd ); - // If the item is considered destroyed, it may have negative charge count, - // see `item::burn?. This in turn means `item::weight` returns a negative value, - // which we can not use, so only call `weight` when it's still an existing item. - const units::mass new_weight = destroyed ? 0_gram : fuel->weight( false ); - if( old_weight != new_weight ) { - create_burnproducts( p, *fuel, old_weight - new_weight ); - } - - if( destroyed ) { - // If we decided the item was destroyed by fire, remove it. - // But remember its contents, except for irremovable mods, if any - std::copy( fuel->contents.begin(), fuel->contents.end(), - std::back_inserter( new_content ) ); - new_content.erase( std::remove_if( new_content.begin(), new_content.end(), [&]( const item & i ) { - return i.is_irremovable(); - } ), new_content.end() ); - fuel = items_here.erase( fuel ); - consumed++; - } else { - ++fuel; - } - } - - spawn_items( p, new_content ); - smoke = roll_remainder( frd.smoke_produced ); - time_added = 1_turns * roll_remainder( frd.fuel_produced ); - } - - //Get the part of the vehicle in the fire. - vehicle *veh = veh_at_internal( p, part ); // _internal skips the boundary check - if( veh != nullptr ) { - veh->damage( part, cur.getFieldDensity() * 10, DT_HEAT, true ); - //Damage the vehicle in the fire. - } - if( can_spread ) { - if( ter.has_flag( TFLAG_SWIMMABLE ) ) { - // Flames die quickly on water - cur.setFieldAge( cur.getFieldAge() + 4_minutes ); - } - - // Consume the terrain we're on - if( ter_furn_has_flag( ter, frn, TFLAG_FLAMMABLE ) ) { - // The fire feeds on the ground itself until max density. - time_added += 1_turns * ( 5 - cur.getFieldDensity() ); - smoke += 2; - smoke += static_cast( windpower / 5 ); - if( cur.getFieldDensity() > 1 && - one_in( 200 - cur.getFieldDensity() * 50 ) ) { - destroy( p, false ); - } - - } else if( ter_furn_has_flag( ter, frn, TFLAG_FLAMMABLE_HARD ) && - one_in( 3 ) ) { - // The fire feeds on the ground itself until max density. - time_added += 1_turns * ( 4 - cur.getFieldDensity() ); - smoke += 2; - smoke += static_cast( windpower / 5 ); - if( cur.getFieldDensity() > 1 && - one_in( 200 - cur.getFieldDensity() * 50 ) ) { - destroy( p, false ); - } - - } else if( ter.has_flag( TFLAG_FLAMMABLE_ASH ) ) { - // The fire feeds on the ground itself until max density. - time_added += 1_turns * ( 5 - cur.getFieldDensity() ); - smoke += 2; - smoke += static_cast( windpower / 5 ); - if( cur.getFieldDensity() > 1 && - one_in( 200 - cur.getFieldDensity() * 50 ) ) { - ter_set( p, t_dirt ); - } - - } else if( frn.has_flag( TFLAG_FLAMMABLE_ASH ) ) { - // The fire feeds on the ground itself until max density. - time_added += 1_turns * ( 5 - cur.getFieldDensity() ); - smoke += 2; - smoke += static_cast( windpower / 5 ); - if( cur.getFieldDensity() > 1 && - one_in( 200 - cur.getFieldDensity() * 50 ) ) { - furn_set( p, f_ash ); - add_item_or_charges( p, item( "ash" ) ); - } - - } else if( ter.has_flag( TFLAG_NO_FLOOR ) && zlevels && p.z > -OVERMAP_DEPTH ) { - // We're hanging in the air - let's fall down - tripoint dst{p.x, p.y, p.z - 1}; - if( valid_move( p, dst, true, true ) ) { - maptile dst_tile = maptile_at_internal( dst ); - field_entry *fire_there = dst_tile.find_field( fd_fire ); - if( fire_there == nullptr ) { - dst_tile.add_field( fd_fire, 1, 0_turns ); - cur.setFieldDensity( cur.getFieldDensity() - 1 ); - } else { - // Don't fuel raging fires or they'll burn forever - // as they can produce small fires above themselves - int new_density = std::max( cur.getFieldDensity(), - fire_there->getFieldDensity() ); - // Allow smaller fires to combine - if( new_density < 3 && - cur.getFieldDensity() == fire_there->getFieldDensity() ) { - new_density++; - } - fire_there->setFieldDensity( new_density ); - // A raging fire below us can support us for a while - // Otherwise decay and decay fast - if( new_density < 3 || one_in( 10 ) ) { - cur.setFieldDensity( cur.getFieldDensity() - 1 ); - } - } - - break; - } - } - } - // Lower age is a longer lasting fire - if( time_added != 0_turns ) { - cur.setFieldAge( cur.getFieldAge() - time_added ); - } else if( can_spread || !ter_furn_has_flag( ter, frn, TFLAG_FIRE_CONTAINER ) ) { - // Nothing to burn = fire should be dying out faster - // Drain more power from big fires, so that they stop raging over nothing - // Except for fires on stoves and fireplaces, those are made to keep the fire alive - cur.setFieldAge( cur.getFieldAge() + 10_seconds * cur.getFieldDensity() ); - } - - // Below we will access our nearest 8 neighbors, so let's cache them now - // This should probably be done more globally, because large fires will re-do it a lot - auto neighs = get_neighbors( p ); - // get the neighbours that are allowed due to wind direction - auto maptiles = get_wind_blockers( winddirection, p ); - maptile remove_tile = std::get<0>( maptiles ); - maptile remove_tile2 = std::get<1>( maptiles ); - maptile remove_tile3 = std::get<2>( maptiles ); - std::vector neighbour_vec; - size_t end_it = static_cast( rng( 0, neighs.size() - 1 ) ); - // Start at end_it + 1, then wrap around until all elements have been processed - for( size_t i = ( end_it + 1 ) % neighs.size(), count = 0; - count != neighs.size(); - i = ( i + 1 ) % neighs.size(), count++ ) { - const auto &neigh = neighs[i]; - if( ( neigh.x != remove_tile.x && neigh.y != remove_tile.y ) || - ( neigh.x != remove_tile2.x && neigh.y != remove_tile2.y ) || - ( neigh.x != remove_tile3.x && neigh.y != remove_tile3.y ) ) { - neighbour_vec.push_back( neigh ); - } else if( x_in_y( 1, std::max( 2, windpower ) ) ) { - neighbour_vec.push_back( neigh ); - } - } - // If the flames are in a pit, it can't spread to non-pit - const bool in_pit = ter.id.id() == t_pit; - - // Count adjacent fires, to optimize out needless smoke and hot air - int adjacent_fires = 0; - - // If the flames are big, they contribute to adjacent flames - if( can_spread ) { - if( cur.getFieldDensity() > 1 && one_in( 3 ) ) { - // Basically: Scan around for a spot, - // if there is more fire there, make it bigger and give it some fuel. - // This is how big fires spend their excess age: - // making other fires bigger. Flashpoint. - if( sheltered || windpower < 5 ) { - end_it = static_cast( rng( 0, neighs.size() - 1 ) ); - for( size_t i = ( end_it + 1 ) % neighs.size(), count = 0; - count != neighs.size() && cur.getFieldAge() < 0_turns; - i = ( i + 1 ) % neighs.size(), count++ ) { - maptile &dst = neighs[i]; - auto dstfld = dst.find_field( fd_fire ); - // If the fire exists and is weaker than ours, boost it - if( dstfld != nullptr && - ( dstfld->getFieldDensity() <= cur.getFieldDensity() || - dstfld->getFieldAge() > cur.getFieldAge() ) && - ( in_pit == ( dst.get_ter() == t_pit ) ) ) { - if( dstfld->getFieldDensity() < 2 ) { - dstfld->setFieldDensity( dstfld->getFieldDensity() + 1 ); - } - - dstfld->setFieldAge( dstfld->getFieldAge() - 5_minutes ); - cur.setFieldAge( cur.getFieldAge() + 5_minutes ); - } - - if( dstfld != nullptr ) { - adjacent_fires++; - } - } - } else { - end_it = static_cast( rng( 0, neighbour_vec.size() - 1 ) ); - for( size_t i = ( end_it + 1 ) % neighbour_vec.size(), count = 0; - count != neighbour_vec.size() && cur.getFieldAge() < 0_turns; - i = ( i + 1 ) % neighbour_vec.size(), count++ ) { - maptile &dst = neighbour_vec[i]; - auto dstfld = dst.find_field( fd_fire ); - // If the fire exists and is weaker than ours, boost it - if( dstfld != nullptr && - ( dstfld->getFieldDensity() <= cur.getFieldDensity() || - dstfld->getFieldAge() > cur.getFieldAge() ) && - ( in_pit == ( dst.get_ter() == t_pit ) ) ) { - if( dstfld->getFieldDensity() < 2 ) { - dstfld->setFieldDensity( dstfld->getFieldDensity() + 1 ); - } - - dstfld->setFieldAge( dstfld->getFieldAge() - 5_minutes ); - cur.setFieldAge( cur.getFieldAge() + 5_minutes ); - } - - if( dstfld != nullptr ) { - adjacent_fires++; - } - } - } - } else if( cur.getFieldAge() < 0_turns && cur.getFieldDensity() < 3 ) { - // See if we can grow into a stage 2/3 fire, for this - // burning neighbors are necessary in addition to - // field age < 0, or alternatively, a LOT of fuel. - - // The maximum fire density is 1 for a lone fire, 2 for at least 1 neighbor, - // 3 for at least 2 neighbors. - int maximum_density = 1; - - // The following logic looks a bit complex due to optimization concerns, so here are the semantics: - // 1. Calculate maximum field density based on fuel, -50 minutes is 2(medium), -500 minutes is 3(raging) - // 2. Calculate maximum field density based on neighbors, 3 neighbors is 2(medium), 7 or more neighbors is 3(raging) - // 3. Pick the higher maximum between 1. and 2. - if( cur.getFieldAge() < -500_minutes ) { - maximum_density = 3; - } else { - for( auto &neigh : neighs ) { - if( neigh.get_field().findField( fd_fire ) != nullptr ) { - adjacent_fires++; - } - } - maximum_density = 1 + ( adjacent_fires >= 3 ) + ( adjacent_fires >= 7 ); - - if( maximum_density < 2 && cur.getFieldAge() < -50_minutes ) { - maximum_density = 2; - } - } - - // If we consumed a lot, the flames grow higher - if( cur.getFieldDensity() < maximum_density && cur.getFieldAge() < 0_turns ) { - // Fires under 0 age grow in size. Level 3 fires under 0 spread later on. - // Weaken the newly-grown fire - cur.setFieldDensity( cur.getFieldDensity() + 1 ); - cur.setFieldAge( cur.getFieldAge() + 10_minutes * cur.getFieldDensity() ); - } - } - } - // Consume adjacent fuel / terrain / webs to spread. - // Allow raging fires (and only raging fires) to spread up - // Spreading down is achieved by wrecking the walls/floor and then falling - if( zlevels && cur.getFieldDensity() == 3 && p.z < OVERMAP_HEIGHT ) { - // Let it burn through the floor - maptile dst = maptile_at_internal( {p.x, p.y, p.z + 1} ); - const auto &dst_ter = dst.get_ter_t(); - if( dst_ter.has_flag( TFLAG_NO_FLOOR ) || - dst_ter.has_flag( TFLAG_FLAMMABLE ) || - dst_ter.has_flag( TFLAG_FLAMMABLE_ASH ) || - dst_ter.has_flag( TFLAG_FLAMMABLE_HARD ) ) { - field_entry *nearfire = dst.find_field( fd_fire ); - if( nearfire != nullptr ) { - nearfire->setFieldAge( nearfire->getFieldAge() - 2_minutes ); - } else { - dst.add_field( fd_fire, 1, 0_turns ); - } - // Fueling fires above doesn't cost fuel - } - } - // Our iterator will start at end_i + 1 and increment from there and then wrap around. - // This guarantees it will check all neighbors, starting from a random one - if( sheltered || windpower < 5 ) { - const size_t end_i = static_cast( rng( 0, neighs.size() - 1 ) ); - for( size_t i = ( end_i + 1 ) % neighs.size(), count = 0; - count != neighs.size(); - i = ( i + 1 ) % neighs.size(), count++ ) { - if( one_in( cur.getFieldDensity() * 2 ) ) { - // Skip some processing to save on CPU - continue; - } - - maptile &dst = neighs[i]; - // No bounds checking here: we'll treat the invalid neighbors as valid. - // We're using the map tile wrapper, so we can treat invalid tiles as sentinels. - // This will create small oddities on map edges, but nothing more noticeable than - // "cut-off" that happens with bounds checks. - - field_entry *nearfire = dst.find_field( fd_fire ); - if( nearfire != nullptr ) { - // We handled supporting fires in the section above, no need to do it here - continue; - } - - field_entry *nearwebfld = dst.find_field( fd_web ); - int spread_chance = 25 * ( cur.getFieldDensity() - 1 ); - if( nearwebfld != nullptr ) { - spread_chance = 50 + spread_chance / 2; - } - - const auto &dster = dst.get_ter_t(); - const auto &dsfrn = dst.get_furn_t(); - // Allow weaker fires to spread occasionally - const int power = cur.getFieldDensity() + one_in( 5 ); - if( can_spread && rng( 1, 100 ) < spread_chance && - ( in_pit == ( dster.id.id() == t_pit ) ) && - ( - ( power >= 3 && cur.getFieldAge() < 0_turns && one_in( 20 ) ) || - ( power >= 2 && ( ter_furn_has_flag( dster, dsfrn, TFLAG_FLAMMABLE ) && one_in( 2 ) ) ) || - ( power >= 2 && ( ter_furn_has_flag( dster, dsfrn, TFLAG_FLAMMABLE_ASH ) && one_in( 2 ) ) ) || - ( power >= 3 && ( ter_furn_has_flag( dster, dsfrn, TFLAG_FLAMMABLE_HARD ) && one_in( 5 ) ) ) || - nearwebfld || ( dst.get_item_count() > 0 && - flammable_items_at( p + eight_horizontal_neighbors[i] ) && - one_in( 5 ) ) - ) ) { - dst.add_field( fd_fire, 1, 0_turns ); // Nearby open flammable ground? Set it on fire. - tmpfld = dst.find_field( fd_fire ); - if( tmpfld != nullptr ) { - // Make the new fire quite weak, so that it doesn't start jumping around instantly - tmpfld->setFieldAge( 2_minutes ); - // Consume a bit of our fuel - cur.setFieldAge( cur.getFieldAge() + 1_minutes ); - } - if( nearwebfld ) { - nearwebfld->setFieldDensity( 0 ); - } - } - } - } else { - const size_t end_i = static_cast( rng( 0, neighbour_vec.size() - 1 ) ); - for( size_t i = ( end_i + 1 ) % neighbour_vec.size(), count = 0; - count != neighbour_vec.size(); - i = ( i + 1 ) % neighbour_vec.size(), count++ ) { - if( one_in( cur.getFieldDensity() * 2 ) ) { - // Skip some processing to save on CPU - continue; - } - - if( neighbour_vec.empty() ) { - continue; - } - - maptile &dst = neighbour_vec[i]; - // No bounds checking here: we'll treat the invalid neighbors as valid. - // We're using the map tile wrapper, so we can treat invalid tiles as sentinels. - // This will create small oddities on map edges, but nothing more noticeable than - // "cut-off" that happens with bounds checks. - - field_entry *nearfire = dst.find_field( fd_fire ); - if( nearfire != nullptr ) { - // We handled supporting fires in the section above, no need to do it here - continue; - } - - field_entry *nearwebfld = dst.find_field( fd_web ); - int spread_chance = 25 * ( cur.getFieldDensity() - 1 ); - if( nearwebfld != nullptr ) { - spread_chance = 50 + spread_chance / 2; - } - - const auto &dster = dst.get_ter_t(); - const auto &dsfrn = dst.get_furn_t(); - // Allow weaker fires to spread occasionally - const int power = cur.getFieldDensity() + one_in( 5 ); - if( can_spread && rng( 1, ( 100 - windpower ) ) < spread_chance && - ( in_pit == ( dster.id.id() == t_pit ) ) && - ( - ( power >= 3 && cur.getFieldAge() < 0_turns && one_in( 20 ) ) || - ( power >= 2 && ( ter_furn_has_flag( dster, dsfrn, TFLAG_FLAMMABLE ) && one_in( 2 ) ) ) || - ( power >= 2 && ( ter_furn_has_flag( dster, dsfrn, TFLAG_FLAMMABLE_ASH ) && one_in( 2 ) ) ) || - ( power >= 3 && ( ter_furn_has_flag( dster, dsfrn, TFLAG_FLAMMABLE_HARD ) && one_in( 5 ) ) ) || - nearwebfld || ( dst.get_item_count() > 0 && - flammable_items_at( p + eight_horizontal_neighbors[i] ) && - one_in( 5 ) ) - ) ) { - dst.add_field( fd_fire, 1, 0_turns ); // Nearby open flammable ground? Set it on fire. - tmpfld = dst.find_field( fd_fire ); - if( tmpfld != nullptr ) { - // Make the new fire quite weak, so that it doesn't start jumping around instantly - tmpfld->setFieldAge( 2_minutes ); - // Consume a bit of our fuel - cur.setFieldAge( cur.getFieldAge() + 1_minutes ); - } - if( nearwebfld ) { - nearwebfld->setFieldDensity( 0 ); - } - } - } - } - // Create smoke once - above us if possible, at us otherwise - if( !ter_furn_has_flag( ter, frn, TFLAG_SUPPRESS_SMOKE ) && - rng( 0, ( 100 - windpower ) ) <= smoke && - rng( 3, 35 ) < cur.getFieldDensity() * 10 ) { - bool smoke_up = zlevels && p.z < OVERMAP_HEIGHT; - if( smoke_up ) { - tripoint up{p.x, p.y, p.z + 1}; - maptile dst = maptile_at_internal( up ); - const auto &dst_ter = dst.get_ter_t(); - if( dst_ter.has_flag( TFLAG_NO_FLOOR ) ) { - dst.add_field( fd_smoke, rng( 1, cur.getFieldDensity() ), 0_turns ); - } else { - // Can't create smoke above - smoke_up = false; - } - } - - if( !smoke_up ) { - maptile dst = maptile_at_internal( p ); - // Create thicker smoke - dst.add_field( fd_smoke, cur.getFieldDensity(), 0_turns ); - } - - dirty_transparency_cache = true; // Smoke affects transparency - } - - // Hot air is a load on the CPU - // Don't produce too much of it if we have a lot fires nearby, they produce - // radiant heat which does what hot air would do anyway - if( adjacent_fires < 5 && rng( 0, 4 - adjacent_fires ) ) { - create_hot_air( p, cur.getFieldDensity() ); - } - } - break; - - case fd_smoke: - case fd_tear_gas: - dirty_transparency_cache = true; - spread_gas( cur, p, curtype, 10, 0_turns ); - break; - - case fd_relax_gas: - dirty_transparency_cache = true; - spread_gas( cur, p, curtype, 15, 5_minutes ); - break; - - case fd_fungal_haze: - dirty_transparency_cache = true; - spread_gas( cur, p, curtype, 13, 5_turns ); - if( one_in( 10 - 2 * cur.getFieldDensity() ) ) { - // Haze'd terrain - fungal_effects( *g, g->m ).spread_fungus( p ); - } - - break; - - case fd_toxic_gas: - dirty_transparency_cache = true; - spread_gas( cur, p, curtype, 30, 3_minutes ); - break; - - case fd_cigsmoke: - dirty_transparency_cache = true; - spread_gas( cur, p, curtype, 250, 6_minutes ); - break; - - case fd_weedsmoke: { - dirty_transparency_cache = true; - spread_gas( cur, p, curtype, 200, 6_minutes ); - - if( one_in( 20 ) ) { - if( npc *const np = g->critter_at( p ) ) { - np->complain_about( "weed_smell", 10_minutes, "" ); - } - } - - } - break; - - case fd_methsmoke: { - dirty_transparency_cache = true; - spread_gas( cur, p, curtype, 175, 7_minutes ); - if( one_in( 20 ) ) { - if( npc *const np = g->critter_at( p ) ) { - np->complain_about( "meth_smell", 30_minutes, "" ); - } - } - } - break; - - case fd_cracksmoke: { - dirty_transparency_cache = true; - spread_gas( cur, p, curtype, 175, 8_minutes ); - - if( one_in( 20 ) ) { - if( npc *const np = g->critter_at( p ) ) { - np->complain_about( "crack_smell", 30_minutes, "" ); - } - } - } - break; - - case fd_nuke_gas: { - dirty_transparency_cache = true; - int extra_radiation = rng( 0, cur.getFieldDensity() ); - adjust_radiation( p, extra_radiation ); - spread_gas( cur, p, curtype, 15, 1_minutes ); - break; - } - case fd_cold_air1: - case fd_cold_air2: - case fd_cold_air3: - case fd_cold_air4: - case fd_hot_air1: - case fd_hot_air2: - case fd_hot_air3: - case fd_hot_air4: - // No transparency cache wrecking here! - spread_gas( cur, p, curtype, 100, 100_minutes ); - break; - - case fd_gas_vent: { - dirty_transparency_cache = true; - for( const tripoint &pnt : points_in_radius( p, cur.getFieldDensity() - 1 ) ) { - field &wandering_field = get_field( pnt ); - tmpfld = wandering_field.findField( fd_toxic_gas ); - if( tmpfld && tmpfld->getFieldDensity() < cur.getFieldDensity() ) { - tmpfld->setFieldDensity( tmpfld->getFieldDensity() + 1 ); - } else { - add_field( pnt, fd_toxic_gas, cur.getFieldDensity() ); - } - } - } - break; - - case fd_smoke_vent: { - dirty_transparency_cache = true; - for( const tripoint &pnt : points_in_radius( p, cur.getFieldDensity() - 1 ) ) { - field &wandering_field = get_field( pnt ); - tmpfld = wandering_field.findField( fd_smoke ); - if( tmpfld && tmpfld->getFieldDensity() < cur.getFieldDensity() ) { - tmpfld->setFieldDensity( tmpfld->getFieldDensity() + 1 ); - } else { - add_field( pnt, fd_smoke, cur.getFieldDensity() ); - } - } - } - break; - - case fd_fire_vent: - if( cur.getFieldDensity() > 1 ) { - if( one_in( 3 ) ) { - cur.setFieldDensity( cur.getFieldDensity() - 1 ); - } - create_hot_air( p, cur.getFieldDensity() ); - } else { - dirty_transparency_cache = true; - add_field( p, fd_flame_burst, 3, cur.getFieldAge() ); - cur.setFieldDensity( 0 ); - } - break; - - case fd_flame_burst: - if( cur.getFieldDensity() > 1 ) { - cur.setFieldDensity( cur.getFieldDensity() - 1 ); - create_hot_air( p, cur.getFieldDensity() ); - } else { - dirty_transparency_cache = true; - add_field( p, fd_fire_vent, 3, cur.getFieldAge() ); - cur.setFieldDensity( 0 ); - } - break; - - case fd_electricity: - if( !one_in( 5 ) ) { // 4 in 5 chance to spread - std::vector valid; - if( impassable( p ) && cur.getFieldDensity() > 1 ) { // We're grounded - int tries = 0; - tripoint pnt; - pnt.z = p.z; - while( tries < 10 && cur.getFieldAge() < 5_minutes && cur.getFieldDensity() > 1 ) { - pnt.x = p.x + rng( -1, 1 ); - pnt.y = p.y + rng( -1, 1 ); - if( passable( pnt ) ) { - add_field( pnt, fd_electricity, 1, cur.getFieldAge() + 1_turns ); - cur.setFieldDensity( cur.getFieldDensity() - 1 ); - tries = 0; - } else { - tries++; - } - } - } else { // We're not grounded; attempt to ground - for( const tripoint &dst : points_in_radius( p, 1 ) ) { - if( impassable( dst ) ) { // Grounded tiles first - valid.push_back( dst ); - } - } - if( valid.empty() ) { // Spread to adjacent space, then - tripoint dst( p.x + rng( -1, 1 ), p.y + rng( -1, 1 ), p.z ); - field_entry *elec = get_field( dst ).findField( fd_electricity ); - if( passable( dst ) && elec != nullptr && - elec->getFieldDensity() < 3 ) { - elec->setFieldDensity( elec->getFieldDensity() + 1 ); - cur.setFieldDensity( cur.getFieldDensity() - 1 ); - } else if( passable( dst ) ) { - add_field( dst, fd_electricity, 1, cur.getFieldAge() + 1_turns ); - } - cur.setFieldDensity( cur.getFieldDensity() - 1 ); - } - while( !valid.empty() && cur.getFieldDensity() > 1 ) { - const tripoint target = random_entry_removed( valid ); - add_field( target, fd_electricity, 1, cur.getFieldAge() + 1_turns ); - cur.setFieldDensity( cur.getFieldDensity() - 1 ); - } - } - } - break; - - case fd_fatigue: { - static const std::array monids = { { - mtype_id( "mon_flying_polyp" ), mtype_id( "mon_hunting_horror" ), - mtype_id( "mon_mi_go" ), mtype_id( "mon_yugg" ), mtype_id( "mon_gelatin" ), - mtype_id( "mon_flaming_eye" ), mtype_id( "mon_kreck" ), mtype_id( "mon_gracke" ), - mtype_id( "mon_blank" ), - } - }; - if( cur.getFieldDensity() < 3 && calendar::once_every( 6_hours ) && one_in( 10 ) ) { - cur.setFieldDensity( cur.getFieldDensity() + 1 ); - } else if( cur.getFieldDensity() == 3 && one_in( 600 ) ) { // Spawn nether creature! - g->summon_mon( random_entry( monids ), p ); - } - } - break; - - case fd_push_items: { - auto items = i_at( p ); - for( auto pushee = items.begin(); pushee != items.end(); ) { - if( pushee->typeId() != "rock" || - pushee->age() < 1_turns ) { - pushee++; - } else { - item tmp = *pushee; - tmp.set_age( 0_turns ); - pushee = items.erase( pushee ); - std::vector valid; - for( const tripoint &dst : points_in_radius( p, 1 ) ) { - if( get_field( dst, fd_push_items ) != nullptr ) { - valid.push_back( dst ); - } - } - if( !valid.empty() ) { - tripoint newp = random_entry( valid ); - add_item_or_charges( newp, tmp ); - if( g->u.pos() == newp ) { - add_msg( m_bad, _( "A %s hits you!" ), tmp.tname() ); - body_part hit = random_body_part(); - g->u.deal_damage( nullptr, hit, damage_instance( DT_BASH, 6 ) ); - g->u.check_dead_state(); - } - - if( npc *const p = g->critter_at( newp ) ) { - // TODO: combine with player character code above - body_part hit = random_body_part(); - p->deal_damage( nullptr, hit, damage_instance( DT_BASH, 6 ) ); - if( g->u.sees( newp ) ) { - add_msg( _( "A %1$s hits %2$s!" ), tmp.tname(), p->name ); - } - p->check_dead_state(); - } else if( monster *const mon = g->critter_at( newp ) ) { - mon->apply_damage( nullptr, bp_torso, 6 - mon->get_armor_bash( bp_torso ) ); - if( g->u.sees( newp ) ) { - add_msg( _( "A %1$s hits the %2$s!" ), tmp.tname(), mon->name() ); - } - mon->check_dead_state(); - } - } - } - } - } - break; - - case fd_shock_vent: - if( cur.getFieldDensity() > 1 ) { - if( one_in( 5 ) ) { - cur.setFieldDensity( cur.getFieldDensity() - 1 ); - } - } else { - cur.setFieldDensity( 3 ); - int num_bolts = rng( 3, 6 ); - for( int i = 0; i < num_bolts; i++ ) { - int xdir = 0; - int ydir = 0; - while( xdir == 0 && ydir == 0 ) { - xdir = rng( -1, 1 ); - ydir = rng( -1, 1 ); - } - int dist = rng( 4, 12 ); - int boltx = p.x; - int bolty = p.y; - for( int n = 0; n < dist; n++ ) { - boltx += xdir; - bolty += ydir; - add_field( tripoint( boltx, bolty, p.z ), fd_electricity, rng( 2, 3 ) ); - if( one_in( 4 ) ) { - if( xdir == 0 ) { - xdir = rng( 0, 1 ) * 2 - 1; - } else { - xdir = 0; - } - } - if( one_in( 4 ) ) { - if( ydir == 0 ) { - ydir = rng( 0, 1 ) * 2 - 1; - } else { - ydir = 0; - } - } - } - } - } - break; - - case fd_acid_vent: - if( cur.getFieldDensity() > 1 ) { - if( cur.getFieldAge() >= 1_minutes ) { - cur.setFieldDensity( cur.getFieldDensity() - 1 ); - cur.setFieldAge( 0_turns ); - } - } else { - cur.setFieldDensity( 3 ); - for( const tripoint &t : points_in_radius( p, 5 ) ) { - const field_entry *acid = get_field( t, fd_acid ); - if( acid != nullptr && acid->getFieldDensity() == 0 ) { - int newdens = 3 - ( rl_dist( p, t ) / 2 ) + ( one_in( 3 ) ? 1 : 0 ); - if( newdens > 3 ) { - newdens = 3; - } - if( newdens > 0 ) { - add_field( t, fd_acid, newdens ); - } - } - } - } - break; - - case fd_bees: - dirty_transparency_cache = true; - // Poor bees are vulnerable to so many other fields. - // TODO: maybe adjust effects based on different fields. - if( curfield.findField( fd_web ) || - curfield.findField( fd_fire ) || - curfield.findField( fd_smoke ) || - curfield.findField( fd_toxic_gas ) || - curfield.findField( fd_tear_gas ) || - curfield.findField( fd_relax_gas ) || - curfield.findField( fd_nuke_gas ) || - curfield.findField( fd_gas_vent ) || - curfield.findField( fd_smoke_vent ) || - curfield.findField( fd_fungicidal_gas ) || - curfield.findField( fd_fire_vent ) || - curfield.findField( fd_flame_burst ) || - curfield.findField( fd_electricity ) || - curfield.findField( fd_fatigue ) || - curfield.findField( fd_shock_vent ) || - curfield.findField( fd_plasma ) || - curfield.findField( fd_laser ) || - curfield.findField( fd_dazzling ) || - curfield.findField( fd_electricity ) || - curfield.findField( fd_incendiary ) ) { - // Kill them at the end of processing. - cur.setFieldDensity( 0 ); - } else { - // Bees chase the player if in range, wander randomly otherwise. - if( !g->u.is_underwater() && - rl_dist( p, g->u.pos() ) < 10 && - clear_path( p, g->u.pos(), 10, 0, 100 ) ) { - - std::vector candidate_positions = - squares_in_direction( p.x, p.y, g->u.posx(), g->u.posy() ); - for( auto &candidate_position : candidate_positions ) { - field &target_field = - get_field( tripoint( candidate_position, p.z ) ); - // Only shift if there are no bees already there. - // TODO: Figure out a way to merge bee fields without allowing - // Them to effectively move several times in a turn depending - // on iteration direction. - if( !target_field.findField( fd_bees ) ) { - add_field( tripoint( candidate_position, p.z ), fd_bees, - cur.getFieldDensity(), cur.getFieldAge() ); - cur.setFieldDensity( 0 ); - break; - } - } - } else { - spread_gas( cur, p, curtype, 5, 0_turns ); - } - } - break; - - case fd_incendiary: { - //Needed for variable scope - dirty_transparency_cache = true; - tripoint dst( p.x + rng( -1, 1 ), p.y + rng( -1, 1 ), p.z ); - if( has_flag( TFLAG_FLAMMABLE, dst ) || - has_flag( TFLAG_FLAMMABLE_ASH, dst ) || - has_flag( TFLAG_FLAMMABLE_HARD, dst ) ) { - add_field( dst, fd_fire, 1 ); - } - - //check piles for flammable items and set those on fire - if( flammable_items_at( dst ) ) { - add_field( dst, fd_fire, 1 ); - } - - spread_gas( cur, p, curtype, 66, 4_minutes ); - create_hot_air( p, cur.getFieldDensity() ); - } - break; - - //Legacy Stuff - case fd_rubble: - make_rubble( p ); - break; - - case fd_fungicidal_gas: { - dirty_transparency_cache = true; - spread_gas( cur, p, curtype, 120, 1_minutes ); - //check the terrain and replace it accordingly to simulate the fungus dieing off - const auto &ter = map_tile.get_ter_t(); - const auto &frn = map_tile.get_furn_t(); - const int density = cur.getFieldDensity(); - if( ter.has_flag( "FUNGUS" ) && one_in( 10 / density ) ) { - ter_set( p, t_dirt ); - } - if( frn.has_flag( "FUNGUS" ) && one_in( 10 / density ) ) { - furn_set( p, f_null ); - } - } - break; - - default: - //Suppress warnings - break; - - } // switch (curtype) - - cur.setFieldAge( cur.getFieldAge() + 1_turns ); - auto &fdata = fieldlist[cur.getFieldType()]; - if( fdata.halflife > 0_turns && cur.getFieldAge() > 0_turns && - dice( 2, to_turns( cur.getFieldAge() ) ) > to_turns( fdata.halflife ) ) { - cur.setFieldAge( 0_turns ); - cur.setFieldDensity( cur.getFieldDensity() - 1 ); - } - if( !cur.isAlive() ) { - current_submap->field_count--; - curfield.removeField( it++ ); - } else { - ++it; - } - } - } - } - return dirty_transparency_cache; -} - -//This entire function makes very little sense. Why are the rules the way they are? Why does walking into some things destroy them but not others? - -/* -Function: step_in_field -Triggers any active abilities a field effect would have. Fire burns you, acid melts you, etc. -If you add a field effect that interacts with the player place a case statement in the switch here. -If you wish for a field effect to do something over time (propagate, interact with terrain, etc) place it in process_subfields -*/ -void map::player_in_field( player &u ) -{ - // A copy of the current field for reference. Do not add fields to it, use map::add_field - field &curfield = get_field( u.pos() ); - bool inside = false; // Are we inside? - //to modify power of a field based on... whatever is relevant for the effect. - int adjusted_intensity; - - //If we are in a vehicle figure out if we are inside (reduces effects usually) - // and what part of the vehicle we need to deal with. - if( u.in_vehicle ) { - if( const optional_vpart_position vp = veh_at( u.pos() ) ) { - inside = vp->is_inside(); - } - } - - // Iterate through all field effects on this tile. - // Do not remove the field with removeField, instead set it's density to 0. It will be removed - // later by the field processing, which will also adjust field_count accordingly. - for( auto &field_list_it : curfield ) { - field_entry &cur = field_list_it.second; - if( !cur.isAlive() ) { - continue; - } - - //Do things based on what field effect we are currently in. - switch( cur.getFieldType() ) { - case fd_null: - case fd_blood: // It doesn't actually do anything //necessary to add other types of blood? - case fd_bile: // Ditto - case fd_cigsmoke: - case fd_weedsmoke: - case fd_methsmoke: - case fd_cracksmoke: - //break instead of return in the event of post-processing in the future; - // also we're in a loop now! - break; - - case fd_web: { - //If we are in a web, can't walk in webs or are in a vehicle, get webbed maybe. - //Moving through multiple webs stacks the effect. - if( !u.has_trait( trait_id( "WEB_WALKER" ) ) && !u.in_vehicle ) { - //between 5 and 15 minus your current web level. - u.add_effect( effect_webbed, 1_turns, num_bp, true, cur.getFieldDensity() ); - cur.setFieldDensity( 0 ); //Its spent. - continue; - //If you are in a vehicle destroy the web. - //It should of been destroyed when you ran over it anyway. - } else if( u.in_vehicle ) { - cur.setFieldDensity( 0 ); - continue; - } - } - break; - - case fd_acid: { - // Assume vehicles block acid damage entirely, - // you're certainly not standing in it. - if( u.in_vehicle ) { - break; - } - - if( u.has_trait( trait_id( "ACIDPROOF" ) ) ) { - // No need for warnings - break; - } - - const int density = cur.getFieldDensity(); - int total_damage = 0; - // Use a helper for a bit less boilerplate - const auto burn_part = [&]( body_part bp, const int scale ) { - const int damage = rng( 1, scale + density ); - // A bit ugly, but better than being annoyed by acid when in hazmat - if( u.get_armor_type( DT_ACID, bp ) < damage ) { - auto ddi = u.deal_damage( nullptr, bp, damage_instance( DT_ACID, damage ) ); - total_damage += ddi.total_damage(); - } - // Represents acid seeping in rather than being splashed on - u.add_env_effect( effect_corroding, bp, 2 + density, time_duration::from_turns( rng( 2, - 1 + density ) ), bp, false, 0 ); - }; - - // 1-3 at density, 1-4 at 2, 1-5 at 3 - burn_part( bp_foot_l, 2 ); - burn_part( bp_foot_r, 2 ); - // 1 dmg at 1 density, 1-3 at 2, 1-5 at 3 - burn_part( bp_leg_l, density - 1 ); - burn_part( bp_leg_r, density - 1 ); - const bool on_ground = u.is_on_ground(); - if( on_ground ) { - // Before, it would just break the legs and leave the survivor alone - burn_part( bp_hand_l, 2 ); - burn_part( bp_hand_r, 2 ); - burn_part( bp_torso, 2 ); - // Less arms = less ability to keep upright - if( ( !u.has_two_arms() && one_in( 4 ) ) || one_in( 2 ) ) { - burn_part( bp_arm_l, 1 ); - burn_part( bp_arm_r, 1 ); - burn_part( bp_head, 1 ); - } - } - - if( on_ground && total_damage > 0 ) { - u.add_msg_player_or_npc( m_bad, _( "The acid burns your body!" ), - _( "The acid burns s body!" ) ); - } else if( total_damage > 0 ) { - u.add_msg_player_or_npc( m_bad, _( "The acid burns your legs and feet!" ), - _( "The acid burns s legs and feet!" ) ); - } else if( on_ground ) { - u.add_msg_if_player( m_warning, _( "You're lying in a pool of acid" ) ); - } else { - u.add_msg_if_player( m_warning, _( "You're standing in a pool of acid" ) ); - } - - u.check_dead_state(); - } - break; - - case fd_sap: - //Sap causes the player to get sap disease, slowing them down. - if( u.in_vehicle ) { - break; //sap does nothing to cars. - } - u.add_msg_player_or_npc( m_bad, _( "The sap sticks to you!" ), - _( "The sap sticks to !" ) ); - u.add_effect( effect_sap, cur.getFieldDensity() * 2_turns ); - cur.setFieldDensity( cur.getFieldDensity() - 1 ); //Use up sap. - break; - - case fd_sludge: - //sludge is on the ground, but you are above the ground when boarded on a vehicle - if( !u.in_vehicle ) { - u.add_msg_if_player( m_bad, _( "The sludge is thick and sticky. You struggle to pull free." ) ); - u.moves -= cur.getFieldDensity() * 300; - cur.setFieldDensity( 0 ); - } - break; - - case fd_fire: - if( u.has_active_bionic( bionic_id( "bio_heatsink" ) ) || u.is_wearing( "rm13_armor_on" ) ) { - //heatsink or suit prevents ALL fire damage. - break; - } - //Burn the player. Less so if you are in a car or ON a car. - adjusted_intensity = cur.getFieldDensity(); - if( u.in_vehicle ) { - if( inside ) { - adjusted_intensity -= 2; - } else { - adjusted_intensity -= 1; - } - } - - if( adjusted_intensity < 1 ) { - break; - } - { - // Burn message by intensity - static const std::array player_burn_msg = {{ - translate_marker( "You burn your legs and feet!" ), - translate_marker( "You're burning up!" ), - translate_marker( "You're set ablaze!" ), - translate_marker( "Your whole body is burning!" ) - } - }; - static const std::array npc_burn_msg = {{ - translate_marker( " burns their legs and feet!" ), - translate_marker( " is burning up!" ), - translate_marker( " is set ablaze!" ), - translate_marker( "s whole body is burning!" ) - } - }; - static const std::array player_warn_msg = {{ - translate_marker( "You're standing in a fire!" ), - translate_marker( "You're waist-deep in a fire!" ), - translate_marker( "You're surrounded by raging fire!" ), - translate_marker( "You're lying in fire!" ) - } - }; - - int burn_min = adjusted_intensity; - int burn_max = 3 * adjusted_intensity + 3; - std::list parts_burned; - int msg_num = adjusted_intensity - 1; - if( !u.is_on_ground() ) { - switch( adjusted_intensity ) { - case 3: - parts_burned.push_back( bp_hand_l ); - parts_burned.push_back( bp_hand_r ); - parts_burned.push_back( bp_arm_l ); - parts_burned.push_back( bp_arm_r ); - /* fallthrough */ - case 2: - parts_burned.push_back( bp_torso ); - /* fallthrough */ - case 1: - parts_burned.push_back( bp_foot_l ); - parts_burned.push_back( bp_foot_r ); - parts_burned.push_back( bp_leg_l ); - parts_burned.push_back( bp_leg_r ); - } - } else { - // Lying in the fire is BAAAD news, hits every body part. - msg_num = 3; - parts_burned.assign( all_body_parts.begin(), all_body_parts.end() ); - } - - int total_damage = 0; - for( auto part_burned : parts_burned ) { - const auto dealt = u.deal_damage( nullptr, part_burned, - damage_instance( DT_HEAT, rng( burn_min, burn_max ) ) ); - total_damage += dealt.type_damage( DT_HEAT ); - } - if( total_damage > 0 ) { - u.add_msg_player_or_npc( m_bad, _( player_burn_msg[msg_num] ), _( npc_burn_msg[msg_num] ) ); - } else { - u.add_msg_if_player( m_warning, _( player_warn_msg[msg_num] ) ); - } - u.check_dead_state(); - } - break; - - case fd_smoke: { - if( !inside ) { - //Get smoke disease from standing in smoke. - int density = cur.getFieldDensity(); - int coughStr; - time_duration coughDur = 0_turns; - if( density >= 3 ) { // thick smoke - coughStr = 4; - coughDur = 15_turns; - } else if( density == 2 ) { // smoke - coughStr = 2; - coughDur = 7_turns; - } else { // density 1, thin smoke - coughStr = 1; - coughDur = 2_turns; - } - u.add_env_effect( effect_smoke, bp_mouth, coughStr, coughDur ); - } - } - break; - - case fd_tear_gas: - //Tear gas will both give you teargas disease and/or blind you. - if( ( cur.getFieldDensity() > 1 || !one_in( 3 ) ) && ( !inside || one_in( 3 ) ) ) { - u.add_env_effect( effect_teargas, bp_mouth, 5, 2_minutes ); - } - if( cur.getFieldDensity() > 1 && ( !inside || one_in( 3 ) ) ) { - u.add_env_effect( effect_blind, bp_eyes, cur.getFieldDensity() * 2, 1_minutes ); - } - break; - - case fd_relax_gas: - if( ( cur.getFieldDensity() > 1 || !one_in( 3 ) ) && ( !inside || one_in( 3 ) ) ) { - u.add_env_effect( effect_relax_gas, bp_mouth, cur.getFieldDensity() * 2, 3_turns ); - } - break; - - case fd_fungal_haze: - if( !u.has_trait( trait_id( "M_IMMUNE" ) ) && ( !inside || one_in( 4 ) ) ) { - u.add_env_effect( effect_fungus, bp_mouth, 4, 10_minutes, num_bp, true ); - u.add_env_effect( effect_fungus, bp_eyes, 4, 10_minutes, num_bp, true ); - } - break; - - case fd_dazzling: - if( cur.getFieldDensity() > 1 || one_in( 5 ) ) { - u.add_env_effect( effect_blind, bp_eyes, 10, 10_turns ); - } else { - u.add_env_effect( effect_blind, bp_eyes, 2, 2_turns ); - } - break; - - case fd_toxic_gas: - // Toxic gas at low levels poisons you. - // Toxic gas at high levels will cause very nasty poison. - { - bool inhaled = false; - if( ( cur.getFieldDensity() == 2 && !inside ) || - ( cur.getFieldDensity() == 3 && inside ) ) { - inhaled = u.add_env_effect( effect_poison, bp_mouth, 5, 3_minutes ); - } else if( cur.getFieldDensity() == 3 && !inside ) { - inhaled = u.add_env_effect( effect_badpoison, bp_mouth, 5, 3_minutes ); - } else if( cur.getFieldDensity() == 1 && ( !inside ) ) { - inhaled = u.add_env_effect( effect_poison, bp_mouth, 2, 2_minutes ); - } - if( inhaled ) { - // player does not know how the npc feels, so no message. - u.add_msg_if_player( m_bad, _( "You feel sick from inhaling the %s" ), cur.name() ); - } - } - break; - - case fd_nuke_gas: { - // Get irradiated by the nuclear fallout. - // Changed to min of density, not 0. - float rads = rng( cur.getFieldDensity(), - cur.getFieldDensity() * ( cur.getFieldDensity() + 1 ) ); - bool rad_proof = !u.irradiate( rads ); - // TODO: Reduce damage for rad resistant? - if( cur.getFieldDensity() == 3 && !rad_proof ) { - u.add_msg_if_player( m_bad, _( "This radioactive gas burns!" ) ); - u.hurtall( rng( 1, 3 ), nullptr ); - } - } - break; - - case fd_flame_burst: - //A burst of flame? Only hits the legs and torso. - if( inside ) { - break; //fireballs can't touch you inside a car. - } - if( !u.has_active_bionic( bionic_id( "bio_heatsink" ) ) && - !u.is_wearing( "rm13_armor_on" ) ) { //heatsink or suit stops fire. - u.add_msg_player_or_npc( m_bad, _( "You're torched by flames!" ), - _( " is torched by flames!" ) ); - u.deal_damage( nullptr, bp_leg_l, damage_instance( DT_HEAT, rng( 2, 6 ) ) ); - u.deal_damage( nullptr, bp_leg_r, damage_instance( DT_HEAT, rng( 2, 6 ) ) ); - u.deal_damage( nullptr, bp_torso, damage_instance( DT_HEAT, rng( 4, 9 ) ) ); - u.check_dead_state(); - } else { - u.add_msg_player_or_npc( _( "These flames do not burn you." ), - _( "Those flames do not burn ." ) ); - } - break; - - case fd_electricity: { - // Small universal damage based on density, only if not electroproofed. - if( u.is_elec_immune() ) { - break; - } - int total_damage = 0; - for( size_t i = 0; i < num_hp_parts; i++ ) { - const body_part bp = player::hp_to_bp( static_cast( i ) ); - const int dmg = rng( 1, cur.getFieldDensity() ); - total_damage += u.deal_damage( nullptr, bp, damage_instance( DT_ELECTRIC, dmg ) ).total_damage(); - } - - if( total_damage > 0 ) { - if( u.has_trait( trait_ELECTRORECEPTORS ) ) { - u.add_msg_player_or_npc( m_bad, _( "You're painfully electrocuted!" ), - _( " is shocked!" ) ); - u.mod_pain( total_damage / 2 ); - } else { - u.add_msg_player_or_npc( m_bad, _( "You're shocked!" ), _( " is shocked!" ) ); - } - } else { - u.add_msg_player_or_npc( _( "The electric cloud doesn't affect you." ), - _( "The electric cloud doesn't seem to affect ." ) ); - } - } - - break; - - case fd_fatigue: - //Teleports you... somewhere. - if( rng( 0, 2 ) < cur.getFieldDensity() && u.is_player() ) { - // TODO: allow teleporting for npcs - add_msg( m_bad, _( "You're violently teleported!" ) ); - u.hurtall( cur.getFieldDensity(), nullptr ); - g->teleport(); - } - break; - - // Why do these get removed??? - // Stepping on a shock vent shuts it down. - case fd_shock_vent: - // Stepping on an acid vent shuts it down. - case fd_acid_vent: - cur.setFieldDensity( 0 ); - continue; - - case fd_bees: - // Player is immune to bees while underwater. - if( !u.is_underwater() ) { - int times_stung = 0; - int density = cur.getFieldDensity(); - // If the bees can get at you, they cause steadily increasing pain. - // TODO: Specific stinging messages. - times_stung += one_in( 4 ) && - u.add_env_effect( effect_stung, bp_torso, density, 9_minutes ); - times_stung += one_in( 4 ) && - u.add_env_effect( effect_stung, bp_torso, density, 9_minutes ); - times_stung += one_in( 4 ) && - u.add_env_effect( effect_stung, bp_torso, density, 9_minutes ); - times_stung += one_in( 4 ) && - u.add_env_effect( effect_stung, bp_torso, density, 9_minutes ); - times_stung += one_in( 4 ) && - u.add_env_effect( effect_stung, bp_torso, density, 9_minutes ); - times_stung += one_in( 4 ) && - u.add_env_effect( effect_stung, bp_torso, density, 9_minutes ); - times_stung += one_in( 4 ) && - u.add_env_effect( effect_stung, bp_torso, density, 9_minutes ); - times_stung += one_in( 4 ) && - u.add_env_effect( effect_stung, bp_torso, density, 9_minutes ); - switch( times_stung ) { - case 0: - // Woo, unscathed! - break; - case 1: - u.add_msg_if_player( m_bad, _( "The bees sting you!" ) ); - break; - case 2: - case 3: - u.add_msg_if_player( m_bad, _( "The bees sting you several times!" ) ); - break; - case 4: - case 5: - u.add_msg_if_player( m_bad, _( "The bees sting you many times!" ) ); - break; - case 6: - case 7: - case 8: - default: - u.add_msg_if_player( m_bad, _( "The bees sting you all over your body!" ) ); - break; - } - } - break; - - case fd_incendiary: - // Mysterious incendiary substance melts you horribly. - if( u.has_trait( trait_M_SKIN2 ) || - u.has_trait( trait_M_SKIN3 ) || - cur.getFieldDensity() == 1 ) { - u.add_msg_player_or_npc( m_bad, _( "The incendiary burns you!" ), - _( "The incendiary burns !" ) ); - u.hurtall( rng( 1, 3 ), nullptr ); - } else { - u.add_msg_player_or_npc( m_bad, _( "The incendiary melts into your skin!" ), - _( "The incendiary melts into s skin!" ) ); - u.add_effect( effect_onfire, 8_turns, bp_torso ); - u.hurtall( rng( 2, 6 ), nullptr ); - } - break; - - case fd_fungicidal_gas: - // Fungicidal gas is unhealthy and becomes deadly if you cross a related threshold. - { - // The gas won't harm you inside a vehicle. - if( inside ) { - break; - } - // Full body suits protect you from the effects of the gas. - if( u.worn_with_flag( "GAS_PROOF" ) && u.get_env_resist( bp_mouth ) >= 15 && - u.get_env_resist( bp_eyes ) >= 15 ) { - break; - } - bool inhaled = false; - const int density = cur.getFieldDensity(); - inhaled = u.add_env_effect( effect_poison, bp_mouth, 5, density * 1_minutes ); - if( u.has_trait( trait_id( "THRESH_MYCUS" ) ) || u.has_trait( trait_id( "THRESH_MARLOSS" ) ) ) { - inhaled |= u.add_env_effect( effect_badpoison, bp_mouth, 5, density * 1_minutes ); - u.hurtall( rng( density, density * 2 ), nullptr ); - u.add_msg_if_player( m_bad, _( "The %s burns your skin." ), cur.name() ); - } - - if( inhaled ) { - u.add_msg_if_player( m_bad, _( "The %s makes you feel sick." ), cur.name() ); - } - } - break; - - default: - //Suppress warnings - break; - } - } - -} - -void map::creature_in_field( Creature &critter ) -{ - auto m = dynamic_cast( &critter ); - auto p = dynamic_cast( &critter ); - if( m != nullptr ) { - monster_in_field( *m ); - } else if( p != nullptr ) { - player_in_field( *p ); - } -} - -void map::monster_in_field( monster &z ) -{ - if( z.digging() ) { - return; // Digging monsters are immune to fields - } - field &curfield = get_field( z.pos() ); - - int dam = 0; - // Iterate through all field effects on this tile. - // Do not remove the field with removeField, instead set it's density to 0. It will be removed - // later by the field processing, which will also adjust field_count accordingly. - for( auto &field_list_it : curfield ) { - field_entry &cur = field_list_it.second; - if( !cur.isAlive() ) { - continue; - } - - switch( cur.getFieldType() ) { - case fd_null: - case fd_blood: // It doesn't actually do anything - case fd_bile: // Ditto - break; - - case fd_web: - if( !z.has_flag( MF_WEBWALK ) ) { - z.add_effect( effect_webbed, 1_turns, num_bp, true, cur.getFieldDensity() ); - cur.setFieldDensity( 0 ); - } - break; - - case fd_acid: - if( !z.has_flag( MF_FLIES ) ) { - const int d = rng( cur.getFieldDensity(), cur.getFieldDensity() * 3 ); - z.deal_damage( nullptr, bp_torso, damage_instance( DT_ACID, d ) ); - z.check_dead_state(); - } - break; - - case fd_sap: - z.moves -= cur.getFieldDensity() * 5; - cur.setFieldDensity( cur.getFieldDensity() - 1 ); - break; - - case fd_sludge: - if( !z.has_flag( MF_DIGS ) && !z.has_flag( MF_FLIES ) && - !z.has_flag( MF_SLUDGEPROOF ) ) { - z.moves -= cur.getFieldDensity() * 300; - cur.setFieldDensity( 0 ); - } - break; - - // TODO: MATERIALS Use fire resistance - case fd_fire: - if( z.has_flag( MF_FIREPROOF ) || z.has_flag( MF_FIREY ) ) { - return; - } - // TODO: Replace the section below with proper json values - if( z.made_of_any( Creature::cmat_flesh ) ) { - dam += 3; - } - if( z.made_of( material_id( "veggy" ) ) ) { - dam += 12; - } - if( z.made_of( LIQUID ) || z.made_of_any( Creature::cmat_flammable ) ) { - dam += 20; - } - if( z.made_of_any( Creature::cmat_flameres ) ) { - dam += -20; - } - if( z.has_flag( MF_FLIES ) ) { - dam -= 15; - } - dam -= z.get_armor_type( DT_HEAT, bp_torso ); - - if( cur.getFieldDensity() == 1 ) { - dam += rng( 2, 6 ); - } else if( cur.getFieldDensity() == 2 ) { - dam += rng( 6, 12 ); - if( !z.has_flag( MF_FLIES ) ) { - z.moves -= 20; - if( dam > 0 ) { - z.add_effect( effect_onfire, 1_turns * rng( dam / 2, dam * 2 ) ); - } - } - } else if( cur.getFieldDensity() == 3 ) { - dam += rng( 10, 20 ); - if( !z.has_flag( MF_FLIES ) || one_in( 3 ) ) { - z.moves -= 40; - if( dam > 0 ) { - z.add_effect( effect_onfire, 1_turns * rng( dam / 2, dam * 2 ) ); - } - } - } - // Drop through to smoke no longer needed as smoke will exist in the same square now, - // this would double apply otherwise. - break; - - case fd_smoke: - if( !z.has_flag( MF_NO_BREATHE ) ) { - if( cur.getFieldDensity() == 3 ) { - z.moves -= rng( 10, 20 ); - } - if( z.made_of( material_id( "veggy" ) ) ) { // Plants suffer from smoke even worse - z.moves -= rng( 1, cur.getFieldDensity() * 12 ); - } - } - break; - - case fd_tear_gas: - if( z.made_of_any( Creature::cmat_fleshnveg ) && !z.has_flag( MF_NO_BREATHE ) ) { - if( cur.getFieldDensity() == 3 ) { - z.add_effect( effect_stunned, rng( 1_minutes, 2_minutes ) ); - dam += rng( 4, 10 ); - } else if( cur.getFieldDensity() == 2 ) { - z.add_effect( effect_stunned, rng( 5_turns, 10_turns ) ); - dam += rng( 2, 5 ); - } else { - z.add_effect( effect_stunned, rng( 1_turns, 5_turns ) ); - } - if( z.made_of( material_id( "veggy" ) ) ) { - z.moves -= rng( cur.getFieldDensity() * 5, cur.getFieldDensity() * 12 ); - dam += cur.getFieldDensity() * rng( 8, 14 ); - } - if( z.has_flag( MF_SEES ) ) { - z.add_effect( effect_blind, cur.getFieldDensity() * 8_turns ); - } - } - break; - - case fd_relax_gas: - if( z.made_of_any( Creature::cmat_fleshnveg ) && !z.has_flag( MF_NO_BREATHE ) ) { - z.add_effect( effect_stunned, rng( cur.getFieldDensity() * 4_turns, - cur.getFieldDensity() * 8_turns ) ); - } - break; - - case fd_dazzling: - if( z.has_flag( MF_SEES ) && !z.has_flag( MF_ELECTRONIC ) ) { - z.add_effect( effect_blind, cur.getFieldDensity() * 12_turns ); - z.add_effect( effect_stunned, cur.getFieldDensity() * rng( 5_turns, 12_turns ) ); - } - break; - - case fd_toxic_gas: - if( !z.has_flag( MF_NO_BREATHE ) ) { - dam += cur.getFieldDensity(); - z.moves -= cur.getFieldDensity(); - } - break; - - case fd_nuke_gas: - if( !z.has_flag( MF_NO_BREATHE ) ) { - if( cur.getFieldDensity() == 3 ) { - z.moves -= rng( 60, 120 ); - dam += rng( 30, 50 ); - } else if( cur.getFieldDensity() == 2 ) { - z.moves -= rng( 20, 50 ); - dam += rng( 10, 25 ); - } else { - z.moves -= rng( 0, 15 ); - dam += rng( 0, 12 ); - } - if( z.made_of( material_id( "veggy" ) ) ) { - z.moves -= rng( cur.getFieldDensity() * 5, cur.getFieldDensity() * 12 ); - dam *= cur.getFieldDensity(); - } - } - break; - - // TODO: MATERIALS Use fire resistance - case fd_flame_burst: - if( z.has_flag( MF_FIREPROOF ) || z.has_flag( MF_FIREY ) ) { - return; - } - if( z.made_of_any( Creature::cmat_flesh ) ) { - dam += 3; - } - if( z.made_of( material_id( "veggy" ) ) ) { - dam += 12; - } - if( z.made_of( LIQUID ) || z.made_of_any( Creature::cmat_flammable ) ) { - dam += 50; - } - if( z.made_of_any( Creature::cmat_flameres ) ) { - dam += -25; - } - dam += rng( 0, 8 ); - z.moves -= 20; - break; - - case fd_electricity: - // We don't want to increase dam, but deal a separate hit so that it can apply effects - z.deal_damage( nullptr, bp_torso, - damage_instance( DT_ELECTRIC, rng( 1, cur.getFieldDensity() * 3 ) ) ); - break; - - case fd_fatigue: - if( rng( 0, 2 ) < cur.getFieldDensity() ) { - dam += cur.getFieldDensity(); - int tries = 0; - tripoint newpos = z.pos(); - do { - newpos.x = rng( z.posx() - SEEX, z.posx() + SEEX ); - newpos.y = rng( z.posy() - SEEY, z.posy() + SEEY ); - tries++; - } while( impassable( newpos ) && tries != 10 ); - - if( tries == 10 ) { - z.die_in_explosion( nullptr ); - } else if( monster *const other = g->critter_at( newpos ) ) { - if( g->u.sees( z ) ) { - add_msg( _( "The %1$s teleports into a %2$s, killing them both!" ), - z.name(), other->name() ); - } - other->die_in_explosion( &z ); - } else { - z.setpos( newpos ); - } - } - break; - - case fd_incendiary: - // TODO: MATERIALS Use fire resistance - if( z.has_flag( MF_FIREPROOF ) || z.has_flag( MF_FIREY ) ) { - return; - } - if( z.made_of_any( Creature::cmat_flesh ) ) { - dam += 3; - } - if( z.made_of( material_id( "veggy" ) ) ) { - dam += 12; - } - if( z.made_of( LIQUID ) || z.made_of_any( Creature::cmat_flammable ) ) { - dam += 20; - } - if( z.made_of_any( Creature::cmat_flameres ) ) { - dam += -5; - } - - if( cur.getFieldDensity() == 1 ) { - dam += rng( 2, 6 ); - } else if( cur.getFieldDensity() == 2 ) { - dam += rng( 6, 12 ); - z.moves -= 20; - if( !z.made_of( LIQUID ) && !z.made_of_any( Creature::cmat_flameres ) ) { - z.add_effect( effect_onfire, rng( 8_turns, 12_turns ) ); - } - } else if( cur.getFieldDensity() == 3 ) { - dam += rng( 10, 20 ); - z.moves -= 40; - if( !z.made_of( LIQUID ) && !z.made_of_any( Creature::cmat_flameres ) ) { - z.add_effect( effect_onfire, rng( 12_turns, 16_turns ) ); - } - } - break; - - case fd_fungal_haze: - if( !z.type->in_species( FUNGUS ) && - !z.type->has_flag( MF_NO_BREATHE ) && - !z.make_fungus() ) { - // Don't insta-kill jabberwocks, that's silly - const int density = cur.getFieldDensity(); - z.moves -= rng( 10 * density, 30 * density ); - dam += rng( 0, 10 * density ); - } - break; - - case fd_fungicidal_gas: - if( z.type->in_species( FUNGUS ) ) { - const int density = cur.getFieldDensity(); - z.moves -= rng( 10 * density, 30 * density ); - dam += rng( 4, 7 * density ); - } - break; - - default: - //Suppress warnings - break; - } - } - - if( dam > 0 ) { - z.apply_damage( nullptr, bp_torso, dam, true ); - z.check_dead_state(); - } -} - int field_entry::move_cost() const { return fieldlist[type].move_cost[ getFieldDensity() - 1 ]; @@ -2644,56 +502,6 @@ int field_entry::getFieldDensity() const return density; } -std::tuple map::get_wind_blockers( const int &winddirection, - const tripoint &pos ) -{ - double raddir = ( ( winddirection + 180 ) % 360 ) * ( M_PI / 180 ); - float fx = -cos( raddir ); - float fy = sin( raddir ); - int roundedx; - int roundedy; - if( fx > 0.5 ) { - roundedx = 1; - } else if( fx < -0.5 ) { - roundedx = -1; - } else { - roundedx = 0; - } - if( fy > 0.5 ) { - roundedy = 1; - } else if( fy < -0.5 ) { - roundedy = -1; - } else { - roundedy = 0; - } - tripoint removepoint( pos - point( roundedx, roundedy ) ); - tripoint removepoint2; - tripoint removepoint3; - if( roundedy != 0 && roundedx == 0 ) { - removepoint2 = removepoint - point( 1, 0 ); - removepoint3 = removepoint + point( 1, 0 ); - } else if( roundedy == 0 && roundedx != 0 ) { - removepoint2 = removepoint - point( 0, 1 ); - removepoint3 = removepoint + point( 0, 1 ); - } else if( roundedy > 0 && roundedx > 0 ) { - removepoint2 = removepoint - point( 1, 0 ); - removepoint3 = removepoint - point( 0, 1 ); - } else if( roundedy < 0 && roundedx > 0 ) { - removepoint2 = removepoint - point( 1, 0 ); - removepoint3 = removepoint + point( 0, 1 ); - } else if( roundedy < 0 && roundedx < 0 ) { - removepoint2 = removepoint + point( 1, 0 ); - removepoint3 = removepoint + point( 0, 1 ); - } else if( roundedy > 0 && roundedx < 0 ) { - removepoint2 = removepoint + point( 1, 0 ); - removepoint3 = removepoint - point( 0, 1 ); - } - const maptile remove_tile = maptile_at( removepoint ); - const maptile remove_tile2 = maptile_at( removepoint2 ); - const maptile remove_tile3 = maptile_at( removepoint3 ); - return std::make_tuple( remove_tile, remove_tile2, remove_tile3 ); -} - time_duration field_entry::getFieldAge() const { return age; @@ -2864,82 +672,3 @@ bool field_type_dangerous( field_id id ) const field_t &ft = fieldlist[id]; return ft.dangerous[0] || ft.dangerous[1] || ft.dangerous[2]; } - -void map::emit_field( const tripoint &pos, const emit_id &src, float mul ) -{ - if( !src.is_valid() ) { - return; - } - - float chance = src->chance() * mul; - if( src.is_valid() && x_in_y( chance, 100 ) ) { - int qty = chance > 100.0f ? roll_remainder( src->qty() * chance / 100.0f ) : src->qty(); - propagate_field( pos, src->field(), qty, src->density() ); - } -} - -void map::propagate_field( const tripoint ¢er, const field_id type, int amount, - int max_density ) -{ - using gas_blast = std::pair; - std::priority_queue, pair_greater_cmp_first> open; - std::set closed; - open.push( { 0.0f, center } ); - - const bool not_gas = fieldlist[ type ].phase != GAS; - - while( amount > 0 && !open.empty() ) { - if( closed.count( open.top().second ) ) { - open.pop(); - continue; - } - - // All points with equal gas density should propagate at the same time - std::list gas_front; - gas_front.push_back( open.top() ); - int cur_intensity = get_field_strength( open.top().second, type ); - open.pop(); - while( !open.empty() && get_field_strength( open.top().second, type ) == cur_intensity ) { - if( closed.count( open.top().second ) == 0 ) { - gas_front.push_back( open.top() ); - } - - open.pop(); - } - - int increment = std::max( 1, amount / gas_front.size() ); - - while( amount > 0 && !gas_front.empty() ) { - auto gp = random_entry_removed( gas_front ); - closed.insert( gp.second ); - int cur_strength = get_field_strength( gp.second, type ); - if( cur_strength < max_density ) { - int bonus = std::min( max_density - cur_strength, increment ); - adjust_field_strength( gp.second, type, bonus ); - amount -= bonus; - } else { - amount--; - } - - if( amount <= 0 ) { - return; - } - - static const std::array x_offset = {{ -1, 1, 0, 0, 1, -1, -1, 1 }}; - static const std::array y_offset = {{ 0, 0, -1, 1, -1, 1, -1, 1 }}; - for( size_t i = 0; i < 8; i++ ) { - tripoint pt = gp.second + point( x_offset[ i ], y_offset[ i ] ); - if( closed.count( pt ) > 0 ) { - continue; - } - - if( impassable( pt ) && ( not_gas || !has_flag( TFLAG_PERMEABLE, pt ) ) ) { - closed.insert( pt ); - continue; - } - - open.push( { static_cast( rl_dist( center, pt ) ), pt } ); - } - } - } -} diff --git a/src/field.h b/src/field.h index f3ae6d7a1f877..08ea930c61012 100644 --- a/src/field.h +++ b/src/field.h @@ -132,15 +132,6 @@ extern field_id field_from_ident( const std::string &field_ident ); */ bool field_type_dangerous( field_id id ); -/** - * converts wind direction to list of co-ords to block neighbours to spread to. - */ -std::tuple get_wind_blockers( const int &winddirection, - const tripoint &pos ); -/** - * converts xy of disallowed wind directions to map tiles. - */ - /** * An active or passive effect existing on a tile. * Each effect can vary in intensity (density) and age (usually used as a time to live). diff --git a/src/field_type.cpp b/src/field_type.cpp new file mode 100644 index 0000000000000..b4be88e7dc51c --- /dev/null +++ b/src/field_type.cpp @@ -0,0 +1,253 @@ +#include "field_type.h" + +#include "debug.h" +#include "enums.h" +#include "generic_factory.h" +#include "json.h" +#include "optional.h" + +namespace +{ + +generic_factory all_field_types( "field types" ); + +} + +/** @relates int_id */ +template<> +bool int_id::is_valid() const +{ + return all_field_types.is_valid( *this ); +} + +/** @relates int_id */ +template<> +const field_type &int_id::obj() const +{ + return all_field_types.obj( *this ); +} + +/** @relates int_id */ +template<> +const string_id &int_id::id() const +{ + return all_field_types.convert( *this ); +} + +/** @relates string_id */ +template<> +bool string_id::is_valid() const +{ + return all_field_types.is_valid( *this ); +} + +/** @relates string_id */ +template<> +const field_type &string_id::obj() const +{ + return all_field_types.obj( *this ); +} + +/** @relates string_id */ +template<> +int_id string_id::id() const +{ + return all_field_types.convert( *this, x_fd_null ); +} + +/** @relates int_id */ +template<> +int_id::int_id( const string_id &id ) : _id( id.id() ) +{ +} + +void field_type::load( JsonObject &jo, const std::string & ) +{ + mandatory( jo, was_loaded, "legacy_enum_id", legacy_enum_id ); + JsonArray ja = jo.get_array( "intensity_levels" ); + for( size_t i = 0; i < ja.size(); ++i ) { + field_intensity_level fil; + field_intensity_level fallback = i > 0 ? intensity_levels[i - 1] : fil; + JsonObject jao = ja.get_object( i ); + optional( jao, was_loaded, "name", fil.name, fallback.name ); + optional( jao, was_loaded, "sym", fil.symbol, unicode_codepoint_from_symbol_reader, + fallback.symbol ); + fil.color = jao.has_member( "color" ) ? color_from_string( jao.get_string( "color" ) ) : + fallback.color; + optional( jao, was_loaded, "transparent", fil.transparent, fallback.transparent ); + optional( jao, was_loaded, "dangerous", fil.dangerous, fallback.dangerous ); + optional( jao, was_loaded, "move_cost", fil.move_cost, fallback.move_cost ); + intensity_levels.emplace_back( fil ); + } + optional( jo, was_loaded, "priority", priority ); + optional( jo, was_loaded, "half_life", half_life ); + if( jo.has_member( "phase" ) ) { + phase = jo.get_enum_value( "phase" ); + } + optional( jo, was_loaded, "accelerated_decay", accelerated_decay ); + optional( jo, was_loaded, "do_item", do_item ); + optional( jo, was_loaded, "is_draw_field", is_draw_field ); +} + +void field_type::check() const +{ + if( intensity_levels.empty() ) { + debugmsg( "No intensity levels defined for field type \"%s\".", id.c_str() ); + } + int i = 0; + for( auto &il : intensity_levels ) { + i++; + if( il.name.empty() ) { + debugmsg( "Intensity level %d defined for field type \"%s\" has empty name.", i, id.c_str() ); + } + } +} + +size_t field_type::count() +{ + return all_field_types.size(); +} + +void field_types::load( JsonObject &jo, const std::string &src ) +{ + all_field_types.load( jo, src ); +} + +void field_types::finalize_all() +{ + set_field_type_ids(); +} + +void field_types::check_consistency() +{ + all_field_types.check(); +} + +void field_types::reset() +{ + all_field_types.reset(); +} + +const std::vector &field_types::get_all() +{ + return all_field_types.get_all(); +} + +field_type_id x_fd_null, + x_fd_blood, + x_fd_bile, + x_fd_gibs_flesh, + x_fd_gibs_veggy, + x_fd_web, + x_fd_slime, + x_fd_acid, + x_fd_sap, + x_fd_sludge, + x_fd_fire, + x_fd_rubble, + x_fd_smoke, + x_fd_toxic_gas, + x_fd_tear_gas, + x_fd_nuke_gas, + x_fd_gas_vent, + x_fd_fire_vent, + x_fd_flame_burst, + x_fd_electricity, + x_fd_fatigue, + x_fd_push_items, + x_fd_shock_vent, + x_fd_acid_vent, + x_fd_plasma, + x_fd_laser, + x_fd_spotlight, + x_fd_dazzling, + x_fd_blood_veggy, + x_fd_blood_insect, + x_fd_blood_invertebrate, + x_fd_gibs_insect, + x_fd_gibs_invertebrate, + x_fd_cigsmoke, + x_fd_weedsmoke, + x_fd_cracksmoke, + x_fd_methsmoke, + x_fd_bees, + x_fd_incendiary, + x_fd_relax_gas, + x_fd_fungal_haze, + x_fd_cold_air1, + x_fd_cold_air2, + x_fd_cold_air3, + x_fd_cold_air4, + x_fd_hot_air1, + x_fd_hot_air2, + x_fd_hot_air3, + x_fd_hot_air4, + x_fd_fungicidal_gas, + x_fd_smoke_vent + ; + +void field_types::set_field_type_ids() +{ + x_fd_null = field_type_id( "fd_null" ); + x_fd_blood = field_type_id( "fd_blood" ); + x_fd_bile = field_type_id( "fd_bile" ); + x_fd_gibs_flesh = field_type_id( "fd_gibs_flesh" ); + x_fd_gibs_veggy = field_type_id( "fd_gibs_veggy" ); + x_fd_web = field_type_id( "fd_web" ); + x_fd_slime = field_type_id( "fd_slime" ); + x_fd_acid = field_type_id( "fd_acid" ); + x_fd_sap = field_type_id( "fd_sap" ); + x_fd_sludge = field_type_id( "fd_sludge" ); + x_fd_fire = field_type_id( "fd_fire" ); + x_fd_rubble = field_type_id( "fd_rubble" ); + x_fd_smoke = field_type_id( "fd_smoke" ); + x_fd_toxic_gas = field_type_id( "fd_toxic_gas" ); + x_fd_tear_gas = field_type_id( "fd_tear_gas" ); + x_fd_nuke_gas = field_type_id( "fd_nuke_gas" ); + x_fd_gas_vent = field_type_id( "fd_gas_vent" ); + x_fd_fire_vent = field_type_id( "fd_fire_vent" ); + x_fd_flame_burst = field_type_id( "fd_flame_burst" ); + x_fd_electricity = field_type_id( "fd_electricity" ); + x_fd_fatigue = field_type_id( "fd_fatigue" ); + x_fd_push_items = field_type_id( "fd_push_items" ); + x_fd_shock_vent = field_type_id( "fd_shock_vent" ); + x_fd_acid_vent = field_type_id( "fd_acid_vent" ); + x_fd_plasma = field_type_id( "fd_plasma" ); + x_fd_laser = field_type_id( "fd_laser" ); + x_fd_spotlight = field_type_id( "fd_spotlight" ); + x_fd_dazzling = field_type_id( "fd_dazzling" ); + x_fd_blood_veggy = field_type_id( "fd_blood_veggy" ); + x_fd_blood_insect = field_type_id( "fd_blood_insect" ); + x_fd_blood_invertebrate = field_type_id( "fd_blood_invertebrate" ); + x_fd_gibs_insect = field_type_id( "fd_gibs_insect" ); + x_fd_gibs_invertebrate = field_type_id( "fd_gibs_invertebrate" ); + x_fd_cigsmoke = field_type_id( "fd_cigsmoke" ); + x_fd_weedsmoke = field_type_id( "fd_weedsmoke" ); + x_fd_cracksmoke = field_type_id( "fd_cracksmoke" ); + x_fd_methsmoke = field_type_id( "fd_methsmoke" ); + x_fd_bees = field_type_id( "fd_bees" ); + x_fd_incendiary = field_type_id( "fd_incendiary" ); + x_fd_relax_gas = field_type_id( "fd_relax_gas" ); + x_fd_fungal_haze = field_type_id( "fd_fungal_haze" ); + x_fd_cold_air1 = field_type_id( "fd_cold_air1" ); + x_fd_cold_air2 = field_type_id( "fd_cold_air2" ); + x_fd_cold_air3 = field_type_id( "fd_cold_air3" ); + x_fd_cold_air4 = field_type_id( "fd_cold_air4" ); + x_fd_hot_air1 = field_type_id( "fd_hot_air1" ); + x_fd_hot_air2 = field_type_id( "fd_hot_air2" ); + x_fd_hot_air3 = field_type_id( "fd_hot_air3" ); + x_fd_hot_air4 = field_type_id( "fd_hot_air4" ); + x_fd_fungicidal_gas = field_type_id( "fd_fungicidal_gas" ); + x_fd_smoke_vent = field_type_id( "fd_smoke_vent" ); +} + +field_type field_types::get_field_type_by_legacy_enum( int legacy_enum_id ) +{ + for( const auto &ft : all_field_types.get_all() ) { + if( legacy_enum_id == ft.legacy_enum_id ) { + return ft; + } + } + debugmsg( "Cannot find field_type for legacy enum: %d.", legacy_enum_id ); + return field_type(); +} diff --git a/src/field_type.h b/src/field_type.h new file mode 100644 index 0000000000000..73b41b10ffe54 --- /dev/null +++ b/src/field_type.h @@ -0,0 +1,147 @@ +#pragma once +#ifndef FIELD_TYPE_H +#define FIELD_TYPE_H + +#include +#include + +#include "calendar.h" +#include "catacharset.h" +#include "color.h" +#include "enums.h" +#include "type_id.h" + +class JsonObject; +struct maptile; +struct tripoint; + +enum phase_id : int; + +struct field_intensity_level { + std::string name; + uint32_t symbol = PERCENT_SIGN_UNICODE; + nc_color color = c_white; + bool dangerous = false; + bool transparent = true; + int move_cost = 0; +}; + +struct field_type { + public: + void load( JsonObject &jo, const std::string &src ); + void check() const; + + public: + // Used by generic_factory + field_type_str_id id; + bool was_loaded = false; + + public: + int legacy_enum_id = -1; + + std::vector intensity_levels; + + int priority = 0; + time_duration half_life = 0_days; + phase_id phase = phase_id::PNULL; + bool accelerated_decay = false; + bool do_item = true; + bool is_draw_field = false; + + public: + std::string get_name( int level = 0 ) const { + return intensity_levels[level].name; + } + std::string get_symbol( int level = 0 ) const { + return utf32_to_utf8( intensity_levels[level].symbol ); + } + nc_color get_color( int level = 0 ) const { + return intensity_levels[level].color; + } + int get_move_cost( int level = 0 ) const { + return intensity_levels[level].move_cost; + } + bool get_dangerous( int level = 0 ) const { + return intensity_levels[level].dangerous; + } + bool get_transparent( int level = 0 ) const { + return intensity_levels[level].transparent; + } + bool is_dangerous() const { + return std::any_of( intensity_levels.begin(), intensity_levels.end(), + []( const field_intensity_level & elem ) { + return elem.dangerous; + } ); + } + + static size_t count(); +}; + +namespace field_types +{ + +void load( JsonObject &jo, const std::string &src ); +void finalize_all(); +void check_consistency(); +void reset(); + +const std::vector &get_all(); +void set_field_type_ids(); +field_type get_field_type_by_legacy_enum( int legacy_enum_id ); + +} + +extern field_type_id x_fd_null, + x_fd_blood, + x_fd_bile, + x_fd_gibs_flesh, + x_fd_gibs_veggy, + x_fd_web, + x_fd_slime, + x_fd_acid, + x_fd_sap, + x_fd_sludge, + x_fd_fire, + x_fd_rubble, + x_fd_smoke, + x_fd_toxic_gas, + x_fd_tear_gas, + x_fd_nuke_gas, + x_fd_gas_vent, + x_fd_fire_vent, + x_fd_flame_burst, + x_fd_electricity, + x_fd_fatigue, + x_fd_push_items, + x_fd_shock_vent, + x_fd_acid_vent, + x_fd_plasma, + x_fd_laser, + x_fd_spotlight, + x_fd_dazzling, + x_fd_blood_veggy, + x_fd_blood_insect, + x_fd_blood_invertebrate, + x_fd_gibs_insect, + x_fd_gibs_invertebrate, + x_fd_cigsmoke, + x_fd_weedsmoke, + x_fd_cracksmoke, + x_fd_methsmoke, + x_fd_bees, + x_fd_incendiary, + x_fd_relax_gas, + x_fd_fungal_haze, + x_fd_cold_air1, + x_fd_cold_air2, + x_fd_cold_air3, + x_fd_cold_air4, + x_fd_hot_air1, + x_fd_hot_air2, + x_fd_hot_air3, + x_fd_hot_air4, + x_fd_fungicidal_gas, + x_fd_smoke_vent + ; + +#endif diff --git a/src/game.cpp b/src/game.cpp index 6618bbd7d7dfc..803efd11b43fb 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -164,7 +164,7 @@ # include #endif -#define dbg(x) DebugLog((DebugLevel)(x),D_GAME) << __FILE__ << ":" << __LINE__ << ": " +#define dbg(x) DebugLog((x),D_GAME) << __FILE__ << ":" << __LINE__ << ": " const int core_version = 6; static constexpr int DANGEROUS_PROXIMITY = 5; @@ -1501,7 +1501,6 @@ bool game::do_turn() sounds::process_sounds(); // Update vision caches for monsters. If this turns out to be expensive, // consider a stripped down cache just for monsters. - m.invalidate_map_cache( get_levz() ); m.build_map_cache( get_levz(), true ); monmove(); if( calendar::once_every( 3_minutes ) ) { @@ -1700,6 +1699,9 @@ int get_heat_radiation( const tripoint &location, bool direct ) fires.reserve( 13 * 13 ); int best_fire = 0; for( const tripoint &dest : g->m.points_in_radius( location, 6 ) ) { + if( !g->m.sees( location, dest, -1 ) ) { + continue; + } int heat_intensity = 0; int ffire = g->m.get_field_strength( dest, fd_fire ); @@ -1708,7 +1710,7 @@ int get_heat_radiation( const tripoint &location, bool direct ) } else if( g->m.tr_at( dest ).loadid == tr_lava ) { heat_intensity = 3; } - if( heat_intensity == 0 || !g->m.sees( location, dest, -1 ) ) { + if( heat_intensity == 0 ) { // No heat source here continue; } @@ -2197,17 +2199,18 @@ input_context get_default_mode_input_context() input_context ctxt( "DEFAULTMODE" ); // Because those keys move the character, they don't pan, as their original name says ctxt.set_iso( true ); - ctxt.register_action( "UP", _( "Move North" ) ); - ctxt.register_action( "RIGHTUP", _( "Move Northeast" ) ); - ctxt.register_action( "RIGHT", _( "Move East" ) ); - ctxt.register_action( "RIGHTDOWN", _( "Move Southeast" ) ); - ctxt.register_action( "DOWN", _( "Move South" ) ); - ctxt.register_action( "LEFTDOWN", _( "Move Southwest" ) ); - ctxt.register_action( "LEFT", _( "Move West" ) ); - ctxt.register_action( "LEFTUP", _( "Move Northwest" ) ); + ctxt.register_action( "UP", translate_marker( "Move North" ) ); + ctxt.register_action( "RIGHTUP", translate_marker( "Move Northeast" ) ); + ctxt.register_action( "RIGHT", translate_marker( "Move East" ) ); + ctxt.register_action( "RIGHTDOWN", translate_marker( "Move Southeast" ) ); + ctxt.register_action( "DOWN", translate_marker( "Move South" ) ); + ctxt.register_action( "LEFTDOWN", translate_marker( "Move Southwest" ) ); + ctxt.register_action( "LEFTDOWN", translate_marker( "Move Southwest" ) ); + ctxt.register_action( "LEFT", translate_marker( "Move West" ) ); + ctxt.register_action( "LEFTUP", translate_marker( "Move Northwest" ) ); ctxt.register_action( "pause" ); - ctxt.register_action( "LEVEL_DOWN", _( "Descend Stairs" ) ); - ctxt.register_action( "LEVEL_UP", _( "Ascend Stairs" ) ); + ctxt.register_action( "LEVEL_DOWN", translate_marker( "Descend Stairs" ) ); + ctxt.register_action( "LEVEL_UP", translate_marker( "Ascend Stairs" ) ); ctxt.register_action( "toggle_map_memory" ); ctxt.register_action( "center" ); ctxt.register_action( "shift_n" ); @@ -3148,7 +3151,6 @@ void game::draw() //temporary fix for updating visibility for minimap ter_view_z = ( u.pos() + u.view_offset ).z; - m.invalidate_map_cache( ter_view_z ); m.build_map_cache( ter_view_z ); m.update_visibility_cache( ter_view_z ); @@ -7113,10 +7115,10 @@ game::vmenu_ret game::list_items( const std::vector &item_list ) std::string action; input_context ctxt( "LIST_ITEMS" ); - ctxt.register_action( "UP", _( "Move cursor up" ) ); - ctxt.register_action( "DOWN", _( "Move cursor down" ) ); - ctxt.register_action( "LEFT", _( "Previous item" ) ); - ctxt.register_action( "RIGHT", _( "Next item" ) ); + ctxt.register_action( "UP", translate_marker( "Move cursor up" ) ); + ctxt.register_action( "DOWN", translate_marker( "Move cursor down" ) ); + ctxt.register_action( "LEFT", translate_marker( "Previous item" ) ); + ctxt.register_action( "RIGHT", translate_marker( "Next item" ) ); ctxt.register_action( "PAGE_DOWN" ); ctxt.register_action( "PAGE_UP" ); ctxt.register_action( "NEXT_TAB" ); @@ -7473,8 +7475,8 @@ game::vmenu_ret game::list_monsters( const std::vector &monster_list std::string action; input_context ctxt( "LIST_MONSTERS" ); - ctxt.register_action( "UP", _( "Move cursor up" ) ); - ctxt.register_action( "DOWN", _( "Move cursor down" ) ); + ctxt.register_action( "UP", translate_marker( "Move cursor up" ) ); + ctxt.register_action( "DOWN", translate_marker( "Move cursor down" ) ); ctxt.register_action( "NEXT_TAB" ); ctxt.register_action( "PREV_TAB" ); ctxt.register_action( "SAFEMODE_BLACKLIST_ADD" ); diff --git a/src/generic_factory.h b/src/generic_factory.h index f6b338871ca14..0588338ceda64 100644 --- a/src/generic_factory.h +++ b/src/generic_factory.h @@ -12,6 +12,7 @@ #include "catacharset.h" #include "debug.h" #include "enum_bitset.h" +#include "field_type.h" #include "init.h" #include "int_id.h" #include "json.h" diff --git a/src/handle_action.cpp b/src/handle_action.cpp index 4d3d94f7a4f1d..ea45b6c762593 100644 --- a/src/handle_action.cpp +++ b/src/handle_action.cpp @@ -66,7 +66,7 @@ #include "units.h" #include "string_id.h" -#define dbg(x) DebugLog((DebugLevel)(x),D_GAME) << __FILE__ << ":" << __LINE__ << ": " +#define dbg(x) DebugLog((x),D_GAME) << __FILE__ << ":" << __LINE__ << ": " const efftype_id effect_alarm_clock( "alarm_clock" ); const efftype_id effect_laserlocked( "laserlocked" ); @@ -1278,67 +1278,23 @@ static void cast_spell() } bool can_cast_spells = false; - std::vector spell_names; - { - uilist_entry dummy( _( "Spell" ) ); - dummy.ctxt = string_format( "%3s %3s %3s %5s %10s %4s %3s", _( "LVL" ), _( "XP%" ), _( "RNG" ), - _( "FAIL%" ), _( "Cast Time" ), _( "Cost" ), _( "DMG" ) ); - dummy.enabled = false; - dummy.text_color = c_light_blue; - dummy.force_color = true; - spell_names.emplace_back( dummy ); - } for( spell_id sp : spells ) { spell temp_spell = u.magic.get_spell( sp ); - std::string nm = temp_spell.name(); - uilist_entry entry( nm ); if( temp_spell.can_cast( u ) ) { can_cast_spells = true; - } else { - entry.enabled = false; } - std::string turns = temp_spell.casting_time() >= 100 ? string_format( _( "%i turns" ), - temp_spell.casting_time() / 100 ) : string_format( _( "%i moves" ), temp_spell.casting_time() ); - std::string cost = string_format( "%4i", temp_spell.energy_cost() ); - switch( temp_spell.energy_source() ) { - case mana_energy: - cost = colorize( cost, c_light_blue ); - break; - case stamina_energy: - cost = colorize( cost, c_green ); - break; - case hp_energy: - cost = colorize( cost, c_red ); - break; - case bionic_energy: - cost = colorize( cost, c_yellow ); - break; - case none_energy: - cost = colorize( _( "none" ), c_light_gray ); - break; - default: - debugmsg( "ERROR: %s has invalid energy_type", temp_spell.id().c_str() ); - break; - } - entry.ctxt = string_format( "%3i (%3s) %3i %3i %% %10s %4s %3i", temp_spell.get_level(), - temp_spell.is_max_level() ? _( "MAX" ) : temp_spell.exp_progress(), temp_spell.range(), - static_cast( round( 100.0f * temp_spell.spell_fail( u ) ) ), turns, cost, - temp_spell.damage() ); - spell_names.emplace_back( entry ); } if( !can_cast_spells ) { add_msg( m_bad, _( "You can't cast any of the spells you know!" ) ); } - // if there's only one spell we know, we still want to see its information - // the 0th "spell" is a header - int action = uilist( _( "Choose your spell:" ), spell_names ) - 1; - if( action < 0 ) { + const int spell_index = u.magic.select_spell( u ); + if( spell_index < 0 ) { return; } - spell sp = u.magic.get_spell( spells[action] ); + spell &sp = *u.magic.get_spells()[spell_index]; if( !u.magic.has_enough_energy( u, sp ) ) { add_msg( m_bad, _( "You don't have enough %s to cast the spell." ), sp.energy_string() ); @@ -1358,6 +1314,21 @@ static void cast_spell() // [2] this value overrides the mana cost if set to 0 cast_spell.values.emplace_back( 1 ); cast_spell.name = sp.id().c_str(); + if( u.magic.casting_ignore ) { + const std::vector ignored_distractions = { + distraction_type::noise, + distraction_type::pain, + distraction_type::attacked, + distraction_type::hostile_spotted, + distraction_type::talked_to, + distraction_type::asthma, + distraction_type::motion_alarm, + distraction_type::weather_change + }; + for( const distraction_type ignored : ignored_distractions ) { + cast_spell.ignore_distraction( ignored ); + } + } u.assign_activity( cast_spell, false ); } @@ -1951,7 +1922,6 @@ bool game::handle_action() add_msg( m_info, _( "You can't disassemble items while you're riding." ) ); } else { u.disassemble(); - g->m.invalidate_map_cache( g->get_levz() ); refresh_all(); } break; diff --git a/src/handle_liquid.cpp b/src/handle_liquid.cpp index c2fcf3db4e734..08fd92143d393 100644 --- a/src/handle_liquid.cpp +++ b/src/handle_liquid.cpp @@ -17,7 +17,7 @@ #include "vehicle.h" #include "vpart_position.h" -#define dbg(x) DebugLog((DebugLevel)(x),D_GAME) << __FILE__ << ":" << __LINE__ << ": " +#define dbg(x) DebugLog((x),D_GAME) << __FILE__ << ":" << __LINE__ << ": " // All serialize_liquid_source functions should add the same number of elements to the vectors of // the activity. This makes it easier to distinguish the values of the source and the values of the target. diff --git a/src/iexamine.cpp b/src/iexamine.cpp index 09dd9e4ec1f44..aebf648a6d239 100644 --- a/src/iexamine.cpp +++ b/src/iexamine.cpp @@ -3192,8 +3192,8 @@ const itype *furn_t::crafting_pseudo_item_type() const const itype *furn_t::crafting_ammo_item_type() const { const itype *pseudo = crafting_pseudo_item_type(); - if( pseudo->tool && !pseudo->tool->ammo_id.is_null() ) { - return item::find_type( pseudo->tool->ammo_id->default_ammotype() ); + if( pseudo->tool && !pseudo->tool->ammo_id.empty() ) { + return item::find_type( ammotype( *pseudo->tool->ammo_id.begin() )->default_ammotype() ); } return nullptr; } @@ -4102,7 +4102,7 @@ void iexamine::autodoc( player &p, const tripoint &examp ) const time_duration duration = itemtype->bionic->difficulty * 20_minutes; const float volume_anesth = itemtype->bionic->difficulty * 20 * 2; // 2ml/min - if( volume_anesth > drug_count && acomps.empty() ) { + if( needs_anesthesia && volume_anesth > drug_count && acomps.empty() ) { add_msg( m_bad, "You don't have enough anesthetic for this operation." ); return; } @@ -4164,7 +4164,7 @@ void iexamine::autodoc( player &p, const tripoint &examp ) const int difficulty = itemtype->bionic ? itemtype->bionic->difficulty : 12; const time_duration duration = difficulty * 20_minutes; const float volume_anesth = difficulty * 20 * 2; // 2ml/min - if( volume_anesth > drug_count && acomps.empty() ) { + if( needs_anesthesia && volume_anesth > drug_count && acomps.empty() ) { add_msg( m_bad, "You don't have enough anesthetic for this operation." ); return; } diff --git a/src/init.cpp b/src/init.cpp index 78e22ae25a2cf..bf1ecd8350ed3 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -25,6 +25,7 @@ #include "faction.h" #include "fault.h" #include "filesystem.h" +#include "field_type.h" #include "flag.h" #include "gates.h" #include "harvest.h" @@ -180,6 +181,7 @@ void DynamicDataLoader::initialize() add( "EXTERNAL_OPTION", &load_external_option ); add( "json_flag", &json_flag::load ); add( "fault", &fault::load_fault ); + add( "field_type", &field_types::load ); add( "emit", &emit::load_emit ); add( "activity_type", &activity_type::load ); add( "vitamin", &vitamin::load_vitamin ); @@ -444,6 +446,7 @@ void DynamicDataLoader::unload_data() json_flag::reset(); requirement_data::reset(); vitamin::reset(); + field_types::reset(); emit::reset(); activity_type::reset(); fault::reset(); @@ -519,6 +522,7 @@ void DynamicDataLoader::finalize_loaded_data( loading_ui &ui ) using named_entry = std::pair>; const std::vector entries = {{ { _( "Body parts" ), &body_part_struct::finalize_all }, + { _( "Field types" ), &field_types::finalize_all }, { _( "Items" ), []() { @@ -594,6 +598,7 @@ void DynamicDataLoader::check_consistency( loading_ui &ui ) } }, { _( "Vitamins" ), &vitamin::check_consistency }, + { _( "Field types" ), &field_types::check_consistency }, { _( "Emissions" ), &emit::check_consistency }, { _( "Activities" ), &activity_type::check_consistency }, { diff --git a/src/input.cpp b/src/input.cpp index e9e3d4ed72ee9..8f2392b7d620e 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -953,8 +953,8 @@ void input_context::display_menu() // Shamelessly stolen from help.cpp input_context ctxt( "HELP_KEYBINDINGS" ); - ctxt.register_action( "UP", _( "Scroll up" ) ); - ctxt.register_action( "DOWN", _( "Scroll down" ) ); + ctxt.register_action( "UP", translate_marker( "Scroll up" ) ); + ctxt.register_action( "DOWN", translate_marker( "Scroll down" ) ); ctxt.register_action( "PAGE_DOWN" ); ctxt.register_action( "PAGE_UP" ); ctxt.register_action( "REMOVE" ); @@ -1293,7 +1293,7 @@ const std::string input_context::get_action_name( const std::string &action_id ) const input_manager::t_string_string_map::const_iterator action_name_override = action_name_overrides.find( action_id ); if( action_name_override != action_name_overrides.end() ) { - return action_name_override->second; + return _( action_name_override->second ); } // 2) Check if the hotkey has a name diff --git a/src/inventory_ui.cpp b/src/inventory_ui.cpp index 4d51492fb8194..494f704633e47 100644 --- a/src/inventory_ui.cpp +++ b/src/inventory_ui.cpp @@ -94,7 +94,7 @@ class selection_column_preset: public inventory_selector_preset } else if( available_count != 1 ) { res << available_count << ' '; } - if( entry.location->ammo_type() == "money" ) { + if( entry.location->ammo_types().count( ammotype( "money" ) ) ) { if( entry.chosen_count > 0 && entry.chosen_count < available_count ) { //~ In the following string, the %s is the amount of money on the selected cards as passed by the display money function, out of the total amount of money on the cards, which is specified by the format_money function") res << string_format( _( "%s of %s" ), entry.location->display_money( entry.chosen_count, @@ -232,7 +232,7 @@ std::string inventory_selector_preset::get_caption( const inventory_entry &entry { const size_t count = entry.get_stack_size(); const std::string disp_name = - ( entry.location->ammo_type() == "money" ) ? + ( entry.location->ammo_types().count( ammotype( "money" ) ) ) ? entry.location->display_money( count, entry.location.charges_in_stack( count ) ) : entry.location->display_name( count ); @@ -1095,7 +1095,7 @@ void inventory_selector::add_character_items( Character &character ) } ); // Visitable interface does not support stacks so it has to be here for( const auto &elem : character.inv.slice() ) { - if( ( &elem->front() )->ammo_type() == "money" ) { + if( ( &elem->front() )->ammo_types().count( ammotype( "money" ) ) ) { add_item( own_inv_column, item_location( character, elem ), elem->size() ); } else { add_items( own_inv_column, [&character]( item * it ) { @@ -1566,18 +1566,18 @@ inventory_selector::inventory_selector( const player &u, const inventory_selecto , own_gear_column( preset ) , map_column( preset ) { - ctxt.register_action( "DOWN", _( "Next item" ) ); - ctxt.register_action( "UP", _( "Previous item" ) ); - ctxt.register_action( "RIGHT", _( "Next column" ) ); - ctxt.register_action( "LEFT", _( "Previous column" ) ); - ctxt.register_action( "CONFIRM", _( "Confirm your selection" ) ); - ctxt.register_action( "QUIT", _( "Cancel" ) ); - ctxt.register_action( "CATEGORY_SELECTION", _( "Switch selection mode" ) ); - ctxt.register_action( "TOGGLE_FAVORITE", _( "Toggle favorite" ) ); - ctxt.register_action( "NEXT_TAB", _( "Page down" ) ); - ctxt.register_action( "PREV_TAB", _( "Page up" ) ); - ctxt.register_action( "HOME", _( "Home" ) ); - ctxt.register_action( "END", _( "End" ) ); + ctxt.register_action( "DOWN", translate_marker( "Next item" ) ); + ctxt.register_action( "UP", translate_marker( "Previous item" ) ); + ctxt.register_action( "RIGHT", translate_marker( "Next column" ) ); + ctxt.register_action( "LEFT", translate_marker( "Previous column" ) ); + ctxt.register_action( "CONFIRM", translate_marker( "Confirm your selection" ) ); + ctxt.register_action( "QUIT", translate_marker( "Cancel" ) ); + ctxt.register_action( "CATEGORY_SELECTION", translate_marker( "Switch selection mode" ) ); + ctxt.register_action( "TOGGLE_FAVORITE", translate_marker( "Toggle favorite" ) ); + ctxt.register_action( "NEXT_TAB", translate_marker( "Page down" ) ); + ctxt.register_action( "PREV_TAB", translate_marker( "Page up" ) ); + ctxt.register_action( "HOME", translate_marker( "Home" ) ); + ctxt.register_action( "END", translate_marker( "End" ) ); ctxt.register_action( "HELP_KEYBINDINGS" ); ctxt.register_action( "ANY_INPUT" ); // For invlets ctxt.register_action( "INVENTORY_FILTER" ); @@ -1789,8 +1789,8 @@ inventory_multiselector::inventory_multiselector( const player &p, inventory_selector( p, preset ), selection_col( new selection_column( "SELECTION_COLUMN", selection_column_title ) ) { - ctxt.register_action( "RIGHT", _( "Mark/unmark selected item" ) ); - ctxt.register_action( "DROP_NON_FAVORITE", _( "Mark/unmark non-favorite items" ) ); + ctxt.register_action( "RIGHT", translate_marker( "Mark/unmark selected item" ) ); + ctxt.register_action( "DROP_NON_FAVORITE", translate_marker( "Mark/unmark non-favorite items" ) ); for( auto &elem : get_all_columns() ) { elem->set_multiselect( true ); diff --git a/src/item.cpp b/src/item.cpp index a2e71604dd11e..18643515a7595 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -203,8 +203,8 @@ item::item( const itype *type, time_point turn, int qty ) : type( type ), bday( last_rot_check = bday; } else if( type->tool ) { - if( ammo_remaining() && ammo_type() ) { - ammo_set( ammo_type()->default_ammotype(), ammo_remaining() ); + if( ammo_remaining() && !ammo_types().empty() ) { + ammo_set( ammo_default(), ammo_remaining() ); } } @@ -364,7 +364,7 @@ item &item::ammo_set( const itype_id &ammo, int qty ) } // handle reloadable tools and guns with no specific ammo type as special case - if( ( ammo == "null" && !ammo_type() ) || ammo_type().str() == "money" ) { + if( ( ammo == "null" && ammo_types().empty() ) || ammo_types().count( ammotype( "money" ) ) ) { if( ( is_tool() || is_gun() ) && magazine_integral() ) { curammo = nullptr; charges = std::min( qty, ammo_capacity() ); @@ -374,7 +374,7 @@ item &item::ammo_set( const itype_id &ammo, int qty ) // check ammo is valid for the item const itype *atype = item_controller->find_template( ammo ); - if( !atype->ammo || !atype->ammo->type.count( ammo_type() ) ) { + if( !atype->ammo || !ammo_types().count( atype->ammo->type ) ) { debugmsg( "Tried to set invalid ammo of %s for %s", atype->nname( qty ), tname() ); return *this; } @@ -403,7 +403,7 @@ item &item::ammo_set( const itype_id &ammo, int qty ) // if default magazine too small fetch instead closest available match if( mag->magazine->capacity < qty ) { // as above call to magazine_default successful can infer minimum one option exists - auto iter = type->magazines.find( ammo_type() ); + auto iter = type->magazines.find( ammotype( ammo ) ); std::vector opts( iter->second.begin(), iter->second.end() ); std::sort( opts.begin(), opts.end(), []( const itype_id & lhs, const itype_id & rhs ) { return find_type( lhs )->magazine->capacity < find_type( rhs )->magazine->capacity; @@ -626,7 +626,7 @@ bool item::stacks_with( const item &rhs, bool check_components ) const if( type != rhs.type ) { return false; } - if( ammo_type() == "money" && charges != 0 && rhs.charges != 0 ) { + if( charges != 0 && rhs.charges != 0 && ammo_current() == "money" ) { // Dealing with nonempty cash cards return true; } @@ -1304,11 +1304,13 @@ std::string item::info( std::vector &info, const iteminfo_query *parts if( is_magazine() && !has_flag( "NO_RELOAD" ) ) { if( parts->test( iteminfo_parts::MAGAZINE_CAPACITY ) ) { - auto fmt = string_format( - ngettext( " round of %s", " rounds of %s", ammo_capacity() ), - ammo_type()->name() ); - info.emplace_back( "MAGAZINE", _( "Capacity: " ), fmt, iteminfo::no_flags, - ammo_capacity() ); + for( const ammotype &at : ammo_types() ) { + auto fmt = string_format( + ngettext( " round of %s", " rounds of %s", ammo_capacity() ), + at->name() ); + info.emplace_back( "MAGAZINE", _( "Capacity: " ), fmt, iteminfo::no_flags, + ammo_capacity() ); + } } if( parts->test( iteminfo_parts::MAGAZINE_RELOAD ) ) { info.emplace_back( "MAGAZINE", _( "Reload time: " ), _( " per round" ), @@ -1322,11 +1324,7 @@ std::string item::info( std::vector &info, const iteminfo_query *parts if( ammo_remaining() > 0 ) { info.emplace_back( "AMMO", _( "Ammunition: " ), ammo_data()->nname( ammo_remaining() ) ); } else if( is_ammo() ) { - info.emplace_back( "AMMO", _( "Types: " ), - enumerate_as_string( type->ammo->type.begin(), type->ammo->type.end(), - []( const ammotype & e ) { - return e->name(); - }, enumeration_conjunction::none ) ); + info.emplace_back( "AMMO", _( "Type: " ), ammo_type()->name() ); } const auto &ammo = *ammo_data()->ammo; @@ -1400,8 +1398,8 @@ std::string item::info( std::vector &info, const iteminfo_query *parts item *aprox = nullptr; item tmp; if( mod->ammo_required() && !mod->ammo_remaining() ) { + tmp.ammo_set( mod->magazine_current() ? tmp.common_ammo_default() : tmp.ammo_default() ); tmp = *mod; - tmp.ammo_set( tmp.ammo_default() ); aprox = &tmp; } @@ -1428,14 +1426,22 @@ std::string item::info( std::vector &info, const iteminfo_query *parts mod->magazine_current()->tname() ) ); } if( mod->ammo_capacity() && parts->test( iteminfo_parts::GUN_CAPACITY ) ) { - auto fmt = string_format( - ngettext( " round of %s", " rounds of %s", mod->ammo_capacity() ), - mod->ammo_type()->name() ); - info.emplace_back( "GUN", _( "Capacity: " ), fmt, iteminfo::no_flags, - mod->ammo_capacity() ); + for( const ammotype &at : mod->ammo_types() ) { + if( mod->magazine_current() && mod->magazine_current()->type->magazine->type.count( at ) ) { + auto fmt = string_format( + ngettext( " round of %s", " rounds of %s", mod->ammo_capacity() ), + at->name() ); + info.emplace_back( "GUN", _( "Capacity: " ), fmt, iteminfo::no_flags, + mod->ammo_capacity() ); + } + } } } else if( parts->test( iteminfo_parts::GUN_TYPE ) ) { - info.emplace_back( "GUN", _( "Type: " ), mod->ammo_type()->name() ); + const std::set &atypes = mod->ammo_types(); + info.emplace_back( "GUN", _( "Type: " ), enumerate_as_string( atypes.begin(), + atypes.end(), []( const ammotype & at ) { + return at->name(); + }, enumeration_conjunction::none ) ); } if( mod->ammo_data() && parts->test( iteminfo_parts::AMMO_REMAINING ) ) { @@ -1715,9 +1721,11 @@ std::string item::info( std::vector &info, const iteminfo_query *parts info.emplace_back( "GUNMOD", _( "Handling modifier: " ), "", iteminfo::show_plus, mod.handling ); } - if( type->mod->ammo_modifier && parts->test( iteminfo_parts::GUNMOD_AMMO ) ) { - info.push_back( iteminfo( "GUNMOD", string_format( _( "Ammo: %s" ), - type->mod->ammo_modifier->name() ) ) ); + if( !type->mod->ammo_modifier.empty() && parts->test( iteminfo_parts::GUNMOD_AMMO ) ) { + for( const ammotype &at : type->mod->ammo_modifier ) { + info.push_back( iteminfo( "GUNMOD", string_format( _( "Ammo: %s" ), + at->name() ) ) ); + } } if( mod.reload_modifier != 0 && parts->test( iteminfo_parts::GUNMOD_RELOAD ) ) { info.emplace_back( "GUNMOD", _( "Reload modifier: " ), _( "%" ), @@ -2109,10 +2117,15 @@ std::string item::info( std::vector &info, const iteminfo_query *parts } else if( ammo_capacity() != 0 && parts->test( iteminfo_parts::TOOL_CAPACITY ) ) { std::string tmp; bool bionic_tool = has_flag( "USES_BIONIC_POWER" ); - if( ammo_type() ) { + if( !ammo_types().empty() ) { //~ "%s" is ammunition type. This types can't be plural. tmp = ngettext( "Maximum charge of %s.", "Maximum charges of %s.", ammo_capacity() ); - tmp = string_format( tmp, ammo_type()->name() ); + const std::set atypes = ammo_types(); + tmp = string_format( tmp, enumerate_as_string( atypes.begin(), + atypes.end(), []( const ammotype & at ) { + return at->name(); + }, enumeration_conjunction::none ) ); + // No need to display max charges, since charges are always equal to bionic power } else if( !bionic_tool ) { tmp = ngettext( "Maximum charge.", "Maximum charges.", ammo_capacity() ); @@ -2877,25 +2890,28 @@ nc_color item::color_in_inventory() const // Guns are green if you are carrying ammo for them // ltred if you have ammo but no mags // Gun with integrated mag counts as both - ammotype amtype = ammo_type(); - // get_ammo finds uncontained ammo, find_ammo finds ammo in magazines - bool has_ammo = !u.get_ammo( amtype ).empty() || !u.find_ammo( *this, false, -1 ).empty(); - bool has_mag = magazine_integral() || !u.find_ammo( *this, true, -1 ).empty(); - if( has_ammo && has_mag ) { - ret = c_green; - } else if( has_ammo || has_mag ) { - ret = c_light_red; + for( const ammotype &at : ammo_types() ) { + // get_ammo finds uncontained ammo, find_ammo finds ammo in magazines + bool has_ammo = !u.get_ammo( at ).empty() || !u.find_ammo( *this, false, -1 ).empty(); + bool has_mag = magazine_integral() || !u.find_ammo( *this, true, -1 ).empty(); + if( has_ammo && has_mag ) { + ret = c_green; + break; + } else if( has_ammo || has_mag ) { + ret = c_light_red; + break; + } } } else if( is_ammo() ) { // Likewise, ammo is green if you have guns that use it // ltred if you have the gun but no mags // Gun with integrated mag counts as both bool has_gun = u.has_item_with( [this]( const item & i ) { - return i.is_gun() && type->ammo->type.count( i.ammo_type() ); + return i.is_gun() && i.ammo_types().count( ammo_type() ); } ); bool has_mag = u.has_item_with( [this]( const item & i ) { - return ( i.is_gun() && i.magazine_integral() && type->ammo->type.count( i.ammo_type() ) ) || - ( i.is_magazine() && type->ammo->type.count( i.ammo_type() ) ); + return ( i.is_gun() && i.magazine_integral() && i.ammo_types().count( ammo_type() ) ) || + ( i.is_magazine() && i.ammo_types().count( ammo_type() ) ); } ); if( has_gun && has_mag ) { ret = c_green; @@ -3383,7 +3399,7 @@ std::string item::display_name( unsigned int quantity ) const } if( amount || show_amt ) { - if( ammo_type().str() == "money" ) { + if( ammo_current() == "money" ) { amt = string_format( " $%.2f", amount / 100.0 ); } else { if( max_amount != 0 ) { @@ -3441,7 +3457,7 @@ int item::price( bool practical ) const // items with integral magazines may contain ammunition which can affect the price child += item( e->ammo_data(), calendar::turn, e->charges ).price( practical ); - } else if( e->is_tool() && !e->ammo_type() && e->ammo_capacity() ) { + } else if( e->is_tool() && e->ammo_types().empty() && e->ammo_capacity() ) { // if tool has no ammo (e.g. spray can) reduce price proportional to remaining charges child *= e->ammo_remaining() / static_cast( std::max( e->type->charges_default(), 1 ) ); } @@ -3504,8 +3520,9 @@ units::mass item::weight( bool include_contents, bool integral ) const } } else if( magazine_integral() && !is_magazine() ) { - if( ammo_type() == ammotype( "plutonium" ) ) { - ret += ammo_remaining() * find_type( ammo_type()->default_ammotype() )->weight / PLUTONIUM_CHARGES; + if( ammo_current() == "plutonium" ) { + ret += ammo_remaining() * find_type( ammotype( + *ammo_types().begin() )->default_ammotype() )->weight / PLUTONIUM_CHARGES; } else if( ammo_data() ) { ret += ammo_remaining() * ammo_data()->weight; } @@ -3862,10 +3879,20 @@ int item::get_quality( const quality_id &id ) const ( is_tool() && std::all_of( contents.begin(), contents.end(), [this]( const item & itm ) { if( itm.is_ammo() ) { - auto &ammo_types = itm.type->ammo->type; - return ammo_types.find( ammo_type() ) != ammo_types.end(); + if( ammo_types().count( itm.ammo_type() ) ) { + return true; + } else { + return false; + } } else if( itm.is_magazine() ) { - return itm.ammo_type() == ammo_type(); + for( const ammotype &at : ammo_types() ) { + for( const ammotype &mag_at : itm.ammo_types() ) { + if( at == mag_at ) { + return true; + } + } + } + return false; } else if( itm.is_toolmod() ) { return true; } @@ -4386,7 +4413,7 @@ bool item::craft_has_charges() { if( count_by_charges() ) { return true; - } else if( !ammo_type() ) { + } else if( ammo_types().empty() ) { return true; } @@ -5242,7 +5269,7 @@ bool item::is_reloadable_helper( const itype_id &ammo, bool now ) const } } else { auto at = find_type( ammo ); - if( ( !at->ammo || !at->ammo->type.count( ammo_type() ) ) && + if( ( !at->ammo || !ammo_types().count( at->ammo->type ) ) && !magazine_compatible().count( ammo ) ) { return false; } @@ -5480,7 +5507,7 @@ bool item::operator<( const item &other ) const !other.contents.empty() ? &other.contents.front() : &other; if( me->typeId() == rhs->typeId() ) { - if( me->ammo_type() == ammotype( "money" ) ) { + if( me->ammo_current() == "money" ) { return me->charges > rhs->charges; } return me->charges < rhs->charges; @@ -5511,7 +5538,7 @@ gun_type_type item::gun_type() const // TODO: move to JSON and remove extraction of this from "GUN" (via skill id) //and from "GUNMOD" (via "mod_targets") in lang/extract_json_strings.py if( gun_skill() == skill_archery ) { - if( ammo_type() == ammotype( "bolt" ) || typeId() == "bullet_crossbow" ) { + if( ammo_types().count( ammotype( "bolt" ) ) || typeId() == "bullet_crossbow" ) { return gun_type_type( translate_marker_context( "gun_type_type", "crossbow" ) ); } else { return gun_type_type( translate_marker_context( "gun_type_type", "bow" ) ); @@ -5773,7 +5800,7 @@ int item::ammo_required() const } if( is_gun() ) { - if( !ammo_type() ) { + if( ammo_types().empty() ) { return 0; } else if( has_flag( "FIRE_100" ) ) { return 100; @@ -5867,9 +5894,9 @@ const itype *item::ammo_data() const auto mods = is_gun() ? gunmods() : toolmods(); for( const auto e : mods ) { - if( e->type->mod->ammo_modifier && - item_controller->has_template( itype_id( e->type->mod->ammo_modifier.str() ) ) ) { - return item_controller->find_template( itype_id( e->type->mod->ammo_modifier.str() ) ); + if( !e->type->mod->ammo_modifier.empty() && + item_controller->has_template( e->ammo_current() ) ) { + return item_controller->find_template( e->ammo_current() ); } } @@ -5882,12 +5909,12 @@ itype_id item::ammo_current() const return ammo ? ammo->get_id() : "null"; } -ammotype item::ammo_type( bool conversion ) const +std::set item::ammo_types( bool conversion ) const { if( conversion ) { auto mods = is_gun() ? gunmods() : toolmods(); for( const auto e : mods ) { - if( e->type->mod->ammo_modifier ) { + if( !e->type->mod->ammo_modifier.empty() ) { return e->type->mod->ammo_modifier; } } @@ -5900,13 +5927,44 @@ ammotype item::ammo_type( bool conversion ) const } else if( is_magazine() ) { return type->magazine->type; } + return {}; +} + +ammotype item::ammo_type() const +{ + if( is_ammo() ) { + return type->ammo->type; + } return ammotype::NULL_ID(); } itype_id item::ammo_default( bool conversion ) const { - auto res = ammo_type( conversion )->default_ammotype(); - return !res.empty() ? res : "NULL"; + std::set atypes = ammo_types( conversion ); + if( !atypes.empty() ) { + itype_id res = ammotype( *atypes.begin() )->default_ammotype(); + if( !res.empty() ) { + return res; + } + } + return "NULL"; +} + +itype_id item::common_ammo_default( bool conversion ) const +{ + std::set atypes = ammo_types( conversion ); + if( !atypes.empty() ) { + for( const ammotype &at : atypes ) { + const item *mag = magazine_current(); + if( mag && mag->type->magazine->type.count( at ) ) { + itype_id res = at->default_ammotype(); + if( !res.empty() ) { + return res; + } + } + } + } + return "NULL"; } std::set item::ammo_effects( bool with_ammo ) const @@ -5930,15 +5988,10 @@ std::set item::ammo_effects( bool with_ammo ) const std::string item::ammo_sort_name() const { if( is_magazine() || is_gun() || is_tool() ) { - return ammo_type()->name(); + return ammotype( *ammo_types().begin() )->name(); } if( is_ammo() ) { - return enumerate_as_string( type->ammo->type.begin(), - type->ammo->type.end(), - []( const ammotype & e ) { - return e->name(); - }, - enumeration_conjunction::none ); + return ammo_type()->name(); } return ""; } @@ -5958,23 +6011,40 @@ bool item::magazine_integral() const itype_id item::magazine_default( bool conversion ) const { - auto mag = type->magazine_default.find( ammo_type( conversion ) ); - return mag != type->magazine_default.end() ? mag->second : "null"; + std::set atypes = ammo_types( conversion ); + if( !atypes.empty() ) { + auto mag = type->magazine_default.find( ammotype( *atypes.begin() ) ); + if( mag != type->magazine_default.end() ) { + return mag->second; + } + } + return "null"; } std::set item::magazine_compatible( bool conversion ) const { + std::set mags = {}; // mods that define magazine_adaptor may override the items usual magazines auto mods = is_gun() ? gunmods() : toolmods(); for( const auto m : mods ) { if( !m->type->mod->magazine_adaptor.empty() ) { - auto mags = m->type->mod->magazine_adaptor.find( ammo_type( conversion ) ); - return mags != m->type->mod->magazine_adaptor.end() ? mags->second : std::set(); + for( const ammotype &atype : ammo_types( conversion ) ) { + if( m->type->mod->magazine_adaptor.count( atype ) ) { + std::set magazines_for_atype = m->type->mod->magazine_adaptor.find( atype )->second; + mags.insert( magazines_for_atype.begin(), magazines_for_atype.end() ); + } + } + return mags; } } - auto mags = type->magazines.find( ammo_type( conversion ) ); - return mags != type->magazines.end() ? mags->second : std::set(); + for( const ammotype &atype : ammo_types( conversion ) ) { + if( type->magazines.count( atype ) ) { + std::set magazines_for_atype = type->magazines.find( atype )->second; + mags.insert( magazines_for_atype.begin(), magazines_for_atype.end() ); + } + } + return mags; } item *item::magazine_current() @@ -6067,12 +6137,17 @@ ret_val item::is_gunmod_compatible( const item &mod ) const !mod.has_flag( "PUMP_RAIL_COMPATIBLE" ) && has_flag( "PUMP_ACTION" ) ) { return ret_val::make_failure( _( "can only accept small mods on that slot" ) ); - } else if( !mod.type->mod->acceptable_ammo.empty() && - !mod.type->mod->acceptable_ammo.count( ammo_type( false ) ) ) { - //~ %1$s - name of the gunmod, %2$s - name of the ammo - return ret_val::make_failure( _( "%1$s cannot be used on %2$s" ), mod.tname( 1 ), - ammo_type( false )->name() ); - + } else if( !mod.type->mod->acceptable_ammo.empty() ) { + bool compat_ammo = false; + for( const ammotype &at : mod.type->mod->acceptable_ammo ) { + if( ammo_types( false ).count( at ) ) { + compat_ammo = true; + } + } + if( compat_ammo ) { + return ret_val::make_failure( + _( "%1$s cannot be used on item with no compatible ammo types" ), mod.tname( 1 ) ); + } } else if( mod.typeId() == "waterproof_gunmod" && has_flag( "WATERPROOF_GUN" ) ) { return ret_val::make_failure( _( "is already waterproof" ) ); @@ -6082,7 +6157,7 @@ ret_val item::is_gunmod_compatible( const item &mod ) const } else if( mod.typeId() == "brass_catcher" && has_flag( "RELOAD_EJECT" ) ) { return ret_val::make_failure( _( "cannot have a brass catcher" ) ); - } else if( ( mod.type->mod->ammo_modifier || !mod.type->mod->magazine_adaptor.empty() ) + } else if( ( mod.type->mod->ammo_modifier.empty() || !mod.type->mod->magazine_adaptor.empty() ) && ( ammo_remaining() > 0 || magazine_current() ) ) { return ret_val::make_failure( _( "must be unloaded before installing this mod" ) ); } @@ -6316,9 +6391,11 @@ void item::reload_option::qty( int val ) if( target->has_flag( "RELOAD_ONE" ) && !ammo->has_flag( "SPEEDLOADER" ) ) { remaining_capacity = 1; } - if( target->ammo_type() == ammotype( "plutonium" ) ) { - remaining_capacity = remaining_capacity / PLUTONIUM_CHARGES + - ( remaining_capacity % PLUTONIUM_CHARGES != 0 ); + if( ammo_obj.type->ammo ) { + if( ammo_obj.ammo_type() == ammotype( "plutonium" ) ) { + remaining_capacity = remaining_capacity / PLUTONIUM_CHARGES + + ( remaining_capacity % PLUTONIUM_CHARGES != 0 ); + } } bool ammo_by_charges = ammo_obj.is_ammo() || ammo_in_liquid_container; @@ -6391,7 +6468,7 @@ bool item::reload( player &u, item_location loc, int qty ) ? get_remaining_capacity_for_liquid( *ammo ) : ammo_capacity() - ammo_remaining(); - if( ammo_type() == ammotype( "plutonium" ) ) { + if( ammo->ammo_type() == ammotype( "plutonium" ) ) { limit = limit / PLUTONIUM_CHARGES + ( limit % PLUTONIUM_CHARGES != 0 ); } @@ -6453,7 +6530,7 @@ bool item::reload( player &u, item_location loc, int qty ) qty = std::min( qty, ammo->ammo_remaining() ); ammo->ammo_consume( qty, tripoint_zero ); charges += qty; - } else if( ammo_type() == ammotype( "plutonium" ) ) { + } else if( ammo->ammo_type() == "plutonium" ) { curammo = find_type( ammo->typeId() ); ammo->charges -= qty; @@ -6773,7 +6850,7 @@ bool item::allow_crafting_component() const } // vehicle batteries are implemented as magazines of charge - if( is_magazine() && ammo_type() == ammotype( "battery" ) ) { + if( is_magazine() && ammo_types().count( ammotype( "battery" ) ) ) { return true; } @@ -6978,8 +7055,8 @@ void item::set_countdown( int num_turns ) debugmsg( "Tried to set a negative countdown value %d.", num_turns ); return; } - if( ammo_type() ) { - debugmsg( "Tried to set countdown on an item with ammo=%s.", ammo_type().c_str() ); + if( !ammo_types().empty() ) { + debugmsg( "Tried to set countdown on an item with ammo." ); return; } charges = num_turns; @@ -8227,7 +8304,7 @@ bool item::is_reloadable() const } else if( !is_gun() && !is_tool() && !is_magazine() ) { return false; - } else if( !ammo_type() ) { + } else if( ammo_types().empty() ) { return false; } diff --git a/src/item.h b/src/item.h index 99615cd79a724..02c8cdf6a3fe4 100644 --- a/src/item.h +++ b/src/item.h @@ -1583,16 +1583,25 @@ class item : public visitable const itype *ammo_data() const; /** Specific ammo type, returns "null" if item is neither ammo nor loaded with any */ itype_id ammo_current() const; - /** Ammo type (@ref ammunition_type) used by item + /** Set of ammo types (@ref ammunition_type) used by item * @param conversion whether to include the effect of any flags or mods which convert the type - * @return NULL if item does not use a specific ammo type (and is consequently not reloadable) */ - ammotype ammo_type( bool conversion = true ) const; + * @return empty set if item does not use a specific ammo type (and is consequently not reloadable) */ + std::set ammo_types( bool conversion = true ) const; + + /** Ammo type of an ammo item + * @return ammotype of ammo item or a null id if the item is not ammo */ + ammotype ammo_type() const; /** Get default ammo used by item or "NULL" if item does not have a default ammo type * @param conversion whether to include the effect of any flags or mods which convert the type * @return NULL if item does not use a specific ammo type (and is consequently not reloadable) */ itype_id ammo_default( bool conversion = true ) const; + /** Get default ammo for the first ammotype common to an item and its current magazine or "NULL" if none exists + * @param conversion whether to include the effect of any flags or mods which convert the type + * @return itype_id of default ammo for the first ammotype common to an item and its current magazine or "NULL" if none exists */ + itype_id common_ammo_default( bool conversion = true ) const; + /** Get ammo effects for item optionally inclusive of any resulting from the loaded ammo */ std::set ammo_effects( bool with_ammo = true ) const; diff --git a/src/item_factory.cpp b/src/item_factory.cpp index ad1fc44451fdd..ede6604ad1f6d 100644 --- a/src/item_factory.cpp +++ b/src/item_factory.cpp @@ -253,7 +253,7 @@ void Item_factory::finalize_pre( itype &obj ) } // for magazines ensure default_ammo is set if( obj.magazine && obj.magazine->default_ammo == "NULL" ) { - obj.magazine->default_ammo = obj.magazine->type->default_ammotype(); + obj.magazine->default_ammo = ammotype( *obj.magazine->type.begin() )->default_ammotype(); } if( obj.gun ) { handle_legacy_ranged( *obj.gun ); @@ -811,8 +811,8 @@ bool Item_factory::check_ammo_type( std::ostream &msg, const ammotype &ammo ) co if( std::none_of( m_templates.begin(), m_templates.end(), [&ammo]( const decltype( m_templates )::value_type & e ) { - return e.second.ammo && e.second.ammo->type.count( ammo ); - } ) ) { + return e.second.ammo && e.second.ammo->type == ammo; +} ) ) { msg << string_format( "there is no actual ammo of type %s defined", ammo.c_str() ) << "\n"; return false; } @@ -934,12 +934,10 @@ void Item_factory::check_definitions() const } } if( type->ammo ) { - if( type->ammo->type.empty() ) { + if( !type->ammo->type && type->ammo->type != ammotype( "NULL" ) ) { msg << "must define at least one ammo type" << "\n"; } - for( const auto &e : type->ammo->type ) { - check_ammo_type( msg, e ); - } + check_ammo_type( msg, type->ammo->type ); if( type->ammo->casing && ( !has_template( *type->ammo->casing ) || *type->ammo->casing == "null" ) ) { msg << string_format( "invalid casing property %s", type->ammo->casing->c_str() ) << "\n"; @@ -949,9 +947,10 @@ void Item_factory::check_definitions() const } } if( type->gun ) { - check_ammo_type( msg, type->gun->ammo ); - - if( !type->gun->ammo ) { + for( const ammotype &at : type->gun->ammo ) { + check_ammo_type( msg, at ); + } + if( type->gun->ammo.empty() ) { // if gun doesn't use ammo forbid both integral or detachable magazines if( static_cast( type->gun->clip ) || !type->magazines.empty() ) { msg << "cannot specify clip_size or magazine without ammo type" << "\n"; @@ -965,9 +964,10 @@ void Item_factory::check_definitions() const if( type->item_tags.count( "RELOAD_AND_SHOOT" ) && !type->magazines.empty() ) { msg << "RELOAD_AND_SHOOT cannot be used with magazines" << "\n"; } - - if( !type->magazines.empty() && !type->magazine_default.count( type->gun->ammo ) ) { - msg << "specified magazine but none provided for default ammo type" << "\n"; + for( const ammotype &at : type->gun->ammo ) { + if( !type->gun->clip && !type->magazines.empty() && !type->magazine_default.count( at ) ) { + msg << "specified magazine but none provided for ammo type " << at.str() << "\n"; + } } } if( type->gun->barrel_length < 0_ml ) { @@ -999,7 +999,9 @@ void Item_factory::check_definitions() const } } if( type->mod ) { - check_ammo_type( msg, type->mod->ammo_modifier ); + for( const ammotype &at : type->mod->ammo_modifier ) { + check_ammo_type( msg, at ); + } for( const auto &e : type->mod->acceptable_ammo ) { check_ammo_type( msg, e ); @@ -1012,15 +1014,17 @@ void Item_factory::check_definitions() const } for( const itype_id &opt : e.second ) { const itype *mag = find_template( opt ); - if( !mag->magazine || mag->magazine->type != e.first ) { + if( !mag->magazine || !mag->magazine->type.count( e.first ) ) { msg << "invalid magazine " << opt << " in magazine adapter\n"; } } } } if( type->magazine ) { - check_ammo_type( msg, type->magazine->type ); - if( !type->magazine->type ) { + for( const ammotype &at : type->magazine->type ) { + check_ammo_type( msg, at ); + } + if( type->magazine->type.empty() ) { msg << "magazine did not specify ammo type" << "\n"; } if( type->magazine->capacity < 0 ) { @@ -1030,7 +1034,7 @@ void Item_factory::check_definitions() const msg << string_format( "invalid count %i", type->magazine->count ) << "\n"; } const itype *da = find_template( type->magazine->default_ammo ); - if( !( da->ammo && da->ammo->type.count( type->magazine->type ) ) ) { + if( !( da->ammo && type->magazine->type.count( da->ammo->type ) ) ) { msg << string_format( "invalid default_ammo %s", type->magazine->default_ammo.c_str() ) << "\n"; } if( type->magazine->reliability < 0 || type->magazine->reliability > 100 ) { @@ -1058,10 +1062,8 @@ void Item_factory::check_definitions() const } else if( !mag_ptr->magazine ) { msg << "Magazine \"" << magazine << "\" specified for \"" << ammo_variety.first.str() << "\" is not a magazine\n"; - } else if( mag_ptr->magazine->type != ammo_variety.first ) { - msg << "magazine \"" << magazine << "\" holds incompatible ammo (\"" - << mag_ptr->magazine->type.str() << "\" instead of \"" - << ammo_variety.first.str() << "\")\n"; + } else if( !mag_ptr->magazine->type.count( ammo_variety.first ) ) { + msg << "magazine \"" << magazine << "\" does not take compatible ammo \n"; } else if( mag_ptr->item_tags.count( "SPEEDLOADER" ) && mag_ptr->magazine->capacity != type->gun->clip ) { msg << "Speedloader " << magazine << " capacity (" @@ -1072,7 +1074,9 @@ void Item_factory::check_definitions() const } if( type->tool ) { - check_ammo_type( msg, type->tool->ammo_id ); + for( const ammotype &at : type->tool->ammo_id ) { + check_ammo_type( msg, at ); + } if( type->tool->revert_to && ( !has_template( *type->tool->revert_to ) || *type->tool->revert_to == "null" ) ) { msg << string_format( "invalid revert_to property %s", type->tool->revert_to->c_str() ) << "\n"; @@ -1152,13 +1156,14 @@ const itype *Item_factory::find_template( const itype_id &id ) const } //If we didn't find the item maybe it is a building instead! - if( oter_str_id( id.c_str() ).is_valid() ) { + const recipe_id &making_id = recipe_id( id.c_str() ); + if( oter_str_id( id.c_str() ).is_valid() || + ( making_id.is_valid() && making_id.obj().is_blueprint() ) ) { itype *def = new itype(); def->id = id; def->name = string_format( "DEBUG: %s", id.c_str() ); def->name_plural = string_format( "%s", id.c_str() ); - const recipe *making = &recipe_id( id.c_str() ).obj(); - def->description = string_format( making->description ); + def->description = string_format( making_id.obj().description ); m_runtimes[ id ].reset( def ); return def; } @@ -1387,7 +1392,16 @@ void Item_factory::load( islot_gun &slot, JsonObject &jo, const std::string &src } assign( jo, "skill", slot.skill_used, strict ); - assign( jo, "ammo", slot.ammo, strict ); + if( jo.has_array( "ammo" ) ) { + slot.ammo.clear(); + JsonArray atypes = jo.get_array( "ammo" ); + for( size_t i = 0; i < atypes.size(); ++i ) { + slot.ammo.insert( ammotype( atypes.get_string( i ) ) ); + } + } else if( jo.has_string( "ammo" ) ) { + slot.ammo.clear(); + slot.ammo.insert( ammotype( jo.get_string( "ammo" ) ) ); + } assign( jo, "range", slot.range, strict ); if( jo.has_object( "ranged_damage" ) ) { assign( jo, "ranged_damage", slot.damage, strict ); @@ -1486,7 +1500,14 @@ void Item_factory::load( islot_tool &slot, JsonObject &jo, const std::string &sr { bool strict = src == "dda"; - assign( jo, "ammo", slot.ammo_id, strict ); + if( jo.has_array( "ammo" ) ) { + JsonArray atypes = jo.get_array( "ammo" ); + for( size_t i = 0; i < atypes.size(); ++i ) { + slot.ammo_id.insert( ammotype( atypes.get_string( i ) ) ); + } + } else if( jo.has_string( "ammo" ) ) { + slot.ammo_id.insert( ammotype( jo.get_string( "ammo" ) ) ); + } assign( jo, "max_charges", slot.max_charges, strict, 0 ); assign( jo, "initial_charges", slot.def_charges, strict, 0 ); assign( jo, "charges_per_use", slot.charges_per_use, strict, 0 ); @@ -1524,7 +1545,14 @@ void Item_factory::load( islot_mod &slot, JsonObject &jo, const std::string &src { bool strict = src == "dda"; - assign( jo, "ammo_modifier", slot.ammo_modifier, strict ); + if( jo.has_array( "ammo_modifier" ) ) { + JsonArray atypes = jo.get_array( "ammo_modifier" ); + for( size_t i = 0; i < atypes.size(); ++i ) { + slot.ammo_modifier.insert( ammotype( atypes.get_string( i ) ) ); + } + } else if( jo.has_string( "ammo_modifier" ) ) { + slot.ammo_modifier.insert( ammotype( jo.get_string( "ammo_modifier" ) ) ); + } assign( jo, "capacity_multiplier", slot.capacity_multiplier, strict ); if( jo.has_member( "acceptable_ammo" ) ) { @@ -1803,8 +1831,14 @@ void Item_factory::load_gunmod( JsonObject &jo, const std::string &src ) void Item_factory::load( islot_magazine &slot, JsonObject &jo, const std::string &src ) { bool strict = src == "dda"; - - assign( jo, "ammo_type", slot.type, strict ); + if( jo.has_array( "ammo_type" ) ) { + JsonArray atypes = jo.get_array( "ammo_type" ); + for( size_t i = 0; i < atypes.size(); ++i ) { + slot.type.insert( ammotype( atypes.get_string( i ) ) ); + } + } else if( jo.has_string( "ammo_type" ) ) { + slot.type.insert( ammotype( jo.get_string( "ammo_type" ) ) ); + } assign( jo, "capacity", slot.capacity, strict, 0 ); assign( jo, "count", slot.count, strict, 0 ); assign( jo, "default_ammo", slot.default_ammo, strict ); @@ -2014,19 +2048,20 @@ void Item_factory::load_basic_info( JsonObject &jo, itype &def, const std::strin if( jo.has_array( "magazines" ) ) { def.magazine_default.clear(); def.magazines.clear(); - } - JsonArray mags = jo.get_array( "magazines" ); - while( mags.has_more() ) { - JsonArray arr = mags.next_array(); - ammotype ammo( arr.get_string( 0 ) ); // an ammo type (e.g. 9mm) - JsonArray compat = arr.get_array( 1 ); // compatible magazines for this ammo type + JsonArray mags = jo.get_array( "magazines" ); + while( mags.has_more() ) { + JsonArray arr = mags.next_array(); - // the first magazine for this ammo type is the default; - def.magazine_default[ ammo ] = compat.get_string( 0 ); + ammotype ammo( arr.get_string( 0 ) ); // an ammo type (e.g. 9mm) + JsonArray compat = arr.get_array( 1 ); // compatible magazines for this ammo type - while( compat.has_more() ) { - def.magazines[ ammo ].insert( compat.next_string() ); + // the first magazine for this ammo type is the default + def.magazine_default[ ammo ] = compat.get_string( 0 ); + + while( compat.has_more() ) { + def.magazines[ ammo ].insert( compat.next_string() ); + } } } diff --git a/src/item_group.cpp b/src/item_group.cpp index 377bbb71fe74d..bc41b350414db 100644 --- a/src/item_group.cpp +++ b/src/item_group.cpp @@ -223,8 +223,8 @@ void Item_modifier::modify( item &new_item ) const } else if( new_item.is_tool() ) { const int qty = std::min( ch, new_item.ammo_capacity() ); new_item.charges = qty; - if( new_item.ammo_type() && qty > 0 ) { - new_item.ammo_set( new_item.ammo_type()->default_ammotype(), qty ); + if( !new_item.ammo_types().empty() && qty > 0 ) { + new_item.ammo_set( new_item.ammo_default(), qty ); } } else if( !new_item.is_gun() ) { //not gun, food, ammo or tool. @@ -235,8 +235,8 @@ void Item_modifier::modify( item &new_item ) const if( ch > 0 && ( new_item.is_gun() || new_item.is_magazine() ) ) { if( ammo == nullptr ) { // In case there is no explicit ammo item defined, use the default ammo - if( new_item.ammo_type() ) { - new_item.ammo_set( new_item.ammo_type()->default_ammotype(), ch ); + if( !new_item.ammo_types().empty() ) { + new_item.ammo_set( new_item.ammo_default(), ch ); } } else { const item am = ammo->create_single( new_item.birthday() ); @@ -265,7 +265,7 @@ void Item_modifier::modify( item &new_item ) const const item am = ammo->create_single( new_item.birthday() ); new_item.ammo_set( am.typeId() ); } else { - new_item.ammo_set( new_item.ammo_type()->default_ammotype() ); + new_item.ammo_set( new_item.ammo_default() ); } } } diff --git a/src/itype.h b/src/itype.h index 854d469083f48..be3a2e0557f19 100644 --- a/src/itype.h +++ b/src/itype.h @@ -89,7 +89,7 @@ class gunmod_location }; struct islot_tool { - ammotype ammo_id = ammotype::NULL_ID(); + std::set ammo_id = {}; cata::optional revert_to; std::string revert_msg; @@ -354,7 +354,7 @@ struct islot_mod { std::set acceptable_ammo; /** If set modifies parent ammo to this type */ - ammotype ammo_modifier = ammotype::NULL_ID(); + std::set ammo_modifier = {}; /** If non-empty replaces the compatible magazines for the parent item */ std::map< ammotype, std::set > magazine_adaptor; @@ -439,7 +439,7 @@ struct islot_gun : common_ranged_data { /** * What type of ammo this gun uses. */ - ammotype ammo = ammotype::NULL_ID(); + std::set ammo = {}; /** * Gun durability, affects gun being damaged during shooting. */ @@ -583,7 +583,7 @@ struct islot_gunmod : common_ranged_data { struct islot_magazine { /** What type of ammo this magazine can be loaded with */ - ammotype type = ammotype::NULL_ID(); + std::set type = {}; /** Capacity of magazine (in equivalent units to ammo charges) */ int capacity = 0; @@ -614,7 +614,7 @@ struct islot_ammo : common_ranged_data { /** * Ammo type, basically the "form" of the ammo that fits into the gun/tool. */ - std::set type; + ammotype type; /** * Type id of casings, if any. */ diff --git a/src/iuse.cpp b/src/iuse.cpp index a7def13f9d52a..359044978a986 100644 --- a/src/iuse.cpp +++ b/src/iuse.cpp @@ -5668,7 +5668,10 @@ int iuse::toolmod_attach( player *p, item *it, bool, const tripoint & ) // can only attach to unmodified tools that use compatible ammo return e.is_tool() && e.toolmods().empty() && !e.magazine_current() && - it->type->mod->acceptable_ammo.count( e.ammo_type( false ) ); + std::any_of( it->type->mod->acceptable_ammo.begin(), + it->type->mod->acceptable_ammo.end(), [&]( const ammotype & at ) { + return e.ammo_types( false ).count( at ); + } ); }; auto loc = g->inv_map_splice( filter, _( "Select tool to modify" ), 1, diff --git a/src/iuse_actor.cpp b/src/iuse_actor.cpp index b8d58c48e156d..989c966f4535a 100644 --- a/src/iuse_actor.cpp +++ b/src/iuse_actor.cpp @@ -2522,10 +2522,7 @@ bool bandolier_actor::is_valid_ammo_type( const itype &t ) const if( !t.ammo ) { return false; } - return std::any_of( t.ammo->type.begin(), t.ammo->type.end(), - [&]( const ammotype & e ) { - return ammo.count( e ); - } ); + return ammo.count( t.ammo->type ); } bool bandolier_actor::can_store( const item &bandolier, const item &obj ) const @@ -2673,8 +2670,7 @@ int ammobelt_actor::use( player &p, item &, bool, const tripoint & ) const mag.ammo_unset(); if( p.rate_action_reload( mag ) != HINT_GOOD ) { - p.add_msg_if_player( _( "Insufficient %s to assemble %s" ), - mag.ammo_type()->name(), mag.tname() ); + p.add_msg_if_player( _( "Insufficient ammunition to assemble %s" ), mag.tname() ); return 0; } diff --git a/src/lightmap.cpp b/src/lightmap.cpp index 9c7b9ee99da2d..552ed6302b56a 100644 --- a/src/lightmap.cpp +++ b/src/lightmap.cpp @@ -80,14 +80,14 @@ void map::add_light_from_items( const tripoint &p, std::list::iterator beg } // TODO: Consider making this just clear the cache and dynamically fill it in as trans() is called -void map::build_transparency_cache( const int zlev ) +bool map::build_transparency_cache( const int zlev ) { auto &map_cache = get_cache( zlev ); auto &transparency_cache = map_cache.transparency_cache; auto &outside_cache = map_cache.outside_cache; if( !map_cache.transparency_cache_dirty ) { - return; + return false; } // Default to just barely not transparent. @@ -167,6 +167,7 @@ void map::build_transparency_cache( const int zlev ) } } map_cache.transparency_cache_dirty = false; + return true; } void map::apply_character_light( player &p ) diff --git a/src/lightmap.h b/src/lightmap.h index a6459f61ded5f..5068d82398ff7 100644 --- a/src/lightmap.h +++ b/src/lightmap.h @@ -23,7 +23,7 @@ #define LIGHT_TRANSPARENCY_OPEN_AIR 0.038376418216 #define LIGHT_TRANSPARENCY_CLEAR 1 -#define LIGHT_RANGE(b) static_cast( -log(LIGHT_AMBIENT_LOW / (float)b) * (1.0 / LIGHT_TRANSPARENCY_OPEN_AIR) ) +#define LIGHT_RANGE(b) static_cast( -log(LIGHT_AMBIENT_LOW / static_cast(b)) * (1.0 / LIGHT_TRANSPARENCY_OPEN_AIR) ) enum lit_level { LL_DARK = 0, diff --git a/src/magic.cpp b/src/magic.cpp index 8f506101ea8cd..babb5ebb0b865 100644 --- a/src/magic.cpp +++ b/src/magic.cpp @@ -15,10 +15,12 @@ #include "mutation.h" #include "npc.h" #include "options.h" +#include "output.h" #include "player.h" #include "projectile.h" #include "rng.h" #include "translations.h" +#include "ui.h" #include @@ -550,6 +552,33 @@ nc_color spell::damage_type_color() const } } +std::string spell::damage_type_string() const +{ + switch( dmg_type() ) { + case DT_HEAT: + return "heat"; + case DT_ACID: + return "acid"; + case DT_BASH: + return "bashing"; + case DT_BIOLOGICAL: + return "biological"; + case DT_COLD: + return "cold"; + case DT_CUT: + return "cutting"; + case DT_ELECTRIC: + return "electric"; + case DT_STAB: + return "stabbing"; + case DT_TRUE: + // not *really* force damage + return "force"; + default: + return "error"; + } +} + // constants defined below are just for the formula to be used, // in order for the inverse formula to be equivalent static const float a = 6200.0; @@ -612,6 +641,30 @@ int spell::casting_exp( const player &p ) const return round( p.adjust_for_focus( base_casting_xp * exp_modifier( p ) ) ); } +std::string spell::enumerate_targets() const +{ + std::vector all_valid_targets; + for( const std::pair pair : target_map ) { + if( is_valid_target( pair.second ) && pair.second != target_none ) { + all_valid_targets.emplace_back( pair.first ); + } + } + if( all_valid_targets.size() == 1 ) { + return all_valid_targets[0]; + } + std::string ret; + for( auto iter = all_valid_targets.begin(); iter != all_valid_targets.end(); iter++ ) { + if( iter + 1 == all_valid_targets.end() ) { + ret = string_format( "%s and %s", ret, *iter ); + } else if( iter == all_valid_targets.begin() ) { + ret = string_format( "%s", *iter ); + } else { + ret = string_format( "%s, %s", ret, *iter ); + } + } + return ret; +} + damage_type spell::dmg_type() const { return type->dmg_type; @@ -860,6 +913,227 @@ int known_magic::time_to_learn_spell( const player &p, spell_id sp ) const ( p.get_skill_level( skill_id( "spellcraft" ) ) / 10.0 ) ); } +size_t known_magic::get_spellname_max_width() +{ + size_t width = 0; + for( const spell *sp : get_spells() ) { + width = std::max( width, sp->name().length() ); + } + return width; +} + +class spellcasting_callback : public uilist_callback +{ + private: + std::vector known_spells; + void draw_spell_info( const spell &sp, const uilist *menu ); + public: + bool casting_ignore; + + spellcasting_callback( std::vector &spells, + bool casting_ignore ) : known_spells( spells ), + casting_ignore( casting_ignore ) {}; + bool key( const input_context &, const input_event &event, int /*entnum*/, + uilist * /*menu*/ ) override { + if( event.get_first_input() == 'I' ) { + casting_ignore = !casting_ignore; + return true; + } + return false; + } + + void select( int entnum, uilist *menu ) override { + mvwputch( menu->window, 0, menu->w_width - menu->pad_right, c_magenta, LINE_OXXX ); + mvwputch( menu->window, menu->w_height - 1, menu->w_width - menu->pad_right, c_magenta, LINE_XXOX ); + for( int i = 1; i < menu->w_height - 1; i++ ) { + mvwputch( menu->window, i, menu->w_width - menu->pad_right, c_magenta, LINE_XOXO ); + } + std::string ignore_string = casting_ignore ? _( "Ignore Distractions" ) : + _( "Popup Distractions" ); + mvwprintz( menu->window, 0, menu->w_width - menu->pad_right + 2, + casting_ignore ? c_red : c_light_green, string_format( "%s %s", "[I]", ignore_string ) ); + draw_spell_info( *known_spells[entnum], menu ); + } +}; + +static std::string moves_to_string( const int moves ) +{ + const int turns = moves / 100; + if( moves < 200 ) { + return _( string_format( "%d %s", moves, "moves" ) ); + } else if( moves < to_moves( 2_minutes ) ) { + return _( string_format( "%d %s", turns, "turns" ) ); + } else if( moves < to_moves( 2_hours ) ) { + return _( string_format( "%d %s", to_minutes( turns * 1_turns ), "minutes" ) ); + } else { + return _( string_format( "%d %s", to_hours( turns * 1_turns ), "hours" ) ); + } +} + +void spellcasting_callback::draw_spell_info( const spell &sp, const uilist *menu ) +{ + const int h_offset = menu->w_width - menu->pad_right + 1; + // includes spaces on either side for readability + const int info_width = menu->pad_right - 4; + const int h_col1 = h_offset + 1; + const int h_col2 = h_offset + ( info_width / 2 ); + const catacurses::window w_menu = menu->window; + // various pieces of spell data mean different things depending on the effect of the spell + const std::string fx = sp.effect(); + int line = 1; + nc_color gray = c_light_gray; + nc_color light_green = c_light_green; + + line += fold_and_print( w_menu, line, h_col1, info_width, gray, sp.description() ); + + line++; + + print_colored_text( w_menu, line, h_col1, gray, gray, + _( string_format( "Spell Level: %d %s", sp.get_level(), sp.is_max_level() ? "(MAX)" : "" ) ) ); + print_colored_text( w_menu, line++, h_col2, gray, gray, + _( string_format( "Max Level: %d", sp.get_max_level() ) ) ); + + print_colored_text( w_menu, line, h_col1, gray, gray, + sp.colorized_fail_percent( g->u ) ); + print_colored_text( w_menu, line++, h_col2, gray, gray, + _( string_format( "Difficulty: %d", sp.get_difficulty() ) ) ); + + print_colored_text( w_menu, line, h_col1, gray, gray, + _( string_format( "Current Exp: %s", colorize( to_string( sp.xp() ), light_green ) ) ) ); + print_colored_text( w_menu, line++, h_col2, gray, gray, + _( string_format( "to Next Level: %s", colorize( to_string( sp.exp_to_next_level() ), + light_green ) ) ) ); + + line++; + + print_colored_text( w_menu, line++, h_col1, gray, gray, + _( string_format( "Casting Cost: %s %s%s", sp.energy_cost_string( g->u ), sp.energy_string(), + sp.energy_source() == hp_energy ? "" : string_format( " ( % s current )", + sp.energy_cur_string( g->u ) ) ) ) ); + + print_colored_text( w_menu, line++, h_col1, gray, gray, + _( string_format( "Casting Time: %s", moves_to_string( sp.casting_time() ) ) ) ); + + line++; + + std::string targets = ""; + if( sp.is_valid_target( target_none ) ) { + targets = "self"; + } else { + targets = sp.enumerate_targets(); + } + print_colored_text( w_menu, line++, h_col1, gray, gray, + _( string_format( "Valid Targets: %s", targets ) ) ); + + line++; + + const int damage = sp.damage(); + std::string damage_string; + std::string aoe_string; + // if it's any type of attack spell, the stats are normal. + if( fx == "target_attack" || fx == "projectile_attack" || fx == "cone_attack" || + fx == "line_attack" ) { + if( damage >= 0 ) { + damage_string = _( string_format( "Damage: %s %s", colorize( to_string( damage ), + sp.damage_type_color() ), + colorize( sp.damage_type_string(), sp.damage_type_color() ) ) ); + } else { + damage_string = _( string_format( "Healing: %s", colorize( "+" + to_string( -damage ), + light_green ) ) ); + } + if( sp.aoe() > 0 ) { + std::string aoe_string_temp = "Spell Radius"; + std::string degree_string = ""; + if( fx == "cone_attack" ) { + aoe_string = "Cone Arc"; + degree_string = "degrees"; + } else if( fx == "line_attack" ) { + aoe_string = "Line Width"; + } + aoe_string = _( string_format( "%s: %d %s", aoe_string_temp, sp.aoe(), degree_string ) ); + } + } else if( fx == "teleport_random" ) { + if( sp.aoe() > 0 ) { + aoe_string = _( string_format( "Variance: %d", sp.aoe() ) ); + } + } else if( fx == "spawn_item" ) { + damage_string = _( string_format( "Spawn %d %s", sp.damage(), item::nname( sp.effect_data(), + sp.damage() ) ) ); + } + + print_colored_text( w_menu, line, h_col1, gray, gray, damage_string ); + print_colored_text( w_menu, line++, h_col2, gray, gray, aoe_string ); + + print_colored_text( w_menu, line++, h_col1, gray, gray, + _( string_format( "Range: %s", sp.range() <= 0 ? "self" : to_string( sp.range() ) ) ) ); + + // todo: damage over time here, when it gets implemeted + + print_colored_text( w_menu, line++, h_col2, gray, gray, sp.duration() <= 0 ? "" : + _( string_format( "Duration: %s", moves_to_string( sp.duration() ) ) ) ); +} + +int known_magic::get_invlet( const spell_id &sp, std::set &used_invlets ) +{ + auto found = invlets.find( sp ); + if( found != invlets.end() ) { + return found->second; + } + for( const std::pair &invlet_pair : invlets ) { + used_invlets.emplace( invlet_pair.second ); + } + for( int i = 'a'; i <= 'z'; i++ ) { + if( used_invlets.count( i ) == 0 ) { + used_invlets.emplace( i ); + return i; + } + } + for( int i = 'A'; i <= 'Z'; i++ ) { + if( used_invlets.count( i ) == 0 ) { + used_invlets.emplace( i ); + return i; + } + } + for( int i = '!'; i <= '-'; i++ ) { + if( used_invlets.count( i ) == 0 ) { + used_invlets.emplace( i ); + return i; + } + } + return 0; +} + +int known_magic::select_spell( const player &p ) +{ + // max width of spell names + const size_t max_spell_name_length = get_spellname_max_width(); + std::vector known_spells = get_spells(); + + uilist spell_menu; + spell_menu.w_height = 24; + spell_menu.w_width = 80; + spell_menu.w_x = ( TERMX - spell_menu.w_width ) / 2; + spell_menu.w_y = ( TERMY - spell_menu.w_height ) / 2; + spell_menu.pad_right = spell_menu.w_width - static_cast( max_spell_name_length ) - 5; + spell_menu.title = _( "Choose a Spell" ); + spellcasting_callback cb( known_spells, casting_ignore ); + spell_menu.callback = &cb; + + std::set used_invlets; + used_invlets.emplace( 'I' ); + + for( size_t i = 0; i < known_spells.size(); i++ ) { + spell_menu.addentry( static_cast( i ), known_spells[i]->can_cast( p ), + get_invlet( known_spells[i]->id(), used_invlets ), known_spells[i]->name() ); + } + + spell_menu.query(); + + casting_ignore = static_cast( spell_menu.callback )->casting_ignore; + + return spell_menu.ret; +} + // spell_effect namespace spell_effect @@ -1220,4 +1494,40 @@ void spawn_ethereal_item( spell &sp ) } } +void recover_energy( spell &sp, const tripoint &target ) +{ + // this spell is not appropriate for healing + const int healing = sp.damage(); + const std::string energy_source = sp.effect_data(); + // TODO: Change to Character + // current limitation is that Character does not have stamina or power_level members + player *p = g->critter_at( target ); + + if( energy_source == "MANA" ) { + p->magic.mod_mana( *p, healing ); + } else if( energy_source == "STAMINA" ) { + if( healing > 0 ) { + p->stamina = std::min( p->get_stamina_max(), p->stamina + healing ); + } else { + p->stamina = std::max( 0, p->stamina + healing ); + } + } else if( energy_source == "FATIGUE" ) { + // fatigue is backwards + p->mod_fatigue( -healing ); + } else if( energy_source == "BIONIC" ) { + if( healing > 0 ) { + p->power_level = std::min( p->max_power_level, p->power_level + healing ); + } else { + p->stamina = std::max( 0, p->stamina + healing ); + } + } else if( energy_source == "PAIN" ) { + // pain is backwards + p->mod_pain_noresist( -healing ); + } else if( energy_source == "HEALTH" ) { + p->mod_healthy( healing ); + } else { + debugmsg( "Invalid effect_str %s for spell %s", energy_source, sp.name() ); + } +} + } diff --git a/src/magic.h b/src/magic.h index ab650f5b65a5d..8c06cf9506631 100644 --- a/src/magic.h +++ b/src/magic.h @@ -238,10 +238,13 @@ class spell std::string energy_cost_string( const player &p ) const; // current energy the player has available as a string std::string energy_cur_string( const player &p ) const; + // prints out a list of valid targets separated by commas + std::string enumerate_targets() const; // energy source enum energy_type energy_source() const; // the color that's representative of the damage type nc_color damage_type_color() const; + std::string damage_type_string() const; // your level in this spell int get_level() const; // difficulty of the level @@ -260,11 +263,16 @@ class known_magic private: // list of spells known std::map spellbook; + // invlets assigned to spell_id + std::map invlets; // the base mana a player would start with int mana_base; // current mana int mana; public: + // ignores all distractions when casting a spell when true + bool casting_ignore = false; + known_magic(); void learn_spell( const std::string &sp, player &p, bool force = false ); @@ -282,6 +290,9 @@ class known_magic std::vector spells() const; // gets the spell associated with the spell_id to be edited spell &get_spell( spell_id sp ); + // opens up a ui that the player can choose a spell from + // returns the index of the spell in the vector of spells + int select_spell( const player &p ); // get all known spells std::vector get_spells(); // how much mana is available to use to cast spells @@ -297,6 +308,11 @@ class known_magic void serialize( JsonOut &json ) const; void deserialize( JsonIn &jsin ); + private: + // gets length of longest spell name + size_t get_spellname_max_width(); + // gets invlet if assigned, or -1 if not + int get_invlet( const spell_id &sp, std::set &used_invlets ); }; namespace spell_effect @@ -319,6 +335,7 @@ std::set spell_effect_line( spell &, const tripoint &source, const int aoe_radius, const bool ignore_walls ); void spawn_ethereal_item( spell &sp ); +void recover_energy( spell &sp, const tripoint &target ); } #endif diff --git a/src/map.cpp b/src/map.cpp index 22f252dbc8ae6..f83757ace58ab 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -95,7 +95,7 @@ const efftype_id effect_boomered( "boomered" ); const efftype_id effect_crushed( "crushed" ); const efftype_id effect_stunned( "stunned" ); -#define dbg(x) DebugLog((DebugLevel)(x),D_MAP) << __FILE__ << ":" << __LINE__ << ": " +#define dbg(x) DebugLog((x),D_MAP) << __FILE__ << ":" << __LINE__ << ": " static std::list nulitems; // Returned when &i_at() is asked for an OOB value static field nulfield; // Returned when &field_at() is asked for an OOB value @@ -389,12 +389,12 @@ void map::vehmove() dirty_vehicle_list.clear(); } -bool map::vehproceed( VehicleList &vehs ) +bool map::vehproceed( VehicleList &vehicle_list ) { wrapped_vehicle *cur_veh = nullptr; float max_of_turn = 0; // First horizontal movement - for( wrapped_vehicle &vehs_v : vehs ) { + for( wrapped_vehicle &vehs_v : vehicle_list ) { if( vehs_v.v->of_turn > max_of_turn ) { cur_veh = &vehs_v; max_of_turn = cur_veh->v->of_turn; @@ -403,7 +403,7 @@ bool map::vehproceed( VehicleList &vehs ) // Then vertical-only movement if( cur_veh == nullptr ) { - for( wrapped_vehicle &vehs_v : vehs ) { + for( wrapped_vehicle &vehs_v : vehicle_list ) { if( vehs_v.v->is_falling ) { cur_veh = &vehs_v; break; @@ -416,6 +416,9 @@ bool map::vehproceed( VehicleList &vehs ) } cur_veh->v = cur_veh->v->act_on_map(); + if( cur_veh->v == nullptr ) { + vehicle_list = get_vehicles(); + } return true; } @@ -523,7 +526,7 @@ vehicle *map::move_vehicle( vehicle &veh, const tripoint &dp, const tileray &fac if( veh_veh_coll_flag ) { // Break here to let the hit vehicle move away - return &veh; + return nullptr; } // If not enough wheels, mess up the ground a bit. @@ -3171,9 +3174,9 @@ void map::bash_ter_furn( const tripoint &p, bash_params ¶ms ) sound_volume = sound_fail_vol; } - sound = bash->sound_fail.empty() ? _( "Thnk!" ) : _( bash->sound_fail ); params.did_bash = true; if( !params.silent ) { + sound = bash->sound_fail.empty() ? _( "Thnk!" ) : _( bash->sound_fail ); sounds::sound( p, sound_volume, sounds::sound_t::combat, sound, false, "smash_fail", soundfxvariant ); } @@ -3202,7 +3205,7 @@ void map::bash_ter_furn( const tripoint &p, bash_params ¶ms ) } soundfxid = "smash_success"; - sound = _( bash->sound ); + sound = bash->sound; // Set this now in case the ter_set below changes this const bool collapses = smash_ter && has_flag( "COLLAPSES", p ); const bool supports = smash_ter && has_flag( "SUPPORTS_ROOF", p ); @@ -3334,7 +3337,7 @@ void map::bash_ter_furn( const tripoint &p, bash_params ¶ms ) params.success |= success; // Not always true, so that we can tell when to stop destroying params.bashed_solid = true; if( !sound.empty() && !params.silent ) { - sounds::sound( p, sound_volume, sounds::sound_t::combat, sound, false, + sounds::sound( p, sound_volume, sounds::sound_t::combat, _( sound ), false, soundfxid, soundfxvariant ); } } @@ -4873,8 +4876,8 @@ static void use_charges_from_furn( const furn_t &f, const itype_id &type, int &q } const itype *itt = f.crafting_pseudo_item_type(); - if( itt != nullptr && itt->tool && itt->tool->ammo_id ) { - const itype_id ammo = itt->tool->ammo_id->default_ammotype(); + if( itt != nullptr && itt->tool && !itt->tool->ammo_id.empty() ) { + const itype_id ammo = ammotype( *itt->tool->ammo_id.begin() )->default_ammotype(); auto stack = m->i_at( p ); auto iter = std::find_if( stack.begin(), stack.end(), [ammo]( const item & i ) { @@ -7779,11 +7782,11 @@ void map::build_obstacle_cache( const tripoint &start, const tripoint &end, } } -void map::build_floor_cache( const int zlev ) +bool map::build_floor_cache( const int zlev ) { auto &ch = get_cache( zlev ); if( !ch.floor_cache_dirty ) { - return; + return false; } auto &floor_cache = ch.floor_cache; @@ -7809,6 +7812,7 @@ void map::build_floor_cache( const int zlev ) } ch.floor_cache_dirty = false; + return zlevels; } void map::build_floor_caches() @@ -7824,10 +7828,11 @@ void map::build_map_cache( const int zlev, bool skip_lightmap ) { const int minz = zlevels ? -OVERMAP_DEPTH : zlev; const int maxz = zlevels ? OVERMAP_HEIGHT : zlev; + bool seen_cache_dirty = false; for( int z = minz; z <= maxz; z++ ) { build_outside_cache( z ); - build_transparency_cache( z ); - build_floor_cache( z ); + seen_cache_dirty |= build_transparency_cache( z ); + seen_cache_dirty |= build_floor_cache( z ); } tripoint start( 0, 0, minz ); @@ -7874,10 +7879,14 @@ void map::build_map_cache( const int zlev, bool skip_lightmap ) const tripoint &p = g->u.pos(); if( ( has_furn( p ) && !furn( p ).obj().transparent ) || !ter( p ).obj().transparent ) { get_cache( p.z ).transparency_cache[p.x][p.y] = LIGHT_TRANSPARENCY_CLEAR; - set_transparency_cache_dirty( p.z ); } - build_seen_cache( g->u.pos(), zlev ); + // Initial value is illegal player position. + static tripoint player_prev_pos = tripoint_zero; + if( seen_cache_dirty || player_prev_pos != p ) { + build_seen_cache( g->u.pos(), zlev ); + player_prev_pos = p; + } if( !skip_lightmap ) { generate_lightmap( zlev ); } diff --git a/src/map.h b/src/map.h index cbb3c5927cc96..8c7bbc5521369 100644 --- a/src/map.h +++ b/src/map.h @@ -1463,11 +1463,15 @@ class map void draw_connections( const oter_id &terrain_type, mapgendata &dat, const time_point &when, const float density ); - void build_transparency_cache( int zlev ); + // Builds a transparency cache and returns true if the cache was invalidated. + // Used to determine if seen cache should be rebuilt. + bool build_transparency_cache( int zlev ); void build_sunlight_cache( int zlev ); public: void build_outside_cache( int zlev ); - void build_floor_cache( int zlev ); + // Builds a floor cache and returns true if the cache was invalidated. + // Used to determine if seen cache should be rebuilt. + bool build_floor_cache( int zlev ); // We want this visible in `game`, because we want it built earlier in the turn than the rest void build_floor_caches(); diff --git a/src/map_field.cpp b/src/map_field.cpp new file mode 100644 index 0000000000000..2cb1cfc1a342d --- /dev/null +++ b/src/map_field.cpp @@ -0,0 +1,2272 @@ +#include "field.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "avatar.h" +#include "calendar.h" +#include "cata_utility.h" +#include "coordinate_conversions.h" +#include "debug.h" +#include "emit.h" +#include "enums.h" +#include "fire.h" +#include "fungal_effects.h" +#include "game.h" +#include "itype.h" +#include "map.h" +#include "map_iterator.h" +#include "mapdata.h" +#include "material.h" +#include "messages.h" +#include "monster.h" +#include "mtype.h" +#include "npc.h" +#include "overmapbuffer.h" +#include "rng.h" +#include "scent_map.h" +#include "submap.h" +#include "translations.h" +#include "vehicle.h" +#include "vpart_position.h" +#include "weather.h" +#include "bodypart.h" +#include "creature.h" +#include "damage.h" +#include "int_id.h" +#include "item.h" +#include "line.h" +#include "math_defines.h" +#include "optional.h" +#include "player.h" +#include "pldata.h" +#include "string_id.h" +#include "units.h" +#include "type_id.h" + +const species_id FUNGUS( "FUNGUS" ); + +const efftype_id effect_badpoison( "badpoison" ); +const efftype_id effect_blind( "blind" ); +const efftype_id effect_corroding( "corroding" ); +const efftype_id effect_fungus( "fungus" ); +const efftype_id effect_onfire( "onfire" ); +const efftype_id effect_poison( "poison" ); +const efftype_id effect_relax_gas( "relax_gas" ); +const efftype_id effect_sap( "sap" ); +const efftype_id effect_smoke( "smoke" ); +const efftype_id effect_stung( "stung" ); +const efftype_id effect_stunned( "stunned" ); +const efftype_id effect_teargas( "teargas" ); +const efftype_id effect_webbed( "webbed" ); + +static const trait_id trait_ELECTRORECEPTORS( "ELECTRORECEPTORS" ); +static const trait_id trait_M_SKIN2( "M_SKIN2" ); +static const trait_id trait_M_SKIN3( "M_SKIN3" ); + +void map::create_burnproducts( const tripoint &p, const item &fuel, const units::mass &burned_mass ) +{ + std::vector all_mats = fuel.made_of(); + if( all_mats.empty() ) { + return; + } + //Items that are multiple materials are assumed to be equal parts each. + units::mass by_weight = burned_mass / all_mats.size(); + for( auto &mat : all_mats ) { + for( auto &bp : mat->burn_products() ) { + itype_id id = bp.first; + // Spawning the same item as the one that was just burned is pointless + // and leads to infinite recursion. + if( fuel.typeId() == id ) { + continue; + } + float eff = bp.second; + int n = floor( eff * ( by_weight / item::find_type( id )->weight ) ); + + if( n <= 0 ) { + continue; + } + spawn_item( p, id, n, 1, calendar::turn ); + } + } +} + +bool map::process_fields() +{ + bool dirty_transparency_cache = false; + const int minz = zlevels ? -OVERMAP_DEPTH : abs_sub.z; + const int maxz = zlevels ? OVERMAP_HEIGHT : abs_sub.z; + for( int z = minz; z <= maxz; z++ ) { + bool zlev_dirty = false; + for( int x = 0; x < my_MAPSIZE; x++ ) { + for( int y = 0; y < my_MAPSIZE; y++ ) { + submap *const current_submap = get_submap_at_grid( { x, y, z } ); + if( current_submap->field_count > 0 ) { + const bool cur_dirty = process_fields_in_submap( current_submap, x, y, z ); + zlev_dirty |= cur_dirty; + } + } + } + + if( zlev_dirty ) { + // For now, just always dirty the transparency cache + // when a field might possibly be changed. + // TODO: check if there are any fields(mostly fire) + // that frequently change, if so set the dirty + // flag, otherwise only set the dirty flag if + // something actually changed + set_transparency_cache_dirty( z ); + dirty_transparency_cache = true; + } + } + + return dirty_transparency_cache; +} + +bool ter_furn_has_flag( const ter_t &ter, const furn_t &furn, const ter_bitflags flag ) +{ + return ter.has_flag( flag ) || furn.has_flag( flag ); +} + +static int ter_furn_movecost( const ter_t &ter, const furn_t &furn ) +{ + if( ter.movecost == 0 ) { + return 0; + } + + if( furn.movecost < 0 ) { + return 0; + } + + return ter.movecost + furn.movecost; +} + +/* +Function: process_fields_in_submap +Iterates over every field on every tile of the given submap given as parameter. +This is the general update function for field effects. This should only be called once per game turn. +If you need to insert a new field behavior per unit time add a case statement in the switch below. +*/ +bool map::process_fields_in_submap( submap *const current_submap, + const int submap_x, const int submap_y, const int submap_z ) +{ + const auto get_neighbors = [this]( const tripoint & pt ) { + // Wrapper to allow skipping bound checks except at the edges of the map + const auto maptile_has_bounds = [this]( const tripoint & pt, const bool bounds_checked ) { + if( bounds_checked ) { + // We know that the point is in bounds + return maptile_at_internal( pt ); + } + + return maptile_at( pt ); + }; + + // Find out which edges are in the bubble + // Where possible, do just one bounds check for all the neighbors + const bool west = pt.x > 0; + const bool north = pt.y > 0; + const bool east = pt.x < SEEX * my_MAPSIZE - 1; + const bool south = pt.y < SEEY * my_MAPSIZE - 1; + return std::array< maptile, 8 > { { + maptile_has_bounds( pt + eight_horizontal_neighbors[0], west &&north ), + maptile_has_bounds( pt + eight_horizontal_neighbors[1], north ), + maptile_has_bounds( pt + eight_horizontal_neighbors[2], east &&north ), + maptile_has_bounds( pt + eight_horizontal_neighbors[3], west ), + maptile_has_bounds( pt + eight_horizontal_neighbors[4], east ), + maptile_has_bounds( pt + eight_horizontal_neighbors[5], west &&south ), + maptile_has_bounds( pt + eight_horizontal_neighbors[6], south ), + maptile_has_bounds( pt + eight_horizontal_neighbors[7], east &&south ), + } + }; + }; + + const auto spread_gas = [this, &get_neighbors]( + field_entry & cur, const tripoint & p, field_id curtype, + int percent_spread, const time_duration & outdoor_age_speedup ) { + const oter_id &cur_om_ter = overmap_buffer.ter( ms_to_omt_copy( g->m.getabs( p ) ) ); + bool sheltered = g->is_sheltered( p ); + int winddirection = g->weather.winddirection; + int windpower = get_local_windpower( g->weather.windspeed, cur_om_ter, p, winddirection, + sheltered ); + // Reset nearby scents to zero + for( const tripoint &tmp : points_in_radius( p, 1 ) ) { + g->scent.set( tmp, 0 ); + } + + const int current_density = cur.getFieldDensity(); + const time_duration current_age = cur.getFieldAge(); + // Dissipate faster outdoors. + if( is_outside( p ) ) { + cur.setFieldAge( current_age + outdoor_age_speedup ); + } + + // Bail out if we don't meet the spread chance or required density. + if( current_density <= 1 || rng( 1, ( 100 - windpower ) ) > percent_spread ) { + return; + } + + const auto can_spread_to = [&]( const maptile & dst, field_id curtype ) { + const field_entry *tmpfld = dst.get_field().findField( curtype ); + const auto &ter = dst.get_ter_t(); + const auto &frn = dst.get_furn_t(); + // Candidates are existing weaker fields or navigable/flagged tiles with no field. + return ( ter_furn_movecost( ter, frn ) > 0 || ter_furn_has_flag( ter, frn, TFLAG_PERMEABLE ) ) && + ( tmpfld == nullptr || tmpfld->getFieldDensity() < cur.getFieldDensity() ); + }; + + const auto spread_to = [&]( maptile & dst ) { + field_entry *candidate_field = dst.find_field( curtype ); + // Nearby gas grows thicker, and ages are shared. + const time_duration age_fraction = current_age / current_density ; + if( candidate_field != nullptr ) { + candidate_field->setFieldDensity( candidate_field->getFieldDensity() + 1 ); + cur.setFieldDensity( current_density - 1 ); + candidate_field->setFieldAge( candidate_field->getFieldAge() + age_fraction ); + cur.setFieldAge( current_age - age_fraction ); + // Or, just create a new field. + } else if( dst.add_field( curtype, 1, 0_turns ) ) { + dst.find_field( curtype )->setFieldAge( age_fraction ); + cur.setFieldDensity( current_density - 1 ); + cur.setFieldAge( current_age - age_fraction ); + } + }; + + // First check if we can fall + // TODO: Make fall and rise chances parameters to enable heavy/light gas + if( zlevels && p.z > -OVERMAP_DEPTH ) { + tripoint down{p.x, p.y, p.z - 1}; + maptile down_tile = maptile_at_internal( down ); + if( can_spread_to( down_tile, curtype ) && valid_move( p, down, true, true ) ) { + spread_to( down_tile ); + return; + } + } + + auto neighs = get_neighbors( p ); + size_t end_it = static_cast( rng( 0, neighs.size() - 1 ) ); + std::vector spread; + std::vector neighbour_vec; + // Then, spread to a nearby point. + // If not possible (or randomly), try to spread up + // Wind direction will block the field spreading into the wind. + spread.reserve( 8 ); + // Start at end_it + 1, then wrap around until all elements have been processed. + for( size_t i = ( end_it + 1 ) % neighs.size(), count = 0 ; + count != neighs.size(); + i = ( i + 1 ) % neighs.size(), count++ ) { + const auto &neigh = neighs[i]; + if( can_spread_to( neigh, curtype ) ) { + spread.push_back( i ); + } + } + auto maptiles = get_wind_blockers( winddirection, p ); + maptile remove_tile = std::get<0>( maptiles ); + maptile remove_tile2 = std::get<1>( maptiles ); + maptile remove_tile3 = std::get<2>( maptiles ); + // three map tiles that are facing th wind direction. + if( !zlevels || one_in( spread.size() ) ) { + // Construct the destination from offset and p + if( g->is_sheltered( p ) || windpower < 5 ) { + spread_to( neighs[ random_entry( spread ) ] ); + } else { + end_it = static_cast( rng( 0, neighs.size() - 1 ) ); + // Start at end_it + 1, then wrap around until all elements have been processed. + for( size_t i = ( end_it + 1 ) % neighs.size(), count = 0 ; + count != neighs.size(); + i = ( i + 1 ) % neighs.size(), count++ ) { + const auto &neigh = neighs[i]; + if( ( neigh.x != remove_tile.x && neigh.y != remove_tile.y ) || + ( neigh.x != remove_tile2.x && neigh.y != remove_tile2.y ) || + ( neigh.x != remove_tile3.x && neigh.y != remove_tile3.y ) ) { + neighbour_vec.push_back( neigh ); + } else if( x_in_y( 1, std::max( 2, windpower ) ) ) { + neighbour_vec.push_back( neigh ); + } + } + if( !neighbour_vec.empty() ) { + spread_to( neighbour_vec[rng( 0, neighbour_vec.size() - 1 )] ); + } + } + } else if( zlevels && p.z < OVERMAP_HEIGHT ) { + tripoint up{p.x, p.y, p.z + 1}; + maptile up_tile = maptile_at_internal( up ); + if( can_spread_to( up_tile, curtype ) && valid_move( p, up, true, true ) ) { + spread_to( up_tile ); + } + } + }; + /* + Function: create_hot_air + Helper function that encapsulates the logic involved in creating hot air. + */ + const auto create_hot_air = [this]( const tripoint & p, int density ) { + field_id hot_air; + switch( density ) { + case 1: + hot_air = fd_hot_air1; + break; + case 2: + hot_air = fd_hot_air2; + break; + case 3: + hot_air = fd_hot_air3; + break; + case 4: + hot_air = fd_hot_air4; + break; + default: + debugmsg( "Tried to spread hot air with density %d", density ); + return; + } + + for( int counter = 0; counter < 5; counter++ ) { + tripoint dst( p.x + rng( -1, 1 ), p.y + rng( -1, 1 ), p.z ); + add_field( dst, hot_air, 1 ); + } + }; + + // This should be true only when the field changes transparency + // More correctly: not just when the field is opaque, but when it changes state + // to a more/less transparent one, or creates a non-transparent field nearby + bool dirty_transparency_cache = false; + //Holds m.field_at(x,y).findField(fd_some_field) type returns. + // Just to avoid typing that long string for a temp value. + field_entry *tmpfld = nullptr; + + tripoint thep; + thep.z = submap_z; + + // Initialize the map tile wrapper + maptile map_tile( current_submap, 0, 0 ); + size_t &locx = map_tile.x; + size_t &locy = map_tile.y; + //Loop through all tiles in this submap indicated by current_submap + for( locx = 0; locx < SEEX; locx++ ) { + for( locy = 0; locy < SEEY; locy++ ) { + // This is a translation from local coordinates to submap coordinates. + // All submaps are in one long 1d array. + thep.x = locx + submap_x * SEEX; + thep.y = locy + submap_y * SEEY; + // A const reference to the tripoint above, so that the code below doesn't accidentally change it + const tripoint &p = thep; + // Get a reference to the field variable from the submap; + // contains all the pointers to the real field effects. + field &curfield = current_submap->fld[locx][locy]; + for( auto it = curfield.begin(); it != curfield.end(); ) { + //Iterating through all field effects in the submap's field. + field_entry &cur = it->second; + // The field might have been killed by processing a neighbor field + if( !cur.isAlive() ) { + if( !fieldlist[cur.getFieldType()].transparent[cur.getFieldDensity() - 1] ) { + dirty_transparency_cache = true; + } + current_submap->field_count--; + curfield.removeField( it++ ); + continue; + } + + //Holds cur.getFieldType() as that is what the old system used before rewrite. + field_id curtype = cur.getFieldType(); + // Again, legacy support in the event someone Mods setFieldDensity to allow more values. + if( cur.getFieldDensity() > 3 || cur.getFieldDensity() < 1 ) { + debugmsg( "Whoooooa density of %d", cur.getFieldDensity() ); + } + + // Don't process "newborn" fields. This gives the player time to run if they need to. + if( cur.getFieldAge() == 0_turns ) { + curtype = fd_null; + } + + int part; + switch( curtype ) { + case fd_null: + case num_fields: + break; // Do nothing, obviously. OBVIOUSLY. + + case fd_blood: + case fd_blood_veggy: + case fd_blood_insect: + case fd_blood_invertebrate: + case fd_bile: + case fd_gibs_flesh: + case fd_gibs_veggy: + case fd_gibs_insect: + case fd_gibs_invertebrate: + // Dissipate faster in water + if( map_tile.get_ter_t().has_flag( TFLAG_SWIMMABLE ) ) { + cur.setFieldAge( cur.getFieldAge() + 25_minutes ); + } + break; + + case fd_acid: { + const auto &ter = map_tile.get_ter_t(); + if( ter.has_flag( TFLAG_SWIMMABLE ) ) { // Dissipate faster in water + cur.setFieldAge( cur.getFieldAge() + 2_minutes ); + } + + // Try to fall by a z-level + if( !zlevels || p.z <= -OVERMAP_DEPTH ) { + break; + } + + tripoint dst{p.x, p.y, p.z - 1}; + if( valid_move( p, dst, true, true ) ) { + maptile dst_tile = maptile_at_internal( dst ); + field_entry *acid_there = dst_tile.find_field( fd_acid ); + if( acid_there == nullptr ) { + dst_tile.add_field( fd_acid, cur.getFieldDensity(), cur.getFieldAge() ); + } else { + // Math can be a bit off, + // but "boiling" falling acid can be allowed to be stronger + // than acid that just lies there + const int sum_density = cur.getFieldDensity() + acid_there->getFieldDensity(); + const int new_density = std::min( 3, sum_density ); + // No way to get precise elapsed time, let's always reset + // Allow falling acid to last longer than regular acid to show it off + const time_duration new_age = -1_minutes * ( sum_density - new_density ); + acid_there->setFieldDensity( new_density ); + acid_there->setFieldAge( new_age ); + } + + // Set ourselves up for removal + cur.setFieldDensity( 0 ); + } + + // TODO: Allow spreading to the sides if age < 0 && density == 3 + } + break; + + // Use the normal aging logic below this switch + case fd_web: + break; + case fd_sap: + break; + case fd_sludge: + break; + case fd_slime: + if( g->scent.get( p ) < cur.getFieldDensity() * 10 ) { + g->scent.set( p, cur.getFieldDensity() * 10 ); + } + break; + case fd_plasma: + case fd_laser: + dirty_transparency_cache = true; + break; + + // TODO: MATERIALS use fire resistance + case fd_fire: { + // Entire objects for ter/frn for flags + const oter_id &cur_om_ter = overmap_buffer.ter( ms_to_omt_copy( g->m.getabs( p ) ) ); + bool sheltered = g->is_sheltered( p ); + int winddirection = g->weather.winddirection; + int windpower = get_local_windpower( g->weather.windspeed, cur_om_ter, p, winddirection, + sheltered ); + const auto &ter = map_tile.get_ter_t(); + const auto &frn = map_tile.get_furn_t(); + + // We've got ter/furn cached, so let's use that + const bool is_sealed = ter_furn_has_flag( ter, frn, TFLAG_SEALED ) && + !ter_furn_has_flag( ter, frn, TFLAG_ALLOW_FIELD_EFFECT ); + // Smoke generation probability, consumed items count + int smoke = 0; + int consumed = 0; + // How much time to add to the fire's life due to burned items/terrain/furniture + time_duration time_added = 0_turns; + // Checks if the fire can spread + // If the flames are in furniture with fire_container flag like brazier or oven, + // they're fully contained, so skip consuming terrain + const bool can_spread = !ter_furn_has_flag( ter, frn, TFLAG_FIRE_CONTAINER ); + // The huge indent below should probably be somehow moved away from here + // without forcing the function to use i_at( p ) for fires without items + if( !is_sealed && map_tile.get_item_count() > 0 ) { + auto items_here = i_at( p ); + std::vector new_content; + for( auto explosive = items_here.begin(); explosive != items_here.end(); ) { + if( explosive->will_explode_in_fire() ) { + // We need to make a copy because the iterator validity is not predictable + item copy = *explosive; + explosive = items_here.erase( explosive ); + if( copy.detonate( p, new_content ) ) { + // Need to restart, iterators may not be valid + explosive = items_here.begin(); + } + } else { + ++explosive; + } + } + + fire_data frd( cur.getFieldDensity(), !can_spread ); + // The highest # of items this fire can remove in one turn + int max_consume = cur.getFieldDensity() * 2; + + for( auto fuel = items_here.begin(); fuel != items_here.end() && consumed < max_consume; ) { + // `item::burn` modifies the charges in order to simulate some of them getting + // destroyed by the fire, this changes the item weight, but may not actually + // destroy it. We need to spawn products anyway. + const units::mass old_weight = fuel->weight( false ); + bool destroyed = fuel->burn( frd ); + // If the item is considered destroyed, it may have negative charge count, + // see `item::burn?. This in turn means `item::weight` returns a negative value, + // which we can not use, so only call `weight` when it's still an existing item. + const units::mass new_weight = destroyed ? 0_gram : fuel->weight( false ); + if( old_weight != new_weight ) { + create_burnproducts( p, *fuel, old_weight - new_weight ); + } + + if( destroyed ) { + // If we decided the item was destroyed by fire, remove it. + // But remember its contents, except for irremovable mods, if any + std::copy( fuel->contents.begin(), fuel->contents.end(), + std::back_inserter( new_content ) ); + new_content.erase( std::remove_if( new_content.begin(), new_content.end(), [&]( const item & i ) { + return i.is_irremovable(); + } ), new_content.end() ); + fuel = items_here.erase( fuel ); + consumed++; + } else { + ++fuel; + } + } + + spawn_items( p, new_content ); + smoke = roll_remainder( frd.smoke_produced ); + time_added = 1_turns * roll_remainder( frd.fuel_produced ); + } + + //Get the part of the vehicle in the fire. + vehicle *veh = veh_at_internal( p, part ); // _internal skips the boundary check + if( veh != nullptr ) { + veh->damage( part, cur.getFieldDensity() * 10, DT_HEAT, true ); + //Damage the vehicle in the fire. + } + if( can_spread ) { + if( ter.has_flag( TFLAG_SWIMMABLE ) ) { + // Flames die quickly on water + cur.setFieldAge( cur.getFieldAge() + 4_minutes ); + } + + // Consume the terrain we're on + if( ter_furn_has_flag( ter, frn, TFLAG_FLAMMABLE ) ) { + // The fire feeds on the ground itself until max density. + time_added += 1_turns * ( 5 - cur.getFieldDensity() ); + smoke += 2; + smoke += static_cast( windpower / 5 ); + if( cur.getFieldDensity() > 1 && + one_in( 200 - cur.getFieldDensity() * 50 ) ) { + destroy( p, false ); + } + + } else if( ter_furn_has_flag( ter, frn, TFLAG_FLAMMABLE_HARD ) && + one_in( 3 ) ) { + // The fire feeds on the ground itself until max density. + time_added += 1_turns * ( 4 - cur.getFieldDensity() ); + smoke += 2; + smoke += static_cast( windpower / 5 ); + if( cur.getFieldDensity() > 1 && + one_in( 200 - cur.getFieldDensity() * 50 ) ) { + destroy( p, false ); + } + + } else if( ter.has_flag( TFLAG_FLAMMABLE_ASH ) ) { + // The fire feeds on the ground itself until max density. + time_added += 1_turns * ( 5 - cur.getFieldDensity() ); + smoke += 2; + smoke += static_cast( windpower / 5 ); + if( cur.getFieldDensity() > 1 && + one_in( 200 - cur.getFieldDensity() * 50 ) ) { + ter_set( p, t_dirt ); + } + + } else if( frn.has_flag( TFLAG_FLAMMABLE_ASH ) ) { + // The fire feeds on the ground itself until max density. + time_added += 1_turns * ( 5 - cur.getFieldDensity() ); + smoke += 2; + smoke += static_cast( windpower / 5 ); + if( cur.getFieldDensity() > 1 && + one_in( 200 - cur.getFieldDensity() * 50 ) ) { + furn_set( p, f_ash ); + add_item_or_charges( p, item( "ash" ) ); + } + + } else if( ter.has_flag( TFLAG_NO_FLOOR ) && zlevels && p.z > -OVERMAP_DEPTH ) { + // We're hanging in the air - let's fall down + tripoint dst{p.x, p.y, p.z - 1}; + if( valid_move( p, dst, true, true ) ) { + maptile dst_tile = maptile_at_internal( dst ); + field_entry *fire_there = dst_tile.find_field( fd_fire ); + if( fire_there == nullptr ) { + dst_tile.add_field( fd_fire, 1, 0_turns ); + cur.setFieldDensity( cur.getFieldDensity() - 1 ); + } else { + // Don't fuel raging fires or they'll burn forever + // as they can produce small fires above themselves + int new_density = std::max( cur.getFieldDensity(), + fire_there->getFieldDensity() ); + // Allow smaller fires to combine + if( new_density < 3 && + cur.getFieldDensity() == fire_there->getFieldDensity() ) { + new_density++; + } + fire_there->setFieldDensity( new_density ); + // A raging fire below us can support us for a while + // Otherwise decay and decay fast + if( new_density < 3 || one_in( 10 ) ) { + cur.setFieldDensity( cur.getFieldDensity() - 1 ); + } + } + + break; + } + } + } + // Lower age is a longer lasting fire + if( time_added != 0_turns ) { + cur.setFieldAge( cur.getFieldAge() - time_added ); + } else if( can_spread || !ter_furn_has_flag( ter, frn, TFLAG_FIRE_CONTAINER ) ) { + // Nothing to burn = fire should be dying out faster + // Drain more power from big fires, so that they stop raging over nothing + // Except for fires on stoves and fireplaces, those are made to keep the fire alive + cur.setFieldAge( cur.getFieldAge() + 10_seconds * cur.getFieldDensity() ); + } + + // Below we will access our nearest 8 neighbors, so let's cache them now + // This should probably be done more globally, because large fires will re-do it a lot + auto neighs = get_neighbors( p ); + // get the neighbours that are allowed due to wind direction + auto maptiles = get_wind_blockers( winddirection, p ); + maptile remove_tile = std::get<0>( maptiles ); + maptile remove_tile2 = std::get<1>( maptiles ); + maptile remove_tile3 = std::get<2>( maptiles ); + std::vector neighbour_vec; + size_t end_it = static_cast( rng( 0, neighs.size() - 1 ) ); + // Start at end_it + 1, then wrap around until all elements have been processed + for( size_t i = ( end_it + 1 ) % neighs.size(), count = 0; + count != neighs.size(); + i = ( i + 1 ) % neighs.size(), count++ ) { + const auto &neigh = neighs[i]; + if( ( neigh.x != remove_tile.x && neigh.y != remove_tile.y ) || + ( neigh.x != remove_tile2.x && neigh.y != remove_tile2.y ) || + ( neigh.x != remove_tile3.x && neigh.y != remove_tile3.y ) ) { + neighbour_vec.push_back( neigh ); + } else if( x_in_y( 1, std::max( 2, windpower ) ) ) { + neighbour_vec.push_back( neigh ); + } + } + // If the flames are in a pit, it can't spread to non-pit + const bool in_pit = ter.id.id() == t_pit; + + // Count adjacent fires, to optimize out needless smoke and hot air + int adjacent_fires = 0; + + // If the flames are big, they contribute to adjacent flames + if( can_spread ) { + if( cur.getFieldDensity() > 1 && one_in( 3 ) ) { + // Basically: Scan around for a spot, + // if there is more fire there, make it bigger and give it some fuel. + // This is how big fires spend their excess age: + // making other fires bigger. Flashpoint. + if( sheltered || windpower < 5 ) { + end_it = static_cast( rng( 0, neighs.size() - 1 ) ); + for( size_t i = ( end_it + 1 ) % neighs.size(), count = 0; + count != neighs.size() && cur.getFieldAge() < 0_turns; + i = ( i + 1 ) % neighs.size(), count++ ) { + maptile &dst = neighs[i]; + auto dstfld = dst.find_field( fd_fire ); + // If the fire exists and is weaker than ours, boost it + if( dstfld != nullptr && + ( dstfld->getFieldDensity() <= cur.getFieldDensity() || + dstfld->getFieldAge() > cur.getFieldAge() ) && + ( in_pit == ( dst.get_ter() == t_pit ) ) ) { + if( dstfld->getFieldDensity() < 2 ) { + dstfld->setFieldDensity( dstfld->getFieldDensity() + 1 ); + } + + dstfld->setFieldAge( dstfld->getFieldAge() - 5_minutes ); + cur.setFieldAge( cur.getFieldAge() + 5_minutes ); + } + + if( dstfld != nullptr ) { + adjacent_fires++; + } + } + } else { + end_it = static_cast( rng( 0, neighbour_vec.size() - 1 ) ); + for( size_t i = ( end_it + 1 ) % neighbour_vec.size(), count = 0; + count != neighbour_vec.size() && cur.getFieldAge() < 0_turns; + i = ( i + 1 ) % neighbour_vec.size(), count++ ) { + maptile &dst = neighbour_vec[i]; + auto dstfld = dst.find_field( fd_fire ); + // If the fire exists and is weaker than ours, boost it + if( dstfld != nullptr && + ( dstfld->getFieldDensity() <= cur.getFieldDensity() || + dstfld->getFieldAge() > cur.getFieldAge() ) && + ( in_pit == ( dst.get_ter() == t_pit ) ) ) { + if( dstfld->getFieldDensity() < 2 ) { + dstfld->setFieldDensity( dstfld->getFieldDensity() + 1 ); + } + + dstfld->setFieldAge( dstfld->getFieldAge() - 5_minutes ); + cur.setFieldAge( cur.getFieldAge() + 5_minutes ); + } + + if( dstfld != nullptr ) { + adjacent_fires++; + } + } + } + } else if( cur.getFieldAge() < 0_turns && cur.getFieldDensity() < 3 ) { + // See if we can grow into a stage 2/3 fire, for this + // burning neighbors are necessary in addition to + // field age < 0, or alternatively, a LOT of fuel. + + // The maximum fire density is 1 for a lone fire, 2 for at least 1 neighbor, + // 3 for at least 2 neighbors. + int maximum_density = 1; + + // The following logic looks a bit complex due to optimization concerns, so here are the semantics: + // 1. Calculate maximum field density based on fuel, -50 minutes is 2(medium), -500 minutes is 3(raging) + // 2. Calculate maximum field density based on neighbors, 3 neighbors is 2(medium), 7 or more neighbors is 3(raging) + // 3. Pick the higher maximum between 1. and 2. + if( cur.getFieldAge() < -500_minutes ) { + maximum_density = 3; + } else { + for( auto &neigh : neighs ) { + if( neigh.get_field().findField( fd_fire ) != nullptr ) { + adjacent_fires++; + } + } + maximum_density = 1 + ( adjacent_fires >= 3 ) + ( adjacent_fires >= 7 ); + + if( maximum_density < 2 && cur.getFieldAge() < -50_minutes ) { + maximum_density = 2; + } + } + + // If we consumed a lot, the flames grow higher + if( cur.getFieldDensity() < maximum_density && cur.getFieldAge() < 0_turns ) { + // Fires under 0 age grow in size. Level 3 fires under 0 spread later on. + // Weaken the newly-grown fire + cur.setFieldDensity( cur.getFieldDensity() + 1 ); + cur.setFieldAge( cur.getFieldAge() + 10_minutes * cur.getFieldDensity() ); + } + } + } + // Consume adjacent fuel / terrain / webs to spread. + // Allow raging fires (and only raging fires) to spread up + // Spreading down is achieved by wrecking the walls/floor and then falling + if( zlevels && cur.getFieldDensity() == 3 && p.z < OVERMAP_HEIGHT ) { + // Let it burn through the floor + maptile dst = maptile_at_internal( {p.x, p.y, p.z + 1} ); + const auto &dst_ter = dst.get_ter_t(); + if( dst_ter.has_flag( TFLAG_NO_FLOOR ) || + dst_ter.has_flag( TFLAG_FLAMMABLE ) || + dst_ter.has_flag( TFLAG_FLAMMABLE_ASH ) || + dst_ter.has_flag( TFLAG_FLAMMABLE_HARD ) ) { + field_entry *nearfire = dst.find_field( fd_fire ); + if( nearfire != nullptr ) { + nearfire->setFieldAge( nearfire->getFieldAge() - 2_minutes ); + } else { + dst.add_field( fd_fire, 1, 0_turns ); + } + // Fueling fires above doesn't cost fuel + } + } + // Our iterator will start at end_i + 1 and increment from there and then wrap around. + // This guarantees it will check all neighbors, starting from a random one + if( sheltered || windpower < 5 ) { + const size_t end_i = static_cast( rng( 0, neighs.size() - 1 ) ); + for( size_t i = ( end_i + 1 ) % neighs.size(), count = 0; + count != neighs.size(); + i = ( i + 1 ) % neighs.size(), count++ ) { + if( one_in( cur.getFieldDensity() * 2 ) ) { + // Skip some processing to save on CPU + continue; + } + + maptile &dst = neighs[i]; + // No bounds checking here: we'll treat the invalid neighbors as valid. + // We're using the map tile wrapper, so we can treat invalid tiles as sentinels. + // This will create small oddities on map edges, but nothing more noticeable than + // "cut-off" that happens with bounds checks. + + field_entry *nearfire = dst.find_field( fd_fire ); + if( nearfire != nullptr ) { + // We handled supporting fires in the section above, no need to do it here + continue; + } + + field_entry *nearwebfld = dst.find_field( fd_web ); + int spread_chance = 25 * ( cur.getFieldDensity() - 1 ); + if( nearwebfld != nullptr ) { + spread_chance = 50 + spread_chance / 2; + } + + const auto &dster = dst.get_ter_t(); + const auto &dsfrn = dst.get_furn_t(); + // Allow weaker fires to spread occasionally + const int power = cur.getFieldDensity() + one_in( 5 ); + if( can_spread && rng( 1, 100 ) < spread_chance && + ( in_pit == ( dster.id.id() == t_pit ) ) && + ( + ( power >= 3 && cur.getFieldAge() < 0_turns && one_in( 20 ) ) || + ( power >= 2 && ( ter_furn_has_flag( dster, dsfrn, TFLAG_FLAMMABLE ) && one_in( 2 ) ) ) || + ( power >= 2 && ( ter_furn_has_flag( dster, dsfrn, TFLAG_FLAMMABLE_ASH ) && one_in( 2 ) ) ) || + ( power >= 3 && ( ter_furn_has_flag( dster, dsfrn, TFLAG_FLAMMABLE_HARD ) && one_in( 5 ) ) ) || + nearwebfld || ( dst.get_item_count() > 0 && + flammable_items_at( p + eight_horizontal_neighbors[i] ) && + one_in( 5 ) ) + ) ) { + dst.add_field( fd_fire, 1, 0_turns ); // Nearby open flammable ground? Set it on fire. + tmpfld = dst.find_field( fd_fire ); + if( tmpfld != nullptr ) { + // Make the new fire quite weak, so that it doesn't start jumping around instantly + tmpfld->setFieldAge( 2_minutes ); + // Consume a bit of our fuel + cur.setFieldAge( cur.getFieldAge() + 1_minutes ); + } + if( nearwebfld ) { + nearwebfld->setFieldDensity( 0 ); + } + } + } + } else { + const size_t end_i = static_cast( rng( 0, neighbour_vec.size() - 1 ) ); + for( size_t i = ( end_i + 1 ) % neighbour_vec.size(), count = 0; + count != neighbour_vec.size(); + i = ( i + 1 ) % neighbour_vec.size(), count++ ) { + if( one_in( cur.getFieldDensity() * 2 ) ) { + // Skip some processing to save on CPU + continue; + } + + if( neighbour_vec.empty() ) { + continue; + } + + maptile &dst = neighbour_vec[i]; + // No bounds checking here: we'll treat the invalid neighbors as valid. + // We're using the map tile wrapper, so we can treat invalid tiles as sentinels. + // This will create small oddities on map edges, but nothing more noticeable than + // "cut-off" that happens with bounds checks. + + field_entry *nearfire = dst.find_field( fd_fire ); + if( nearfire != nullptr ) { + // We handled supporting fires in the section above, no need to do it here + continue; + } + + field_entry *nearwebfld = dst.find_field( fd_web ); + int spread_chance = 25 * ( cur.getFieldDensity() - 1 ); + if( nearwebfld != nullptr ) { + spread_chance = 50 + spread_chance / 2; + } + + const auto &dster = dst.get_ter_t(); + const auto &dsfrn = dst.get_furn_t(); + // Allow weaker fires to spread occasionally + const int power = cur.getFieldDensity() + one_in( 5 ); + if( can_spread && rng( 1, ( 100 - windpower ) ) < spread_chance && + ( in_pit == ( dster.id.id() == t_pit ) ) && + ( + ( power >= 3 && cur.getFieldAge() < 0_turns && one_in( 20 ) ) || + ( power >= 2 && ( ter_furn_has_flag( dster, dsfrn, TFLAG_FLAMMABLE ) && one_in( 2 ) ) ) || + ( power >= 2 && ( ter_furn_has_flag( dster, dsfrn, TFLAG_FLAMMABLE_ASH ) && one_in( 2 ) ) ) || + ( power >= 3 && ( ter_furn_has_flag( dster, dsfrn, TFLAG_FLAMMABLE_HARD ) && one_in( 5 ) ) ) || + nearwebfld || ( dst.get_item_count() > 0 && + flammable_items_at( p + eight_horizontal_neighbors[i] ) && + one_in( 5 ) ) + ) ) { + dst.add_field( fd_fire, 1, 0_turns ); // Nearby open flammable ground? Set it on fire. + tmpfld = dst.find_field( fd_fire ); + if( tmpfld != nullptr ) { + // Make the new fire quite weak, so that it doesn't start jumping around instantly + tmpfld->setFieldAge( 2_minutes ); + // Consume a bit of our fuel + cur.setFieldAge( cur.getFieldAge() + 1_minutes ); + } + if( nearwebfld ) { + nearwebfld->setFieldDensity( 0 ); + } + } + } + } + // Create smoke once - above us if possible, at us otherwise + if( !ter_furn_has_flag( ter, frn, TFLAG_SUPPRESS_SMOKE ) && + rng( 0, ( 100 - windpower ) ) <= smoke && + rng( 3, 35 ) < cur.getFieldDensity() * 10 ) { + bool smoke_up = zlevels && p.z < OVERMAP_HEIGHT; + if( smoke_up ) { + tripoint up{p.x, p.y, p.z + 1}; + maptile dst = maptile_at_internal( up ); + const auto &dst_ter = dst.get_ter_t(); + if( dst_ter.has_flag( TFLAG_NO_FLOOR ) ) { + dst.add_field( fd_smoke, rng( 1, cur.getFieldDensity() ), 0_turns ); + } else { + // Can't create smoke above + smoke_up = false; + } + } + + if( !smoke_up ) { + maptile dst = maptile_at_internal( p ); + // Create thicker smoke + dst.add_field( fd_smoke, cur.getFieldDensity(), 0_turns ); + } + + dirty_transparency_cache = true; // Smoke affects transparency + } + + // Hot air is a load on the CPU + // Don't produce too much of it if we have a lot fires nearby, they produce + // radiant heat which does what hot air would do anyway + if( adjacent_fires < 5 && rng( 0, 4 - adjacent_fires ) ) { + create_hot_air( p, cur.getFieldDensity() ); + } + } + break; + + case fd_smoke: + case fd_tear_gas: + dirty_transparency_cache = true; + spread_gas( cur, p, curtype, 10, 0_turns ); + break; + + case fd_relax_gas: + dirty_transparency_cache = true; + spread_gas( cur, p, curtype, 15, 5_minutes ); + break; + + case fd_fungal_haze: + dirty_transparency_cache = true; + spread_gas( cur, p, curtype, 13, 5_turns ); + if( one_in( 10 - 2 * cur.getFieldDensity() ) ) { + // Haze'd terrain + fungal_effects( *g, g->m ).spread_fungus( p ); + } + + break; + + case fd_toxic_gas: + dirty_transparency_cache = true; + spread_gas( cur, p, curtype, 30, 3_minutes ); + break; + + case fd_cigsmoke: + dirty_transparency_cache = true; + spread_gas( cur, p, curtype, 250, 6_minutes ); + break; + + case fd_weedsmoke: { + dirty_transparency_cache = true; + spread_gas( cur, p, curtype, 200, 6_minutes ); + + if( one_in( 20 ) ) { + if( npc *const np = g->critter_at( p ) ) { + np->complain_about( "weed_smell", 10_minutes, "" ); + } + } + + } + break; + + case fd_methsmoke: { + dirty_transparency_cache = true; + spread_gas( cur, p, curtype, 175, 7_minutes ); + if( one_in( 20 ) ) { + if( npc *const np = g->critter_at( p ) ) { + np->complain_about( "meth_smell", 30_minutes, "" ); + } + } + } + break; + + case fd_cracksmoke: { + dirty_transparency_cache = true; + spread_gas( cur, p, curtype, 175, 8_minutes ); + + if( one_in( 20 ) ) { + if( npc *const np = g->critter_at( p ) ) { + np->complain_about( "crack_smell", 30_minutes, "" ); + } + } + } + break; + + case fd_nuke_gas: { + dirty_transparency_cache = true; + int extra_radiation = rng( 0, cur.getFieldDensity() ); + adjust_radiation( p, extra_radiation ); + spread_gas( cur, p, curtype, 15, 1_minutes ); + break; + } + case fd_cold_air1: + case fd_cold_air2: + case fd_cold_air3: + case fd_cold_air4: + case fd_hot_air1: + case fd_hot_air2: + case fd_hot_air3: + case fd_hot_air4: + // No transparency cache wrecking here! + spread_gas( cur, p, curtype, 100, 100_minutes ); + break; + + case fd_gas_vent: { + dirty_transparency_cache = true; + for( const tripoint &pnt : points_in_radius( p, cur.getFieldDensity() - 1 ) ) { + field &wandering_field = get_field( pnt ); + tmpfld = wandering_field.findField( fd_toxic_gas ); + if( tmpfld && tmpfld->getFieldDensity() < cur.getFieldDensity() ) { + tmpfld->setFieldDensity( tmpfld->getFieldDensity() + 1 ); + } else { + add_field( pnt, fd_toxic_gas, cur.getFieldDensity() ); + } + } + } + break; + + case fd_smoke_vent: { + dirty_transparency_cache = true; + for( const tripoint &pnt : points_in_radius( p, cur.getFieldDensity() - 1 ) ) { + field &wandering_field = get_field( pnt ); + tmpfld = wandering_field.findField( fd_smoke ); + if( tmpfld && tmpfld->getFieldDensity() < cur.getFieldDensity() ) { + tmpfld->setFieldDensity( tmpfld->getFieldDensity() + 1 ); + } else { + add_field( pnt, fd_smoke, cur.getFieldDensity() ); + } + } + } + break; + + case fd_fire_vent: + if( cur.getFieldDensity() > 1 ) { + if( one_in( 3 ) ) { + cur.setFieldDensity( cur.getFieldDensity() - 1 ); + } + create_hot_air( p, cur.getFieldDensity() ); + } else { + dirty_transparency_cache = true; + add_field( p, fd_flame_burst, 3, cur.getFieldAge() ); + cur.setFieldDensity( 0 ); + } + break; + + case fd_flame_burst: + if( cur.getFieldDensity() > 1 ) { + cur.setFieldDensity( cur.getFieldDensity() - 1 ); + create_hot_air( p, cur.getFieldDensity() ); + } else { + dirty_transparency_cache = true; + add_field( p, fd_fire_vent, 3, cur.getFieldAge() ); + cur.setFieldDensity( 0 ); + } + break; + + case fd_electricity: + if( !one_in( 5 ) ) { // 4 in 5 chance to spread + std::vector valid; + if( impassable( p ) && cur.getFieldDensity() > 1 ) { // We're grounded + int tries = 0; + tripoint pnt; + pnt.z = p.z; + while( tries < 10 && cur.getFieldAge() < 5_minutes && cur.getFieldDensity() > 1 ) { + pnt.x = p.x + rng( -1, 1 ); + pnt.y = p.y + rng( -1, 1 ); + if( passable( pnt ) ) { + add_field( pnt, fd_electricity, 1, cur.getFieldAge() + 1_turns ); + cur.setFieldDensity( cur.getFieldDensity() - 1 ); + tries = 0; + } else { + tries++; + } + } + } else { // We're not grounded; attempt to ground + for( const tripoint &dst : points_in_radius( p, 1 ) ) { + if( impassable( dst ) ) { // Grounded tiles first + valid.push_back( dst ); + } + } + if( valid.empty() ) { // Spread to adjacent space, then + tripoint dst( p.x + rng( -1, 1 ), p.y + rng( -1, 1 ), p.z ); + field_entry *elec = get_field( dst ).findField( fd_electricity ); + if( passable( dst ) && elec != nullptr && + elec->getFieldDensity() < 3 ) { + elec->setFieldDensity( elec->getFieldDensity() + 1 ); + cur.setFieldDensity( cur.getFieldDensity() - 1 ); + } else if( passable( dst ) ) { + add_field( dst, fd_electricity, 1, cur.getFieldAge() + 1_turns ); + } + cur.setFieldDensity( cur.getFieldDensity() - 1 ); + } + while( !valid.empty() && cur.getFieldDensity() > 1 ) { + const tripoint target = random_entry_removed( valid ); + add_field( target, fd_electricity, 1, cur.getFieldAge() + 1_turns ); + cur.setFieldDensity( cur.getFieldDensity() - 1 ); + } + } + } + break; + + case fd_fatigue: { + static const std::array monids = { { + mtype_id( "mon_flying_polyp" ), mtype_id( "mon_hunting_horror" ), + mtype_id( "mon_mi_go" ), mtype_id( "mon_yugg" ), mtype_id( "mon_gelatin" ), + mtype_id( "mon_flaming_eye" ), mtype_id( "mon_kreck" ), mtype_id( "mon_gracke" ), + mtype_id( "mon_blank" ), + } + }; + if( cur.getFieldDensity() < 3 && calendar::once_every( 6_hours ) && one_in( 10 ) ) { + cur.setFieldDensity( cur.getFieldDensity() + 1 ); + } else if( cur.getFieldDensity() == 3 && one_in( 600 ) ) { // Spawn nether creature! + g->summon_mon( random_entry( monids ), p ); + } + } + break; + + case fd_push_items: { + auto items = i_at( p ); + for( auto pushee = items.begin(); pushee != items.end(); ) { + if( pushee->typeId() != "rock" || + pushee->age() < 1_turns ) { + pushee++; + } else { + item tmp = *pushee; + tmp.set_age( 0_turns ); + pushee = items.erase( pushee ); + std::vector valid; + for( const tripoint &dst : points_in_radius( p, 1 ) ) { + if( get_field( dst, fd_push_items ) != nullptr ) { + valid.push_back( dst ); + } + } + if( !valid.empty() ) { + tripoint newp = random_entry( valid ); + add_item_or_charges( newp, tmp ); + if( g->u.pos() == newp ) { + add_msg( m_bad, _( "A %s hits you!" ), tmp.tname() ); + body_part hit = random_body_part(); + g->u.deal_damage( nullptr, hit, damage_instance( DT_BASH, 6 ) ); + g->u.check_dead_state(); + } + + if( npc *const p = g->critter_at( newp ) ) { + // TODO: combine with player character code above + body_part hit = random_body_part(); + p->deal_damage( nullptr, hit, damage_instance( DT_BASH, 6 ) ); + if( g->u.sees( newp ) ) { + add_msg( _( "A %1$s hits %2$s!" ), tmp.tname(), p->name ); + } + p->check_dead_state(); + } else if( monster *const mon = g->critter_at( newp ) ) { + mon->apply_damage( nullptr, bp_torso, 6 - mon->get_armor_bash( bp_torso ) ); + if( g->u.sees( newp ) ) { + add_msg( _( "A %1$s hits the %2$s!" ), tmp.tname(), mon->name() ); + } + mon->check_dead_state(); + } + } + } + } + } + break; + + case fd_shock_vent: + if( cur.getFieldDensity() > 1 ) { + if( one_in( 5 ) ) { + cur.setFieldDensity( cur.getFieldDensity() - 1 ); + } + } else { + cur.setFieldDensity( 3 ); + int num_bolts = rng( 3, 6 ); + for( int i = 0; i < num_bolts; i++ ) { + int xdir = 0; + int ydir = 0; + while( xdir == 0 && ydir == 0 ) { + xdir = rng( -1, 1 ); + ydir = rng( -1, 1 ); + } + int dist = rng( 4, 12 ); + int boltx = p.x; + int bolty = p.y; + for( int n = 0; n < dist; n++ ) { + boltx += xdir; + bolty += ydir; + add_field( tripoint( boltx, bolty, p.z ), fd_electricity, rng( 2, 3 ) ); + if( one_in( 4 ) ) { + if( xdir == 0 ) { + xdir = rng( 0, 1 ) * 2 - 1; + } else { + xdir = 0; + } + } + if( one_in( 4 ) ) { + if( ydir == 0 ) { + ydir = rng( 0, 1 ) * 2 - 1; + } else { + ydir = 0; + } + } + } + } + } + break; + + case fd_acid_vent: + if( cur.getFieldDensity() > 1 ) { + if( cur.getFieldAge() >= 1_minutes ) { + cur.setFieldDensity( cur.getFieldDensity() - 1 ); + cur.setFieldAge( 0_turns ); + } + } else { + cur.setFieldDensity( 3 ); + for( const tripoint &t : points_in_radius( p, 5 ) ) { + const field_entry *acid = get_field( t, fd_acid ); + if( acid != nullptr && acid->getFieldDensity() == 0 ) { + int newdens = 3 - ( rl_dist( p, t ) / 2 ) + ( one_in( 3 ) ? 1 : 0 ); + if( newdens > 3 ) { + newdens = 3; + } + if( newdens > 0 ) { + add_field( t, fd_acid, newdens ); + } + } + } + } + break; + + case fd_bees: + dirty_transparency_cache = true; + // Poor bees are vulnerable to so many other fields. + // TODO: maybe adjust effects based on different fields. + if( curfield.findField( fd_web ) || + curfield.findField( fd_fire ) || + curfield.findField( fd_smoke ) || + curfield.findField( fd_toxic_gas ) || + curfield.findField( fd_tear_gas ) || + curfield.findField( fd_relax_gas ) || + curfield.findField( fd_nuke_gas ) || + curfield.findField( fd_gas_vent ) || + curfield.findField( fd_smoke_vent ) || + curfield.findField( fd_fungicidal_gas ) || + curfield.findField( fd_fire_vent ) || + curfield.findField( fd_flame_burst ) || + curfield.findField( fd_electricity ) || + curfield.findField( fd_fatigue ) || + curfield.findField( fd_shock_vent ) || + curfield.findField( fd_plasma ) || + curfield.findField( fd_laser ) || + curfield.findField( fd_dazzling ) || + curfield.findField( fd_electricity ) || + curfield.findField( fd_incendiary ) ) { + // Kill them at the end of processing. + cur.setFieldDensity( 0 ); + } else { + // Bees chase the player if in range, wander randomly otherwise. + if( !g->u.is_underwater() && + rl_dist( p, g->u.pos() ) < 10 && + clear_path( p, g->u.pos(), 10, 0, 100 ) ) { + + std::vector candidate_positions = + squares_in_direction( p.x, p.y, g->u.posx(), g->u.posy() ); + for( auto &candidate_position : candidate_positions ) { + field &target_field = + get_field( tripoint( candidate_position, p.z ) ); + // Only shift if there are no bees already there. + // TODO: Figure out a way to merge bee fields without allowing + // Them to effectively move several times in a turn depending + // on iteration direction. + if( !target_field.findField( fd_bees ) ) { + add_field( tripoint( candidate_position, p.z ), fd_bees, + cur.getFieldDensity(), cur.getFieldAge() ); + cur.setFieldDensity( 0 ); + break; + } + } + } else { + spread_gas( cur, p, curtype, 5, 0_turns ); + } + } + break; + + case fd_incendiary: { + //Needed for variable scope + dirty_transparency_cache = true; + tripoint dst( p.x + rng( -1, 1 ), p.y + rng( -1, 1 ), p.z ); + if( has_flag( TFLAG_FLAMMABLE, dst ) || + has_flag( TFLAG_FLAMMABLE_ASH, dst ) || + has_flag( TFLAG_FLAMMABLE_HARD, dst ) ) { + add_field( dst, fd_fire, 1 ); + } + + //check piles for flammable items and set those on fire + if( flammable_items_at( dst ) ) { + add_field( dst, fd_fire, 1 ); + } + + spread_gas( cur, p, curtype, 66, 4_minutes ); + create_hot_air( p, cur.getFieldDensity() ); + } + break; + + //Legacy Stuff + case fd_rubble: + make_rubble( p ); + break; + + case fd_fungicidal_gas: { + dirty_transparency_cache = true; + spread_gas( cur, p, curtype, 120, 1_minutes ); + //check the terrain and replace it accordingly to simulate the fungus dieing off + const auto &ter = map_tile.get_ter_t(); + const auto &frn = map_tile.get_furn_t(); + const int density = cur.getFieldDensity(); + if( ter.has_flag( "FUNGUS" ) && one_in( 10 / density ) ) { + ter_set( p, t_dirt ); + } + if( frn.has_flag( "FUNGUS" ) && one_in( 10 / density ) ) { + furn_set( p, f_null ); + } + } + break; + + default: + //Suppress warnings + break; + + } // switch (curtype) + + cur.setFieldAge( cur.getFieldAge() + 1_turns ); + auto &fdata = fieldlist[cur.getFieldType()]; + if( fdata.halflife > 0_turns && cur.getFieldAge() > 0_turns && + dice( 2, to_turns( cur.getFieldAge() ) ) > to_turns( fdata.halflife ) ) { + cur.setFieldAge( 0_turns ); + cur.setFieldDensity( cur.getFieldDensity() - 1 ); + } + if( !cur.isAlive() ) { + current_submap->field_count--; + curfield.removeField( it++ ); + } else { + ++it; + } + } + } + } + return dirty_transparency_cache; +} + +//This entire function makes very little sense. Why are the rules the way they are? Why does walking into some things destroy them but not others? + +/* +Function: step_in_field +Triggers any active abilities a field effect would have. Fire burns you, acid melts you, etc. +If you add a field effect that interacts with the player place a case statement in the switch here. +If you wish for a field effect to do something over time (propagate, interact with terrain, etc) place it in process_subfields +*/ +void map::player_in_field( player &u ) +{ + // A copy of the current field for reference. Do not add fields to it, use map::add_field + field &curfield = get_field( u.pos() ); + bool inside = false; // Are we inside? + //to modify power of a field based on... whatever is relevant for the effect. + int adjusted_intensity; + + //If we are in a vehicle figure out if we are inside (reduces effects usually) + // and what part of the vehicle we need to deal with. + if( u.in_vehicle ) { + if( const optional_vpart_position vp = veh_at( u.pos() ) ) { + inside = vp->is_inside(); + } + } + + // Iterate through all field effects on this tile. + // Do not remove the field with removeField, instead set it's density to 0. It will be removed + // later by the field processing, which will also adjust field_count accordingly. + for( auto &field_list_it : curfield ) { + field_entry &cur = field_list_it.second; + if( !cur.isAlive() ) { + continue; + } + + //Do things based on what field effect we are currently in. + switch( cur.getFieldType() ) { + case fd_null: + case fd_blood: // It doesn't actually do anything //necessary to add other types of blood? + case fd_bile: // Ditto + case fd_cigsmoke: + case fd_weedsmoke: + case fd_methsmoke: + case fd_cracksmoke: + //break instead of return in the event of post-processing in the future; + // also we're in a loop now! + break; + + case fd_web: { + //If we are in a web, can't walk in webs or are in a vehicle, get webbed maybe. + //Moving through multiple webs stacks the effect. + if( !u.has_trait( trait_id( "WEB_WALKER" ) ) && !u.in_vehicle ) { + //between 5 and 15 minus your current web level. + u.add_effect( effect_webbed, 1_turns, num_bp, true, cur.getFieldDensity() ); + cur.setFieldDensity( 0 ); //Its spent. + continue; + //If you are in a vehicle destroy the web. + //It should of been destroyed when you ran over it anyway. + } else if( u.in_vehicle ) { + cur.setFieldDensity( 0 ); + continue; + } + } + break; + + case fd_acid: { + // Assume vehicles block acid damage entirely, + // you're certainly not standing in it. + if( u.in_vehicle ) { + break; + } + + if( u.has_trait( trait_id( "ACIDPROOF" ) ) ) { + // No need for warnings + break; + } + + const int density = cur.getFieldDensity(); + int total_damage = 0; + // Use a helper for a bit less boilerplate + const auto burn_part = [&]( body_part bp, const int scale ) { + const int damage = rng( 1, scale + density ); + // A bit ugly, but better than being annoyed by acid when in hazmat + if( u.get_armor_type( DT_ACID, bp ) < damage ) { + auto ddi = u.deal_damage( nullptr, bp, damage_instance( DT_ACID, damage ) ); + total_damage += ddi.total_damage(); + } + // Represents acid seeping in rather than being splashed on + u.add_env_effect( effect_corroding, bp, 2 + density, time_duration::from_turns( rng( 2, + 1 + density ) ), bp, false, 0 ); + }; + + // 1-3 at density, 1-4 at 2, 1-5 at 3 + burn_part( bp_foot_l, 2 ); + burn_part( bp_foot_r, 2 ); + // 1 dmg at 1 density, 1-3 at 2, 1-5 at 3 + burn_part( bp_leg_l, density - 1 ); + burn_part( bp_leg_r, density - 1 ); + const bool on_ground = u.is_on_ground(); + if( on_ground ) { + // Before, it would just break the legs and leave the survivor alone + burn_part( bp_hand_l, 2 ); + burn_part( bp_hand_r, 2 ); + burn_part( bp_torso, 2 ); + // Less arms = less ability to keep upright + if( ( !u.has_two_arms() && one_in( 4 ) ) || one_in( 2 ) ) { + burn_part( bp_arm_l, 1 ); + burn_part( bp_arm_r, 1 ); + burn_part( bp_head, 1 ); + } + } + + if( on_ground && total_damage > 0 ) { + u.add_msg_player_or_npc( m_bad, _( "The acid burns your body!" ), + _( "The acid burns s body!" ) ); + } else if( total_damage > 0 ) { + u.add_msg_player_or_npc( m_bad, _( "The acid burns your legs and feet!" ), + _( "The acid burns s legs and feet!" ) ); + } else if( on_ground ) { + u.add_msg_if_player( m_warning, _( "You're lying in a pool of acid" ) ); + } else { + u.add_msg_if_player( m_warning, _( "You're standing in a pool of acid" ) ); + } + + u.check_dead_state(); + } + break; + + case fd_sap: + //Sap causes the player to get sap disease, slowing them down. + if( u.in_vehicle ) { + break; //sap does nothing to cars. + } + u.add_msg_player_or_npc( m_bad, _( "The sap sticks to you!" ), + _( "The sap sticks to !" ) ); + u.add_effect( effect_sap, cur.getFieldDensity() * 2_turns ); + cur.setFieldDensity( cur.getFieldDensity() - 1 ); //Use up sap. + break; + + case fd_sludge: + //sludge is on the ground, but you are above the ground when boarded on a vehicle + if( !u.in_vehicle ) { + u.add_msg_if_player( m_bad, _( "The sludge is thick and sticky. You struggle to pull free." ) ); + u.moves -= cur.getFieldDensity() * 300; + cur.setFieldDensity( 0 ); + } + break; + + case fd_fire: + if( u.has_active_bionic( bionic_id( "bio_heatsink" ) ) || u.is_wearing( "rm13_armor_on" ) ) { + //heatsink or suit prevents ALL fire damage. + break; + } + //Burn the player. Less so if you are in a car or ON a car. + adjusted_intensity = cur.getFieldDensity(); + if( u.in_vehicle ) { + if( inside ) { + adjusted_intensity -= 2; + } else { + adjusted_intensity -= 1; + } + } + + if( adjusted_intensity < 1 ) { + break; + } + { + // Burn message by intensity + static const std::array player_burn_msg = {{ + translate_marker( "You burn your legs and feet!" ), + translate_marker( "You're burning up!" ), + translate_marker( "You're set ablaze!" ), + translate_marker( "Your whole body is burning!" ) + } + }; + static const std::array npc_burn_msg = {{ + translate_marker( " burns their legs and feet!" ), + translate_marker( " is burning up!" ), + translate_marker( " is set ablaze!" ), + translate_marker( "s whole body is burning!" ) + } + }; + static const std::array player_warn_msg = {{ + translate_marker( "You're standing in a fire!" ), + translate_marker( "You're waist-deep in a fire!" ), + translate_marker( "You're surrounded by raging fire!" ), + translate_marker( "You're lying in fire!" ) + } + }; + + int burn_min = adjusted_intensity; + int burn_max = 3 * adjusted_intensity + 3; + std::list parts_burned; + int msg_num = adjusted_intensity - 1; + if( !u.is_on_ground() ) { + switch( adjusted_intensity ) { + case 3: + parts_burned.push_back( bp_hand_l ); + parts_burned.push_back( bp_hand_r ); + parts_burned.push_back( bp_arm_l ); + parts_burned.push_back( bp_arm_r ); + /* fallthrough */ + case 2: + parts_burned.push_back( bp_torso ); + /* fallthrough */ + case 1: + parts_burned.push_back( bp_foot_l ); + parts_burned.push_back( bp_foot_r ); + parts_burned.push_back( bp_leg_l ); + parts_burned.push_back( bp_leg_r ); + } + } else { + // Lying in the fire is BAAAD news, hits every body part. + msg_num = 3; + parts_burned.assign( all_body_parts.begin(), all_body_parts.end() ); + } + + int total_damage = 0; + for( auto part_burned : parts_burned ) { + const auto dealt = u.deal_damage( nullptr, part_burned, + damage_instance( DT_HEAT, rng( burn_min, burn_max ) ) ); + total_damage += dealt.type_damage( DT_HEAT ); + } + if( total_damage > 0 ) { + u.add_msg_player_or_npc( m_bad, _( player_burn_msg[msg_num] ), _( npc_burn_msg[msg_num] ) ); + } else { + u.add_msg_if_player( m_warning, _( player_warn_msg[msg_num] ) ); + } + u.check_dead_state(); + } + break; + + case fd_smoke: { + if( !inside ) { + //Get smoke disease from standing in smoke. + int density = cur.getFieldDensity(); + int coughStr; + time_duration coughDur = 0_turns; + if( density >= 3 ) { // thick smoke + coughStr = 4; + coughDur = 15_turns; + } else if( density == 2 ) { // smoke + coughStr = 2; + coughDur = 7_turns; + } else { // density 1, thin smoke + coughStr = 1; + coughDur = 2_turns; + } + u.add_env_effect( effect_smoke, bp_mouth, coughStr, coughDur ); + } + } + break; + + case fd_tear_gas: + //Tear gas will both give you teargas disease and/or blind you. + if( ( cur.getFieldDensity() > 1 || !one_in( 3 ) ) && ( !inside || one_in( 3 ) ) ) { + u.add_env_effect( effect_teargas, bp_mouth, 5, 2_minutes ); + } + if( cur.getFieldDensity() > 1 && ( !inside || one_in( 3 ) ) ) { + u.add_env_effect( effect_blind, bp_eyes, cur.getFieldDensity() * 2, 1_minutes ); + } + break; + + case fd_relax_gas: + if( ( cur.getFieldDensity() > 1 || !one_in( 3 ) ) && ( !inside || one_in( 3 ) ) ) { + u.add_env_effect( effect_relax_gas, bp_mouth, cur.getFieldDensity() * 2, 3_turns ); + } + break; + + case fd_fungal_haze: + if( !u.has_trait( trait_id( "M_IMMUNE" ) ) && ( !inside || one_in( 4 ) ) ) { + u.add_env_effect( effect_fungus, bp_mouth, 4, 10_minutes, num_bp, true ); + u.add_env_effect( effect_fungus, bp_eyes, 4, 10_minutes, num_bp, true ); + } + break; + + case fd_dazzling: + if( cur.getFieldDensity() > 1 || one_in( 5 ) ) { + u.add_env_effect( effect_blind, bp_eyes, 10, 10_turns ); + } else { + u.add_env_effect( effect_blind, bp_eyes, 2, 2_turns ); + } + break; + + case fd_toxic_gas: + // Toxic gas at low levels poisons you. + // Toxic gas at high levels will cause very nasty poison. + { + bool inhaled = false; + if( ( cur.getFieldDensity() == 2 && !inside ) || + ( cur.getFieldDensity() == 3 && inside ) ) { + inhaled = u.add_env_effect( effect_poison, bp_mouth, 5, 3_minutes ); + } else if( cur.getFieldDensity() == 3 && !inside ) { + inhaled = u.add_env_effect( effect_badpoison, bp_mouth, 5, 3_minutes ); + } else if( cur.getFieldDensity() == 1 && ( !inside ) ) { + inhaled = u.add_env_effect( effect_poison, bp_mouth, 2, 2_minutes ); + } + if( inhaled ) { + // player does not know how the npc feels, so no message. + u.add_msg_if_player( m_bad, _( "You feel sick from inhaling the %s" ), cur.name() ); + } + } + break; + + case fd_nuke_gas: { + // Get irradiated by the nuclear fallout. + // Changed to min of density, not 0. + float rads = rng( cur.getFieldDensity(), + cur.getFieldDensity() * ( cur.getFieldDensity() + 1 ) ); + bool rad_proof = !u.irradiate( rads ); + // TODO: Reduce damage for rad resistant? + if( cur.getFieldDensity() == 3 && !rad_proof ) { + u.add_msg_if_player( m_bad, _( "This radioactive gas burns!" ) ); + u.hurtall( rng( 1, 3 ), nullptr ); + } + } + break; + + case fd_flame_burst: + //A burst of flame? Only hits the legs and torso. + if( inside ) { + break; //fireballs can't touch you inside a car. + } + if( !u.has_active_bionic( bionic_id( "bio_heatsink" ) ) && + !u.is_wearing( "rm13_armor_on" ) ) { //heatsink or suit stops fire. + u.add_msg_player_or_npc( m_bad, _( "You're torched by flames!" ), + _( " is torched by flames!" ) ); + u.deal_damage( nullptr, bp_leg_l, damage_instance( DT_HEAT, rng( 2, 6 ) ) ); + u.deal_damage( nullptr, bp_leg_r, damage_instance( DT_HEAT, rng( 2, 6 ) ) ); + u.deal_damage( nullptr, bp_torso, damage_instance( DT_HEAT, rng( 4, 9 ) ) ); + u.check_dead_state(); + } else { + u.add_msg_player_or_npc( _( "These flames do not burn you." ), + _( "Those flames do not burn ." ) ); + } + break; + + case fd_electricity: { + // Small universal damage based on density, only if not electroproofed. + if( u.is_elec_immune() ) { + break; + } + int total_damage = 0; + for( size_t i = 0; i < num_hp_parts; i++ ) { + const body_part bp = player::hp_to_bp( static_cast( i ) ); + const int dmg = rng( 1, cur.getFieldDensity() ); + total_damage += u.deal_damage( nullptr, bp, damage_instance( DT_ELECTRIC, dmg ) ).total_damage(); + } + + if( total_damage > 0 ) { + if( u.has_trait( trait_ELECTRORECEPTORS ) ) { + u.add_msg_player_or_npc( m_bad, _( "You're painfully electrocuted!" ), + _( " is shocked!" ) ); + u.mod_pain( total_damage / 2 ); + } else { + u.add_msg_player_or_npc( m_bad, _( "You're shocked!" ), _( " is shocked!" ) ); + } + } else { + u.add_msg_player_or_npc( _( "The electric cloud doesn't affect you." ), + _( "The electric cloud doesn't seem to affect ." ) ); + } + } + + break; + + case fd_fatigue: + //Teleports you... somewhere. + if( rng( 0, 2 ) < cur.getFieldDensity() && u.is_player() ) { + // TODO: allow teleporting for npcs + add_msg( m_bad, _( "You're violently teleported!" ) ); + u.hurtall( cur.getFieldDensity(), nullptr ); + g->teleport(); + } + break; + + // Why do these get removed??? + // Stepping on a shock vent shuts it down. + case fd_shock_vent: + // Stepping on an acid vent shuts it down. + case fd_acid_vent: + cur.setFieldDensity( 0 ); + continue; + + case fd_bees: + // Player is immune to bees while underwater. + if( !u.is_underwater() ) { + int times_stung = 0; + int density = cur.getFieldDensity(); + // If the bees can get at you, they cause steadily increasing pain. + // TODO: Specific stinging messages. + times_stung += one_in( 4 ) && + u.add_env_effect( effect_stung, bp_torso, density, 9_minutes ); + times_stung += one_in( 4 ) && + u.add_env_effect( effect_stung, bp_torso, density, 9_minutes ); + times_stung += one_in( 4 ) && + u.add_env_effect( effect_stung, bp_torso, density, 9_minutes ); + times_stung += one_in( 4 ) && + u.add_env_effect( effect_stung, bp_torso, density, 9_minutes ); + times_stung += one_in( 4 ) && + u.add_env_effect( effect_stung, bp_torso, density, 9_minutes ); + times_stung += one_in( 4 ) && + u.add_env_effect( effect_stung, bp_torso, density, 9_minutes ); + times_stung += one_in( 4 ) && + u.add_env_effect( effect_stung, bp_torso, density, 9_minutes ); + times_stung += one_in( 4 ) && + u.add_env_effect( effect_stung, bp_torso, density, 9_minutes ); + switch( times_stung ) { + case 0: + // Woo, unscathed! + break; + case 1: + u.add_msg_if_player( m_bad, _( "The bees sting you!" ) ); + break; + case 2: + case 3: + u.add_msg_if_player( m_bad, _( "The bees sting you several times!" ) ); + break; + case 4: + case 5: + u.add_msg_if_player( m_bad, _( "The bees sting you many times!" ) ); + break; + case 6: + case 7: + case 8: + default: + u.add_msg_if_player( m_bad, _( "The bees sting you all over your body!" ) ); + break; + } + } + break; + + case fd_incendiary: + // Mysterious incendiary substance melts you horribly. + if( u.has_trait( trait_M_SKIN2 ) || + u.has_trait( trait_M_SKIN3 ) || + cur.getFieldDensity() == 1 ) { + u.add_msg_player_or_npc( m_bad, _( "The incendiary burns you!" ), + _( "The incendiary burns !" ) ); + u.hurtall( rng( 1, 3 ), nullptr ); + } else { + u.add_msg_player_or_npc( m_bad, _( "The incendiary melts into your skin!" ), + _( "The incendiary melts into s skin!" ) ); + u.add_effect( effect_onfire, 8_turns, bp_torso ); + u.hurtall( rng( 2, 6 ), nullptr ); + } + break; + + case fd_fungicidal_gas: + // Fungicidal gas is unhealthy and becomes deadly if you cross a related threshold. + { + // The gas won't harm you inside a vehicle. + if( inside ) { + break; + } + // Full body suits protect you from the effects of the gas. + if( u.worn_with_flag( "GAS_PROOF" ) && u.get_env_resist( bp_mouth ) >= 15 && + u.get_env_resist( bp_eyes ) >= 15 ) { + break; + } + bool inhaled = false; + const int density = cur.getFieldDensity(); + inhaled = u.add_env_effect( effect_poison, bp_mouth, 5, density * 1_minutes ); + if( u.has_trait( trait_id( "THRESH_MYCUS" ) ) || u.has_trait( trait_id( "THRESH_MARLOSS" ) ) ) { + inhaled |= u.add_env_effect( effect_badpoison, bp_mouth, 5, density * 1_minutes ); + u.hurtall( rng( density, density * 2 ), nullptr ); + u.add_msg_if_player( m_bad, _( "The %s burns your skin." ), cur.name() ); + } + + if( inhaled ) { + u.add_msg_if_player( m_bad, _( "The %s makes you feel sick." ), cur.name() ); + } + } + break; + + default: + //Suppress warnings + break; + } + } + +} + +void map::creature_in_field( Creature &critter ) +{ + auto m = dynamic_cast( &critter ); + auto p = dynamic_cast( &critter ); + if( m != nullptr ) { + monster_in_field( *m ); + } else if( p != nullptr ) { + player_in_field( *p ); + } +} + +void map::monster_in_field( monster &z ) +{ + if( z.digging() ) { + return; // Digging monsters are immune to fields + } + field &curfield = get_field( z.pos() ); + + int dam = 0; + // Iterate through all field effects on this tile. + // Do not remove the field with removeField, instead set it's density to 0. It will be removed + // later by the field processing, which will also adjust field_count accordingly. + for( auto &field_list_it : curfield ) { + field_entry &cur = field_list_it.second; + if( !cur.isAlive() ) { + continue; + } + + switch( cur.getFieldType() ) { + case fd_null: + case fd_blood: // It doesn't actually do anything + case fd_bile: // Ditto + break; + + case fd_web: + if( !z.has_flag( MF_WEBWALK ) ) { + z.add_effect( effect_webbed, 1_turns, num_bp, true, cur.getFieldDensity() ); + cur.setFieldDensity( 0 ); + } + break; + + case fd_acid: + if( !z.has_flag( MF_FLIES ) ) { + const int d = rng( cur.getFieldDensity(), cur.getFieldDensity() * 3 ); + z.deal_damage( nullptr, bp_torso, damage_instance( DT_ACID, d ) ); + z.check_dead_state(); + } + break; + + case fd_sap: + z.moves -= cur.getFieldDensity() * 5; + cur.setFieldDensity( cur.getFieldDensity() - 1 ); + break; + + case fd_sludge: + if( !z.has_flag( MF_DIGS ) && !z.has_flag( MF_FLIES ) && + !z.has_flag( MF_SLUDGEPROOF ) ) { + z.moves -= cur.getFieldDensity() * 300; + cur.setFieldDensity( 0 ); + } + break; + + // TODO: MATERIALS Use fire resistance + case fd_fire: + if( z.has_flag( MF_FIREPROOF ) || z.has_flag( MF_FIREY ) ) { + return; + } + // TODO: Replace the section below with proper json values + if( z.made_of_any( Creature::cmat_flesh ) ) { + dam += 3; + } + if( z.made_of( material_id( "veggy" ) ) ) { + dam += 12; + } + if( z.made_of( LIQUID ) || z.made_of_any( Creature::cmat_flammable ) ) { + dam += 20; + } + if( z.made_of_any( Creature::cmat_flameres ) ) { + dam += -20; + } + if( z.has_flag( MF_FLIES ) ) { + dam -= 15; + } + dam -= z.get_armor_type( DT_HEAT, bp_torso ); + + if( cur.getFieldDensity() == 1 ) { + dam += rng( 2, 6 ); + } else if( cur.getFieldDensity() == 2 ) { + dam += rng( 6, 12 ); + if( !z.has_flag( MF_FLIES ) ) { + z.moves -= 20; + if( dam > 0 ) { + z.add_effect( effect_onfire, 1_turns * rng( dam / 2, dam * 2 ) ); + } + } + } else if( cur.getFieldDensity() == 3 ) { + dam += rng( 10, 20 ); + if( !z.has_flag( MF_FLIES ) || one_in( 3 ) ) { + z.moves -= 40; + if( dam > 0 ) { + z.add_effect( effect_onfire, 1_turns * rng( dam / 2, dam * 2 ) ); + } + } + } + // Drop through to smoke no longer needed as smoke will exist in the same square now, + // this would double apply otherwise. + break; + + case fd_smoke: + if( !z.has_flag( MF_NO_BREATHE ) ) { + if( cur.getFieldDensity() == 3 ) { + z.moves -= rng( 10, 20 ); + } + if( z.made_of( material_id( "veggy" ) ) ) { // Plants suffer from smoke even worse + z.moves -= rng( 1, cur.getFieldDensity() * 12 ); + } + } + break; + + case fd_tear_gas: + if( z.made_of_any( Creature::cmat_fleshnveg ) && !z.has_flag( MF_NO_BREATHE ) ) { + if( cur.getFieldDensity() == 3 ) { + z.add_effect( effect_stunned, rng( 1_minutes, 2_minutes ) ); + dam += rng( 4, 10 ); + } else if( cur.getFieldDensity() == 2 ) { + z.add_effect( effect_stunned, rng( 5_turns, 10_turns ) ); + dam += rng( 2, 5 ); + } else { + z.add_effect( effect_stunned, rng( 1_turns, 5_turns ) ); + } + if( z.made_of( material_id( "veggy" ) ) ) { + z.moves -= rng( cur.getFieldDensity() * 5, cur.getFieldDensity() * 12 ); + dam += cur.getFieldDensity() * rng( 8, 14 ); + } + if( z.has_flag( MF_SEES ) ) { + z.add_effect( effect_blind, cur.getFieldDensity() * 8_turns ); + } + } + break; + + case fd_relax_gas: + if( z.made_of_any( Creature::cmat_fleshnveg ) && !z.has_flag( MF_NO_BREATHE ) ) { + z.add_effect( effect_stunned, rng( cur.getFieldDensity() * 4_turns, + cur.getFieldDensity() * 8_turns ) ); + } + break; + + case fd_dazzling: + if( z.has_flag( MF_SEES ) && !z.has_flag( MF_ELECTRONIC ) ) { + z.add_effect( effect_blind, cur.getFieldDensity() * 12_turns ); + z.add_effect( effect_stunned, cur.getFieldDensity() * rng( 5_turns, 12_turns ) ); + } + break; + + case fd_toxic_gas: + if( !z.has_flag( MF_NO_BREATHE ) ) { + dam += cur.getFieldDensity(); + z.moves -= cur.getFieldDensity(); + } + break; + + case fd_nuke_gas: + if( !z.has_flag( MF_NO_BREATHE ) ) { + if( cur.getFieldDensity() == 3 ) { + z.moves -= rng( 60, 120 ); + dam += rng( 30, 50 ); + } else if( cur.getFieldDensity() == 2 ) { + z.moves -= rng( 20, 50 ); + dam += rng( 10, 25 ); + } else { + z.moves -= rng( 0, 15 ); + dam += rng( 0, 12 ); + } + if( z.made_of( material_id( "veggy" ) ) ) { + z.moves -= rng( cur.getFieldDensity() * 5, cur.getFieldDensity() * 12 ); + dam *= cur.getFieldDensity(); + } + } + break; + + // TODO: MATERIALS Use fire resistance + case fd_flame_burst: + if( z.has_flag( MF_FIREPROOF ) || z.has_flag( MF_FIREY ) ) { + return; + } + if( z.made_of_any( Creature::cmat_flesh ) ) { + dam += 3; + } + if( z.made_of( material_id( "veggy" ) ) ) { + dam += 12; + } + if( z.made_of( LIQUID ) || z.made_of_any( Creature::cmat_flammable ) ) { + dam += 50; + } + if( z.made_of_any( Creature::cmat_flameres ) ) { + dam += -25; + } + dam += rng( 0, 8 ); + z.moves -= 20; + break; + + case fd_electricity: + // We don't want to increase dam, but deal a separate hit so that it can apply effects + z.deal_damage( nullptr, bp_torso, + damage_instance( DT_ELECTRIC, rng( 1, cur.getFieldDensity() * 3 ) ) ); + break; + + case fd_fatigue: + if( rng( 0, 2 ) < cur.getFieldDensity() ) { + dam += cur.getFieldDensity(); + int tries = 0; + tripoint newpos = z.pos(); + do { + newpos.x = rng( z.posx() - SEEX, z.posx() + SEEX ); + newpos.y = rng( z.posy() - SEEY, z.posy() + SEEY ); + tries++; + } while( impassable( newpos ) && tries != 10 ); + + if( tries == 10 ) { + z.die_in_explosion( nullptr ); + } else if( monster *const other = g->critter_at( newpos ) ) { + if( g->u.sees( z ) ) { + add_msg( _( "The %1$s teleports into a %2$s, killing them both!" ), + z.name(), other->name() ); + } + other->die_in_explosion( &z ); + } else { + z.setpos( newpos ); + } + } + break; + + case fd_incendiary: + // TODO: MATERIALS Use fire resistance + if( z.has_flag( MF_FIREPROOF ) || z.has_flag( MF_FIREY ) ) { + return; + } + if( z.made_of_any( Creature::cmat_flesh ) ) { + dam += 3; + } + if( z.made_of( material_id( "veggy" ) ) ) { + dam += 12; + } + if( z.made_of( LIQUID ) || z.made_of_any( Creature::cmat_flammable ) ) { + dam += 20; + } + if( z.made_of_any( Creature::cmat_flameres ) ) { + dam += -5; + } + + if( cur.getFieldDensity() == 1 ) { + dam += rng( 2, 6 ); + } else if( cur.getFieldDensity() == 2 ) { + dam += rng( 6, 12 ); + z.moves -= 20; + if( !z.made_of( LIQUID ) && !z.made_of_any( Creature::cmat_flameres ) ) { + z.add_effect( effect_onfire, rng( 8_turns, 12_turns ) ); + } + } else if( cur.getFieldDensity() == 3 ) { + dam += rng( 10, 20 ); + z.moves -= 40; + if( !z.made_of( LIQUID ) && !z.made_of_any( Creature::cmat_flameres ) ) { + z.add_effect( effect_onfire, rng( 12_turns, 16_turns ) ); + } + } + break; + + case fd_fungal_haze: + if( !z.type->in_species( FUNGUS ) && + !z.type->has_flag( MF_NO_BREATHE ) && + !z.make_fungus() ) { + // Don't insta-kill jabberwocks, that's silly + const int density = cur.getFieldDensity(); + z.moves -= rng( 10 * density, 30 * density ); + dam += rng( 0, 10 * density ); + } + break; + + case fd_fungicidal_gas: + if( z.type->in_species( FUNGUS ) ) { + const int density = cur.getFieldDensity(); + z.moves -= rng( 10 * density, 30 * density ); + dam += rng( 4, 7 * density ); + } + break; + + default: + //Suppress warnings + break; + } + } + + if( dam > 0 ) { + z.apply_damage( nullptr, bp_torso, dam, true ); + z.check_dead_state(); + } +} + +std::tuple map::get_wind_blockers( const int &winddirection, + const tripoint &pos ) +{ + double raddir = ( ( winddirection + 180 ) % 360 ) * ( M_PI / 180 ); + float fx = -cos( raddir ); + float fy = sin( raddir ); + int roundedx; + int roundedy; + if( fx > 0.5 ) { + roundedx = 1; + } else if( fx < -0.5 ) { + roundedx = -1; + } else { + roundedx = 0; + } + if( fy > 0.5 ) { + roundedy = 1; + } else if( fy < -0.5 ) { + roundedy = -1; + } else { + roundedy = 0; + } + tripoint removepoint( pos - point( roundedx, roundedy ) ); + tripoint removepoint2; + tripoint removepoint3; + if( roundedy != 0 && roundedx == 0 ) { + removepoint2 = removepoint - point( 1, 0 ); + removepoint3 = removepoint + point( 1, 0 ); + } else if( roundedy == 0 && roundedx != 0 ) { + removepoint2 = removepoint - point( 0, 1 ); + removepoint3 = removepoint + point( 0, 1 ); + } else if( roundedy > 0 && roundedx > 0 ) { + removepoint2 = removepoint - point( 1, 0 ); + removepoint3 = removepoint - point( 0, 1 ); + } else if( roundedy < 0 && roundedx > 0 ) { + removepoint2 = removepoint - point( 1, 0 ); + removepoint3 = removepoint + point( 0, 1 ); + } else if( roundedy < 0 && roundedx < 0 ) { + removepoint2 = removepoint + point( 1, 0 ); + removepoint3 = removepoint + point( 0, 1 ); + } else if( roundedy > 0 && roundedx < 0 ) { + removepoint2 = removepoint + point( 1, 0 ); + removepoint3 = removepoint - point( 0, 1 ); + } + const maptile remove_tile = maptile_at( removepoint ); + const maptile remove_tile2 = maptile_at( removepoint2 ); + const maptile remove_tile3 = maptile_at( removepoint3 ); + return std::make_tuple( remove_tile, remove_tile2, remove_tile3 ); +} + +void map::emit_field( const tripoint &pos, const emit_id &src, float mul ) +{ + if( !src.is_valid() ) { + return; + } + + float chance = src->chance() * mul; + if( src.is_valid() && x_in_y( chance, 100 ) ) { + int qty = chance > 100.0f ? roll_remainder( src->qty() * chance / 100.0f ) : src->qty(); + propagate_field( pos, src->field(), qty, src->density() ); + } +} + +void map::propagate_field( const tripoint ¢er, const field_id type, int amount, + int max_density ) +{ + using gas_blast = std::pair; + std::priority_queue, pair_greater_cmp_first> open; + std::set closed; + open.push( { 0.0f, center } ); + + const bool not_gas = fieldlist[ type ].phase != GAS; + + while( amount > 0 && !open.empty() ) { + if( closed.count( open.top().second ) ) { + open.pop(); + continue; + } + + // All points with equal gas density should propagate at the same time + std::list gas_front; + gas_front.push_back( open.top() ); + int cur_intensity = get_field_strength( open.top().second, type ); + open.pop(); + while( !open.empty() && get_field_strength( open.top().second, type ) == cur_intensity ) { + if( closed.count( open.top().second ) == 0 ) { + gas_front.push_back( open.top() ); + } + + open.pop(); + } + + int increment = std::max( 1, amount / gas_front.size() ); + + while( amount > 0 && !gas_front.empty() ) { + auto gp = random_entry_removed( gas_front ); + closed.insert( gp.second ); + int cur_strength = get_field_strength( gp.second, type ); + if( cur_strength < max_density ) { + int bonus = std::min( max_density - cur_strength, increment ); + adjust_field_strength( gp.second, type, bonus ); + amount -= bonus; + } else { + amount--; + } + + if( amount <= 0 ) { + return; + } + + static const std::array x_offset = {{ -1, 1, 0, 0, 1, -1, -1, 1 }}; + static const std::array y_offset = {{ 0, 0, -1, 1, -1, 1, -1, 1 }}; + for( size_t i = 0; i < 8; i++ ) { + tripoint pt = gp.second + point( x_offset[ i ], y_offset[ i ] ); + if( closed.count( pt ) > 0 ) { + continue; + } + + if( impassable( pt ) && ( not_gas || !has_flag( TFLAG_PERMEABLE, pt ) ) ) { + closed.insert( pt ); + continue; + } + + open.push( { static_cast( rl_dist( center, pt ) ), pt } ); + } + } + } +} diff --git a/src/mapbuffer.cpp b/src/mapbuffer.cpp index 199fa08c6583c..d8f798d5d73a8 100644 --- a/src/mapbuffer.cpp +++ b/src/mapbuffer.cpp @@ -34,7 +34,7 @@ #include "visitable.h" #include "type_id.h" -#define dbg(x) DebugLog((DebugLevel)(x),D_MAP) << __FILE__ << ":" << __LINE__ << ": " +#define dbg(x) DebugLog((x),D_MAP) << __FILE__ << ":" << __LINE__ << ": " mapbuffer MAPBUFFER; diff --git a/src/mapgen.cpp b/src/mapgen.cpp index a8d7d6691053e..ab48cd9b643e9 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -66,7 +66,7 @@ #include "cata_utility.h" #include "int_id.h" -#define dbg(x) DebugLog((DebugLevel)(x),D_MAP_GEN) << __FILE__ << ":" << __LINE__ << ": " +#define dbg(x) DebugLog((x),D_MAP_GEN) << __FILE__ << ":" << __LINE__ << ": " #define MON_RADIUS 3 @@ -7010,7 +7010,7 @@ std::vector map::place_items( const items_location &loc, int chance, int e->contents.emplace_back( e->magazine_default(), e->birthday() ); } if( rng( 0, 99 ) < ammo && e->ammo_remaining() == 0 ) { - e->ammo_set( e->ammo_type()->default_ammotype(), e->ammo_capacity() ); + e->ammo_set( e->ammo_default(), e->ammo_capacity() ); } } } diff --git a/src/mapgen_functions.cpp b/src/mapgen_functions.cpp index ac3c3f0fccd39..1a9031147d8c8 100644 --- a/src/mapgen_functions.cpp +++ b/src/mapgen_functions.cpp @@ -38,7 +38,7 @@ class npc_template; -#define dbg(x) DebugLog((DebugLevel)(x),D_MAP_GEN) << __FILE__ << ":" << __LINE__ << ": " +#define dbg(x) DebugLog((x),D_MAP_GEN) << __FILE__ << ":" << __LINE__ << ": " const mtype_id mon_ant_larva( "mon_ant_larva" ); const mtype_id mon_ant_queen( "mon_ant_queen" ); diff --git a/src/messages.cpp b/src/messages.cpp index 9d2b7e5454cf5..52e266d81b95f 100644 --- a/src/messages.cpp +++ b/src/messages.cpp @@ -474,8 +474,8 @@ void Messages::dialog::init() w = catacurses::newwin( w_height, w_width, w_y, w_x ); ctxt = input_context( "MESSAGE_LOG" ); - ctxt.register_action( "UP", _( "Scroll up" ) ); - ctxt.register_action( "DOWN", _( "Scroll down" ) ); + ctxt.register_action( "UP", translate_marker( "Scroll up" ) ); + ctxt.register_action( "DOWN", translate_marker( "Scroll down" ) ); ctxt.register_action( "PAGE_UP" ); ctxt.register_action( "PAGE_DOWN" ); ctxt.register_action( "FILTER" ); diff --git a/src/mission.cpp b/src/mission.cpp index 19f9cf48fcdc4..c359caf9eed4e 100644 --- a/src/mission.cpp +++ b/src/mission.cpp @@ -29,7 +29,7 @@ #include "monster.h" #include "player.h" -#define dbg(x) DebugLog((DebugLevel)(x),D_GAME) << __FILE__ << ":" << __LINE__ << ": " +#define dbg(x) DebugLog((x),D_GAME) << __FILE__ << ":" << __LINE__ << ": " mission mission_type::create( const int npc_id ) const { diff --git a/src/mission_companion.cpp b/src/mission_companion.cpp index dfa5a707c9e39..b0245ba535380 100644 --- a/src/mission_companion.cpp +++ b/src/mission_companion.cpp @@ -400,8 +400,8 @@ bool talk_function::display_and_choose_opts( mission_data &mission_key, const tr part_y + TITLE_TAB_HEIGHT + 1, part_x + MAX_FAC_NAME_SIZE ); input_context ctxt( "FACTIONS" ); - ctxt.register_action( "UP", _( "Move cursor up" ) ); - ctxt.register_action( "DOWN", _( "Move cursor down" ) ); + ctxt.register_action( "UP", translate_marker( "Move cursor up" ) ); + ctxt.register_action( "DOWN", translate_marker( "Move cursor down" ) ); ctxt.register_action( "NEXT_TAB" ); ctxt.register_action( "PREV_TAB" ); ctxt.register_action( "PAGE_UP" ); diff --git a/src/monmove.cpp b/src/monmove.cpp index c92900eb37b7f..496eb8b20ff58 100644 --- a/src/monmove.cpp +++ b/src/monmove.cpp @@ -917,18 +917,20 @@ void monster::footsteps( const tripoint &p ) if( volume == 0 ) { return; } - std::string footstep = _( "footsteps." ); + std::string footstep; if( type->in_species( BLOB ) ) { - footstep = _( "plop." ); + footstep = translate_marker( "plop." ); } else if( type->in_species( ZOMBIE ) ) { - footstep = _( "shuffling." ); + footstep = translate_marker( "shuffling." ); } else if( type->in_species( ROBOT ) ) { - footstep = _( "mechanical whirring." ); + footstep = translate_marker( "mechanical whirring." ); } else if( type->in_species( WORM ) ) { - footstep = _( "rustle." ); + footstep = translate_marker( "rustle." ); + } else { + footstep = translate_marker( "footsteps" ); } int dist = rl_dist( p, g->u.pos() ); - sounds::add_footstep( p, volume, dist, this, footstep ); + sounds::add_footstep( p, volume, dist, this, _( footstep ) ); return; } diff --git a/src/ncurses_def.cpp b/src/ncurses_def.cpp index 479afdc8cb550..ea0d6029bb9b5 100644 --- a/src/ncurses_def.cpp +++ b/src/ncurses_def.cpp @@ -285,19 +285,19 @@ input_event input_manager::get_input_event() return input_event( KEY_BACKSPACE, CATA_INPUT_KEYBOARD ); } rval.type = CATA_INPUT_KEYBOARD; - rval.text.append( 1, ( char ) key ); + rval.text.append( 1, static_cast( key ) ); // Read the UTF-8 sequence (if any) if( key < 127 ) { // Single byte sequence } else if( 194 <= key && key <= 223 ) { - rval.text.append( 1, ( char ) getch() ); + rval.text.append( 1, static_cast( getch() ) ); } else if( 224 <= key && key <= 239 ) { - rval.text.append( 1, ( char ) getch() ); - rval.text.append( 1, ( char ) getch() ); + rval.text.append( 1, static_cast( getch() ) ); + rval.text.append( 1, static_cast( getch() ) ); } else if( 240 <= key && key <= 244 ) { - rval.text.append( 1, ( char ) getch() ); - rval.text.append( 1, ( char ) getch() ); - rval.text.append( 1, ( char ) getch() ); + rval.text.append( 1, static_cast( getch() ) ); + rval.text.append( 1, static_cast( getch() ) ); + rval.text.append( 1, static_cast( getch() ) ); } else { // Other control character, etc. - no text at all, return an event // without the text property diff --git a/src/npc.cpp b/src/npc.cpp index 391296f544a31..44fbe531f6397 100644 --- a/src/npc.cpp +++ b/src/npc.cpp @@ -565,7 +565,7 @@ void starting_inv( npc &who, const npc_class_id &type ) res.emplace_back( "lighter" ); // If wielding a gun, get some additional ammo for it if( who.weapon.is_gun() ) { - item ammo( who.weapon.ammo_type()->default_ammotype() ); + item ammo( who.weapon.ammo_default() ); ammo = ammo.in_its_container(); if( ammo.made_of( LIQUID ) ) { item container( "bottle_plastic" ); @@ -784,7 +784,7 @@ void npc::starting_weapon( const npc_class_id &type ) } if( weapon.is_gun() ) { - weapon.ammo_set( weapon.type->gun->ammo->default_ammotype() ); + weapon.ammo_set( weapon.ammo_default() ); } weapon.set_owner( my_fac ); } @@ -1183,7 +1183,7 @@ void npc::decide_needs() elem = 20; } if( weapon.is_gun() ) { - needrank[need_ammo] = 5 * get_ammo( weapon.type->gun->ammo ).size(); + needrank[need_ammo] = 5 * get_ammo( ammotype( *weapon.type->gun->ammo.begin() ) ).size(); } needrank[need_weapon] = weapon_value( weapon ); @@ -1374,14 +1374,11 @@ int npc::value( const item &it, int market_price ) const } if( it.is_ammo() ) { - if( weapon.is_gun() && it.type->ammo->type.count( weapon.ammo_type() ) ) { + if( weapon.is_gun() && weapon.ammo_types().count( it.ammo_type() ) ) { ret += 14; // TODO: magazines - don't count ammo as usable if the weapon isn't. } - if( std::any_of( it.type->ammo->type.begin(), it.type->ammo->type.end(), - [&]( const ammotype & e ) { - return has_gun_for_ammo( e ); - } ) ) { + if( has_gun_for_ammo( it.ammo_type() ) ) { ret += 14; // TODO: consider making this cumulative (once was) } } diff --git a/src/npcmove.cpp b/src/npcmove.cpp index edff86a2db36e..f897263e29560 100644 --- a/src/npcmove.cpp +++ b/src/npcmove.cpp @@ -1636,8 +1636,8 @@ bool npc::recharge_cbm() if( use_bionic_by_id( bio_reactor ) || use_bionic_by_id( bio_advreactor ) ) { const std::function reactor_filter = []( const item & it ) { - return it.is_ammo() && ( it.type->ammo->type.count( plutonium ) > 0 || - it.type->ammo->type.count( reactor_slurry ) > 0 ); + return it.is_ammo() && ( it.ammo_type() == plutonium || + it.ammo_type() == reactor_slurry ); }; if( consume_cbm_items( reactor_filter ) ) { return true; @@ -1943,7 +1943,7 @@ bool npc::wont_hit_friend( const tripoint &tar, const item &it, bool throwing ) bool npc::enough_time_to_reload( const item &gun ) const { - int rltime = item_reload_cost( gun, item( gun.ammo_type()->default_ammotype() ), + int rltime = item_reload_cost( gun, item( gun.ammo_default() ), gun.ammo_capacity() ); const float turns_til_reloaded = static_cast( rltime ) / get_speed(); diff --git a/src/npctalk.cpp b/src/npctalk.cpp index fa4ef95c7db32..d81f4b15593ff 100644 --- a/src/npctalk.cpp +++ b/src/npctalk.cpp @@ -101,7 +101,7 @@ static std::map json_talk_topics; // Every OWED_VAL that the NPC owes you counts as +1 towards convincing #define OWED_VAL 1000 -#define dbg(x) DebugLog((DebugLevel)(x),D_GAME) << __FILE__ << ":" << __LINE__ << ": " +#define dbg(x) DebugLog((x),D_GAME) << __FILE__ << ":" << __LINE__ << ": " int topic_category( const talk_topic &the_topic ); @@ -1352,7 +1352,7 @@ void parse_tags( std::string &phrase, const player &u, const player &me, const i if( !me.weapon.is_gun() ) { phrase.replace( fa, l, _( "BADAMMO" ) ); } else { - phrase.replace( fa, l, me.weapon.ammo_type()->name() ); + phrase.replace( fa, l, me.weapon.ammo_current() ); } } else if( tag == "" ) { std::string activity_name; diff --git a/src/overmap.cpp b/src/overmap.cpp index 59ebbcecd347e..b3fa057986795 100644 --- a/src/overmap.cpp +++ b/src/overmap.cpp @@ -51,7 +51,7 @@ #include "monster.h" #include "string_formatter.h" -#define dbg(x) DebugLog((DebugLevel)(x),D_MAP_GEN) << __FILE__ << ":" << __LINE__ << ": " +#define dbg(x) DebugLog((x),D_MAP_GEN) << __FILE__ << ":" << __LINE__ << ": " #define BUILDINGCHANCE 4 #define MIN_ANT_SIZE 8 diff --git a/src/overmap_ui.cpp b/src/overmap_ui.cpp index 72d178113f004..c7a6cdf537bc5 100644 --- a/src/overmap_ui.cpp +++ b/src/overmap_ui.cpp @@ -1091,8 +1091,8 @@ static bool search( tripoint &curs, const tripoint &orig, const bool show_explor input_context ctxt( "OVERMAP_SEARCH" ); ctxt.register_leftright(); - ctxt.register_action( "NEXT_TAB", _( "Next target" ) ); - ctxt.register_action( "PREV_TAB", _( "Previous target" ) ); + ctxt.register_action( "NEXT_TAB", translate_marker( "Next target" ) ); + ctxt.register_action( "PREV_TAB", translate_marker( "Previous target" ) ); ctxt.register_action( "QUIT" ); ctxt.register_action( "CONFIRM" ); ctxt.register_action( "HELP_KEYBINDINGS" ); diff --git a/src/pickup.cpp b/src/pickup.cpp index aa22da0312ff3..055ff7074c14e 100644 --- a/src/pickup.cpp +++ b/src/pickup.cpp @@ -540,13 +540,13 @@ void Pickup::pick_up( const tripoint &p, int min, from_where get_items_from ) ctxt.register_action( "DOWN" ); ctxt.register_action( "RIGHT" ); ctxt.register_action( "LEFT" ); - ctxt.register_action( "NEXT_TAB", _( "Next page" ) ); - ctxt.register_action( "PREV_TAB", _( "Previous page" ) ); + ctxt.register_action( "NEXT_TAB", translate_marker( "Next page" ) ); + ctxt.register_action( "PREV_TAB", translate_marker( "Previous page" ) ); ctxt.register_action( "SCROLL_UP" ); ctxt.register_action( "SCROLL_DOWN" ); ctxt.register_action( "CONFIRM" ); ctxt.register_action( "SELECT_ALL" ); - ctxt.register_action( "QUIT", _( "Cancel" ) ); + ctxt.register_action( "QUIT", translate_marker( "Cancel" ) ); ctxt.register_action( "ANY_INPUT" ); ctxt.register_action( "HELP_KEYBINDINGS" ); ctxt.register_action( "FILTER" ); @@ -802,7 +802,7 @@ void Pickup::pick_up( const tripoint &p, int min, from_where get_items_from ) stealing = true; } } - if( stacked_here[true_it].begin()->_item.ammo_type() == "money" ) { + if( stacked_here[true_it].begin()->_item.ammo_current() == "money" ) { //Count charges // TODO: transition to the item_location system used for the inventory unsigned long charges_total = 0; diff --git a/src/player.cpp b/src/player.cpp index e38dbf700c977..6cba194377464 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -2809,20 +2809,21 @@ void player::pause() return; } - VehicleList vehs = g->m.get_vehicles(); - vehicle *veh = nullptr; - for( auto &v : vehs ) { - veh = v.v; - if( veh && veh->is_moving() && veh->player_in_control( *this ) ) { - if( one_in( 8 ) ) { - double exp_temp = 1 + veh->total_mass() / 400.0_kilogram + std::abs( veh->velocity / 3200.0 ); + if( in_vehicle && one_in( 8 ) ) { + VehicleList vehs = g->m.get_vehicles(); + vehicle *veh = nullptr; + for( auto &v : vehs ) { + veh = v.v; + if( veh && veh->is_moving() && veh->player_in_control( *this ) ) { + double exp_temp = 1 + veh->total_mass() / 400.0_kilogram + + std::abs( veh->velocity / 3200.0 ); int experience = static_cast( exp_temp ); if( exp_temp - experience > 0 && x_in_y( exp_temp - experience, 1.0 ) ) { experience++; } practice( skill_id( "driving" ), experience ); + break; } - break; } } @@ -2931,8 +2932,8 @@ void player::search_surroundings() if( !sees( tp ) ) { continue; } - if( tr.name().empty() || tr.can_see( tp, *this ) ) { - // Already seen, or has no name -> can never be seen + if( tr.is_always_invisible() || tr.can_see( tp, *this ) ) { + // Already seen, or can never be seen continue; } // Chance to detect traps we haven't yet seen. @@ -2976,7 +2977,7 @@ int player::read_speed( bool return_stat_effect ) const ret = 100; } // return_stat_effect actually matters here - return ( return_stat_effect ? ret : ret / 10 ); + return 6 * ( return_stat_effect ? ret : ret / 10 ); } int player::rust_rate( bool return_stat_effect ) const @@ -7548,7 +7549,7 @@ item::reload_option player::select_ammo( const item &base, return row; }; - itype_id last = uistate.lastreload[ base.ammo_type() ]; + itype_id last = uistate.lastreload[ ammotype( base.ammo_default() ) ]; // We keep the last key so that pressing the key twice (for example, r-r for reload) // will always pick the first option on the list. int last_key = inp_mngr.get_previously_pressed_key(); @@ -7656,7 +7657,8 @@ item::reload_option player::select_ammo( const item &base, } const item_location &sel = opts[ menu.ret ].ammo; - uistate.lastreload[ base.ammo_type() ] = sel->is_ammo_container() ? sel->contents.front().typeId() : + uistate.lastreload[ ammotype( base.ammo_default() ) ] = sel->is_ammo_container() ? + sel->contents.front().typeId() : sel->typeId(); return std::move( opts[ menu.ret ] ); } @@ -7720,7 +7722,10 @@ item::reload_option player::select_ammo( const item &base, bool prompt, bool emp } else if( base.is_watertight_container() ) { name = base.is_container_empty() ? "liquid" : base.contents.front().tname(); } else { - name = base.ammo_type()->name(); + const std::set &atypes = base.ammo_types(); + name = enumerate_as_string( atypes.begin(), atypes.end(), []( const ammotype & at ) { + return at->name(); + }, enumeration_conjunction::none ); } add_msg_if_player( m_info, _( "You don't have any %s to reload your %s!" ), name, base.tname() ); @@ -8742,7 +8747,7 @@ bool player::unload( item &it ) } // Next check for any reasons why the item cannot be unloaded - if( !target->ammo_type() || target->ammo_capacity() <= 0 ) { + if( target->ammo_types().empty() || target->ammo_capacity() <= 0 ) { add_msg( m_info, _( "You can't unload a %s!" ), target->tname() ); return false; } @@ -8804,7 +8809,7 @@ bool player::unload( item &it ) } else if( target->ammo_remaining() ) { int qty = target->ammo_remaining(); - if( target->ammo_type() == ammotype( "plutonium" ) ) { + if( target->ammo_current() == "plutonium" ) { qty = target->ammo_remaining() / PLUTONIUM_CHARGES; if( qty > 0 ) { add_msg( _( "You recover %i unused plutonium." ), qty ); @@ -8832,7 +8837,7 @@ bool player::unload( item &it ) // If successful remove appropriate qty of ammo consuming half as much time as required to load it this->moves -= this->item_reload_cost( *target, ammo, qty ) / 2; - if( target->ammo_type() == ammotype( "plutonium" ) ) { + if( target->ammo_current() == "plutonium" ) { qty *= PLUTONIUM_CHARGES; } @@ -8900,7 +8905,7 @@ hint_rating player::rate_action_unload( const item &it ) const } } - if( it.ammo_type().is_null() ) { + if( it.ammo_types().empty() ) { return HINT_CANT; } @@ -10731,7 +10736,7 @@ bool player::has_gun_for_ammo( const ammotype &at ) const { return has_item_with( [at]( const item & it ) { // item::ammo_type considers the active gunmod. - return it.is_gun() && it.ammo_type() == at; + return it.is_gun() && it.ammo_types().count( at ); } ); } @@ -10739,10 +10744,10 @@ bool player::has_magazine_for_ammo( const ammotype &at ) const { return has_item_with( [&at]( const item & it ) { return !it.has_flag( "NO_RELOAD" ) && - ( ( it.is_magazine() && it.ammo_type() == at ) || - ( it.is_gun() && it.magazine_integral() && it.ammo_type() == at ) || + ( ( it.is_magazine() && it.ammo_types().count( at ) ) || + ( it.is_gun() && it.magazine_integral() && it.ammo_types().count( at ) ) || ( it.is_gun() && it.magazine_current() != nullptr && - it.magazine_current()->ammo_type() == at ) ); + it.magazine_current()->ammo_types().count( at ) ) ); } ); } diff --git a/src/player_display.cpp b/src/player_display.cpp index 357726f2ec3b5..fc52baeafe872 100644 --- a/src/player_display.cpp +++ b/src/player_display.cpp @@ -1259,10 +1259,10 @@ Strength - 4; Dexterity - 4; Intelligence - 4; Perception - 4" ) ); input_context ctxt( "PLAYER_INFO" ); ctxt.register_updown(); - ctxt.register_action( "NEXT_TAB", _( "Cycle to next category" ) ); - ctxt.register_action( "PREV_TAB", _( "Cycle to previous category" ) ); + ctxt.register_action( "NEXT_TAB", translate_marker( "Cycle to next category" ) ); + ctxt.register_action( "PREV_TAB", translate_marker( "Cycle to previous category" ) ); ctxt.register_action( "QUIT" ); - ctxt.register_action( "CONFIRM", _( "Toggle skill training" ) ); + ctxt.register_action( "CONFIRM", translate_marker( "Toggle skill training" ) ); ctxt.register_action( "HELP_KEYBINDINGS" ); std::string action; @@ -1301,7 +1301,8 @@ Strength - 4; Dexterity - 4; Intelligence - 4; Perception - 4" ) ); catacurses::refresh(); int curtab = 1; - size_t min, max; + size_t min = 0; + size_t max = 0; line = 0; bool done = false; size_t half_y = 0; diff --git a/src/ranged.cpp b/src/ranged.cpp index f9e47f17f2867..534c37d0f6edc 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -347,8 +347,8 @@ int player::fire_gun( const tripoint &target, int shots, item &gun ) cycle_action( gun, pos() ); if( has_trait( trait_PYROMANIA ) && !has_morale( MORALE_PYROMANIA_STARTFIRE ) ) { - if( gun.ammo_type() == ammotype( "flammable" ) || gun.ammo_type() == ammotype( "66mm" ) || - gun.ammo_type() == ammotype( "84x246mm" ) || gun.ammo_type() == ammotype( "m235" ) ) { + if( gun.ammo_current() == "flammable" || gun.ammo_current() == "66mm" || + gun.ammo_current() == "84x246mm" || gun.ammo_current() == "m235" ) { add_msg_if_player( m_good, _( "You feel a surge of euphoria as flames roar out of the %s!" ), gun.tname() ); add_morale( MORALE_PYROMANIA_STARTFIRE, 15, 15, 8_hours, 6_hours ); @@ -2147,21 +2147,21 @@ item::sound_data item::gun_noise( const bool burst ) const noise = std::max( noise, 0 ); - if( ammo_type() == ammotype( "40mm" ) ) { + if( ammo_current() == "40mm" ) { // Grenade launchers return { 8, _( "Thunk!" ) }; - } else if( ammo_type() == ammotype( "12mm" ) || ammo_type() == ammotype( "metal_rail" ) ) { + } else if( ammo_current() == "12mm" || ammo_current() == "metal_rail" ) { // Railguns return { 24, _( "tz-CRACKck!" ) }; - } else if( ammo_type() == ammotype( "flammable" ) || ammo_type() == ammotype( "66mm" ) || - ammo_type() == ammotype( "84x246mm" ) || ammo_type() == ammotype( "m235" ) ) { + } else if( ammo_current() == "flammable" || ammo_current() == "66mm" || + ammo_current() == "84x246mm" || ammo_current() == "m235" ) { // Rocket launchers and flamethrowers return { 4, _( "Fwoosh!" ) }; - } else if( ammo_type() == ammotype( "arrow" ) ) { + } else if( ammo_current() == "arrow" ) { return { noise, _( "whizz!" ) }; - } else if( ammo_type() == ammotype( "bolt" ) ) { + } else if( ammo_current() == "bolt" ) { return { noise, _( "thonk!" ) }; } @@ -2282,7 +2282,7 @@ dispersion_sources player::get_weapon_dispersion( const item &obj ) const double player::gun_value( const item &weap, int ammo ) const { // TODO: Mods - // TODO: Allow using a specified type of ammo rather than default + // TODO: Allow using a specified type of ammo rather than default or current if( !weap.type->gun ) { return 0.0; } @@ -2292,14 +2292,21 @@ double player::gun_value( const item &weap, int ammo ) const } const islot_gun &gun = *weap.type->gun; - const itype_id ammo_type = weap.ammo_default( true ); + itype_id ammo_type; + if( weap.ammo_current() != "null" ) { + ammo_type = weap.ammo_current(); + } else if( weap.magazine_current() ) { + ammo_type = weap.common_ammo_default(); + } else { + ammo_type = weap.ammo_default(); + } const itype *def_ammo_i = ammo_type != "NULL" ? item::find_type( ammo_type ) : nullptr; damage_instance gun_damage = weap.gun_damage(); item tmp = weap; - tmp.ammo_set( weap.ammo_default() ); + tmp.ammo_set( ammo_type ); int total_dispersion = get_weapon_dispersion( tmp ).max() + effective_dispersion( tmp.sight_dispersion() ); diff --git a/src/recipe.cpp b/src/recipe.cpp index 6065491069fc3..fa6d90d29e0cb 100644 --- a/src/recipe.cpp +++ b/src/recipe.cpp @@ -44,7 +44,7 @@ int recipe::batch_time( int batch, float multiplier, size_t assistants ) const // if recipe does not benefit from batching and we have no assistants, don't do unnecessary additional calculations if( batch_rscale == 0.0 && assistants == 0 ) { - return local_time * batch; + return static_cast( local_time ) * batch; } float total_time = 0.0; @@ -309,11 +309,14 @@ void recipe::add_requirements( const std::vector> std::string recipe::get_consistency_error() const { - if( !item::type_is_defined( result_ ) && category != "CC_BUILDING" ) { + if( category == "CC_BUILDING" ) { + if( is_blueprint() || oter_str_id( result_.c_str() ).is_valid() ) { + return std::string(); + } return "defines invalid result"; } - if( category == "CC_BUILDING" && !oter_str_id( result_.c_str() ).is_valid() ) { + if( !item::type_is_defined( result_ ) ) { return "defines invalid result"; } diff --git a/src/recipe_dictionary.cpp b/src/recipe_dictionary.cpp index 0cbcbcb7b5e26..7c3edc3d1cb2c 100644 --- a/src/recipe_dictionary.cpp +++ b/src/recipe_dictionary.cpp @@ -375,16 +375,20 @@ void recipe_dictionary::finalize() } } - // Cache auto-learn recipes + // Cache auto-learn recipes and blueprints for( const auto &e : recipe_dict.recipes ) { if( e.second.autolearn ) { recipe_dict.autolearn.insert( &e.second ); } + if( e.second.is_blueprint() ) { + recipe_dict.blueprints.insert( &e.second ); + } } } void recipe_dictionary::reset() { + recipe_dict.blueprints.clear(); recipe_dict.autolearn.clear(); recipe_dict.recipes.clear(); recipe_dict.uncraft.clear(); diff --git a/src/recipe_dictionary.h b/src/recipe_dictionary.h index 70ff2f6595dc0..33dd7f7f80b60 100644 --- a/src/recipe_dictionary.h +++ b/src/recipe_dictionary.h @@ -30,6 +30,11 @@ class recipe_dictionary return autolearn; } + /** Returns all blueprints */ + const std::set &all_blueprints() const { + return blueprints; + } + size_t size() const; std::map::const_iterator begin() const; std::map::const_iterator end() const; @@ -57,6 +62,7 @@ class recipe_dictionary std::map recipes; std::map uncraft; std::set autolearn; + std::set blueprints; static void finalize_internal( std::map &obj ); }; diff --git a/src/recipe_groups.cpp b/src/recipe_groups.cpp index 4e64ba8300818..b3c2bbb422e2c 100644 --- a/src/recipe_groups.cpp +++ b/src/recipe_groups.cpp @@ -23,7 +23,8 @@ using group_id = string_id; struct recipe_group_data { group_id id; std::string building_type = "NONE"; - std::map cooking_recipes; + std::map recipes; + std::map> om_terrains; bool was_loaded; void load( JsonObject &jo, const std::string &src ); @@ -43,45 +44,69 @@ void recipe_group_data::load( JsonObject &jo, const std::string & ) JsonObject ordering = jsarr.next_object(); const std::string name_id = ordering.get_string( "id" ); const std::string desc = ordering.get_string( "description" ); - cooking_recipes[desc] = name_id; + recipes[desc] = name_id; + om_terrains[name_id] = std::set(); + JsonArray js_terr = ordering.get_array( "om_terrains" ); + while( js_terr.has_more() ) { + const std::string ter_type = js_terr.next_string(); + om_terrains[name_id].insert( ter_type ); + } } - } void recipe_group_data::check() const { - for( auto a : cooking_recipes ) { + for( const auto &a : recipes ) { if( !recipe_id( a.second ).is_valid() ) { debugmsg( "%s is not a valid recipe", a.second ); } } } -std::map recipe_group::get_recipes( const std::string &id ) +std::map recipe_group::get_recipes_by_bldg( const std::string &bldg ) { std::map all_rec; - if( id == "ALL" ) { + if( bldg == "ALL" ) { for( const auto &gr : recipe_groups_data.get_all() ) { - std::map tmp = gr.cooking_recipes; + std::map tmp = gr.recipes; all_rec.insert( tmp.begin(), tmp.end() ); } return all_rec; - } else if( id == "COOK" || id == "BASE" || id == "FARM" || id == "SMITH" ) { + } else { for( const auto &gr : recipe_groups_data.get_all() ) { - if( gr.building_type != id ) { + if( gr.building_type != bldg ) { continue; } - std::map tmp = gr.cooking_recipes; + std::map tmp = gr.recipes; all_rec.insert( tmp.begin(), tmp.end() ); } return all_rec; } +} + +std::map recipe_group::get_recipes_by_id( const std::string &id, + const std::string &om_terrain_id ) +{ + std::map all_rec; if( !recipe_groups_data.is_valid( group_id( id ) ) ) { return all_rec; } const recipe_group_data &group = recipe_groups_data.obj( group_id( id ) ); - return group.cooking_recipes; + if( om_terrain_id != "ANY" ) { + for( const auto &recp : group.recipes ) { + const auto &recp_terrain = group.om_terrains.find( recp.second ); + if( recp_terrain == group.om_terrains.end() ) { + continue; + } + if( recp_terrain->second.find( om_terrain_id ) != recp_terrain->second.end() ) { + all_rec[recp.first] = recp.second; + } + } + return all_rec; + } + return group.recipes; } + void recipe_group::load( JsonObject &jo, const std::string &src ) { recipe_groups_data.load( jo, src ); diff --git a/src/recipe_groups.h b/src/recipe_groups.h index 5c8f71885dae0..741bc61085d0e 100644 --- a/src/recipe_groups.h +++ b/src/recipe_groups.h @@ -14,8 +14,9 @@ void load( JsonObject &jo, const std::string &src ); void check(); void reset(); -std::map get_recipes( const std::string &id ); - +std::map get_recipes_by_bldg( const std::string &id ); +std::map get_recipes_by_id( const std::string &id, + const std::string &om_terrain_id = "ANY" ); } #endif diff --git a/src/savegame_json.cpp b/src/savegame_json.cpp index bc209ecfbd1ab..5150b71a25492 100644 --- a/src/savegame_json.cpp +++ b/src/savegame_json.cpp @@ -188,7 +188,7 @@ std::vector item::magazine_convert() // limit ammo to base capacity and return any excess as a new item charges = std::min( charges, type->gun->clip ); if( qty > 0 ) { - res.emplace_back( ammo_current() != "null" ? ammo_current() : ammo_type()->default_ammotype(), + res.emplace_back( ammo_current() != "null" ? ammo_current() : ammo_default(), calendar::turn, qty ); } @@ -201,7 +201,7 @@ std::vector item::magazine_convert() // now handle items using the new detachable magazines that haven't yet been converted item mag( magazine_default(), calendar::turn ); - item ammo( ammo_current() != "null" ? ammo_current() : ammo_type()->default_ammotype(), + item ammo( ammo_current() != "null" ? ammo_current() : ammo_default(), calendar::turn ); // give base item an appropriate magazine and add to that any ammo originally stored in base item diff --git a/src/sdl_wrappers.cpp b/src/sdl_wrappers.cpp index 0eecf782df353..5ad6e90f99998 100644 --- a/src/sdl_wrappers.cpp +++ b/src/sdl_wrappers.cpp @@ -17,7 +17,7 @@ # endif #endif // TILES -#define dbg(x) DebugLog((DebugLevel)(x),D_SDL) << __FILE__ << ":" << __LINE__ << ": " +#define dbg(x) DebugLog((x),D_SDL) << __FILE__ << ":" << __LINE__ << ": " bool printErrorIf( const bool condition, const char *const message ) { diff --git a/src/sdlsound.cpp b/src/sdlsound.cpp index 9bb56920dc7b3..c57408340420a 100644 --- a/src/sdlsound.cpp +++ b/src/sdlsound.cpp @@ -32,7 +32,7 @@ #include "sdl_wrappers.h" #include "sounds.h" -#define dbg(x) DebugLog((DebugLevel)(x),D_SDL) << __FILE__ << ":" << __LINE__ << ": " +#define dbg(x) DebugLog((x),D_SDL) << __FILE__ << ":" << __LINE__ << ": " using id_and_variant = std::pair; struct sound_effect_resource { diff --git a/src/sdltiles.cpp b/src/sdltiles.cpp index 09ee613011e47..16fecdd00bfc2 100644 --- a/src/sdltiles.cpp +++ b/src/sdltiles.cpp @@ -78,7 +78,7 @@ #include "inventory.h" #endif -#define dbg(x) DebugLog((DebugLevel)(x),D_SDL) << __FILE__ << ":" << __LINE__ << ": " +#define dbg(x) DebugLog((x),D_SDL) << __FILE__ << ":" << __LINE__ << ": " //*********************************** //Globals * @@ -2058,8 +2058,8 @@ void draw_quick_shortcuts() if( !quick_shortcuts_enabled || SDL_IsTextInputActive() || ( get_option( "ANDROID_HIDE_HOLDS" ) && !is_quick_shortcut_touch && finger_down_time > 0 && - SDL_GetTicks() - finger_down_time >= ( unsigned long ) - get_option( "ANDROID_INITIAL_DELAY" ) ) ) { // player is swipe + holding in a direction + SDL_GetTicks() - finger_down_time >= static_cast( + get_option( "ANDROID_INITIAL_DELAY" ) ) ) ) { // player is swipe + holding in a direction return; } @@ -2132,7 +2132,8 @@ void draw_quick_shortcuts() } hovered = is_quick_shortcut_touch && hovered_quick_shortcut == &event; show_hint = hovered && - SDL_GetTicks() - finger_down_time > ( unsigned long )get_option( "ANDROID_INITIAL_DELAY" ); + SDL_GetTicks() - finger_down_time > static_cast< unsigned long > + ( get_option( "ANDROID_INITIAL_DELAY" ) ); std::string hint_text; if( show_hint ) { if( touch_input_context.get_category() == "INVENTORY" && inv_chars.valid( key ) ) { @@ -2247,7 +2248,8 @@ void draw_virtual_joystick() // Bail out if we don't need to draw the joystick if( !get_option( "ANDROID_SHOW_VIRTUAL_JOYSTICK" ) || finger_down_time <= 0 || - SDL_GetTicks() - finger_down_time <= ( unsigned long )get_option( "ANDROID_INITIAL_DELAY" ) || + SDL_GetTicks() - finger_down_time <= static_cast + ( get_option( "ANDROID_INITIAL_DELAY" ) ) || is_quick_shortcut_touch || is_two_finger_touch ) { return; @@ -2300,10 +2302,10 @@ void update_finger_repeat_delay() std::max( 0.01f, ( get_option( "ANDROID_REPEAT_DELAY_RANGE" ) ) * longest_window_edge ), 0.0f, 1.0f ); finger_repeat_delay = lerp( std::pow( t, get_option( "ANDROID_SENSITIVITY_POWER" ) ), - ( unsigned long )std::max( get_option( "ANDROID_REPEAT_DELAY_MIN" ), - get_option( "ANDROID_REPEAT_DELAY_MAX" ) ), - ( unsigned long )std::min( get_option( "ANDROID_REPEAT_DELAY_MIN" ), - get_option( "ANDROID_REPEAT_DELAY_MAX" ) ) ); + static_cast( std::max( get_option( "ANDROID_REPEAT_DELAY_MIN" ), + get_option( "ANDROID_REPEAT_DELAY_MAX" ) ) ), + static_cast( std::min( get_option( "ANDROID_REPEAT_DELAY_MIN" ), + get_option( "ANDROID_REPEAT_DELAY_MAX" ) ) ) ); } // TODO: Is there a better way to detect when string entry is allowed? @@ -2393,7 +2395,8 @@ void handle_finger_input( unsigned long ticks ) } } } else { - if( ticks - finger_down_time >= ( unsigned long )get_option( "ANDROID_INITIAL_DELAY" ) ) { + if( ticks - finger_down_time >= static_cast + ( get_option( "ANDROID_INITIAL_DELAY" ) ) ) { // Single tap (repeat) - held, so always treat this as a tap // We only allow repeats for waiting, not confirming in menus as that's a bit silly if( is_default_mode ) { @@ -2402,7 +2405,7 @@ void handle_finger_input( unsigned long ticks ) } } else { if( last_tap_time > 0 && - ticks - last_tap_time < ( unsigned long )get_option( "ANDROID_INITIAL_DELAY" ) ) { + ticks - last_tap_time < static_cast( get_option( "ANDROID_INITIAL_DELAY" ) ) ) { // Double tap last_input = input_event( is_default_mode ? KEY_ESCAPE : KEY_ESCAPE, CATA_INPUT_KEYBOARD ); last_tap_time = 0; @@ -2679,7 +2682,8 @@ static void CheckMessages() // Toggle quick shortcuts on/off if( ac_back_down_time > 0 && - ticks - ac_back_down_time > ( unsigned long )get_option( "ANDROID_INITIAL_DELAY" ) ) { + ticks - ac_back_down_time > static_cast + ( get_option( "ANDROID_INITIAL_DELAY" ) ) ) { if( !quick_shortcuts_toggle_handled ) { quick_shortcuts_enabled = !quick_shortcuts_enabled; quick_shortcuts_toggle_handled = true; @@ -2702,7 +2706,8 @@ static void CheckMessages() // Handle repeating inputs from touch + holds if( !is_quick_shortcut_touch && !is_two_finger_touch && finger_down_time > 0 && - ticks - finger_down_time > ( unsigned long )get_option( "ANDROID_INITIAL_DELAY" ) ) { + ticks - finger_down_time > static_cast + ( get_option( "ANDROID_INITIAL_DELAY" ) ) ) { if( ticks - finger_repeat_time > finger_repeat_delay ) { handle_finger_input( ticks ); finger_repeat_time = ticks; @@ -2712,7 +2717,8 @@ static void CheckMessages() // If we received a first tap and not another one within a certain period, this was a single tap, so trigger the input event if( !is_quick_shortcut_touch && !is_two_finger_touch && last_tap_time > 0 && - ticks - last_tap_time >= ( unsigned long )get_option( "ANDROID_INITIAL_DELAY" ) ) { + ticks - last_tap_time >= static_cast + ( get_option( "ANDROID_INITIAL_DELAY" ) ) ) { // Single tap last_tap_time = ticks; last_input = input_event( is_default_mode ? get_key_event_from_string( @@ -2723,7 +2729,8 @@ static void CheckMessages() // ensure hint text pops up even if player doesn't move finger to trigger a FINGERMOTION event if( is_quick_shortcut_touch && finger_down_time > 0 && - ticks - finger_down_time > ( unsigned long )get_option( "ANDROID_INITIAL_DELAY" ) ) { + ticks - finger_down_time > static_cast + ( get_option( "ANDROID_INITIAL_DELAY" ) ) ) { needupdate = true; } } @@ -2826,7 +2833,8 @@ static void CheckMessages() #if defined(__ANDROID__) // Toggle virtual keyboard with Android back button if( ev.key.keysym.sym == SDLK_AC_BACK ) { - if( ticks - ac_back_down_time <= ( unsigned long )get_option( "ANDROID_INITIAL_DELAY" ) ) { + if( ticks - ac_back_down_time <= static_cast + ( get_option( "ANDROID_INITIAL_DELAY" ) ) ) { if( SDL_IsTextInputActive() ) { SDL_StopTextInput(); } else { @@ -2996,7 +3004,8 @@ static void CheckMessages() // Get the quick shortcut that was originally touched quick_shortcut = get_quick_shortcut_under_finger( true ); if( quick_shortcut && - ticks - finger_down_time <= ( unsigned long )get_option( "ANDROID_INITIAL_DELAY" ) && + ticks - finger_down_time <= static_cast( get_option( "ANDROID_INITIAL_DELAY" ) ) + && finger_curr_y < finger_down_y && finger_down_y - finger_curr_y > std::abs( finger_down_x - finger_curr_x ) ) { // a flick up was detected, remove the quick shortcut! @@ -3067,8 +3076,8 @@ static void CheckMessages() } } } - } else if( ticks - finger_down_time <= ( unsigned long ) - get_option( "ANDROID_INITIAL_DELAY" ) ) { + } else if( ticks - finger_down_time <= static_cast( + get_option( "ANDROID_INITIAL_DELAY" ) ) ) { handle_finger_input( ticks ); } } diff --git a/src/sounds.cpp b/src/sounds.cpp index 38060476b9680..43e1373f54d6a 100644 --- a/src/sounds.cpp +++ b/src/sounds.cpp @@ -57,7 +57,7 @@ # endif #endif -#define dbg(x) DebugLog((DebugLevel)(x),D_SDL) << __FILE__ << ":" << __LINE__ << ": " +#define dbg(x) DebugLog((x),D_SDL) << __FILE__ << ":" << __LINE__ << ": " weather_type previous_weather; int prev_hostiles = 0; diff --git a/src/string_id_null_ids.cpp b/src/string_id_null_ids.cpp index d7f359cc9ff9d..8f2e563648121 100644 --- a/src/string_id_null_ids.cpp +++ b/src/string_id_null_ids.cpp @@ -39,6 +39,7 @@ MAKE_NULL_ID2( oter_t, "", 0 ) MAKE_NULL_ID2( oter_type_t, "", 0 ) MAKE_NULL_ID2( ter_t, "t_null", 0 ) MAKE_NULL_ID2( trap, "tr_null" ) +MAKE_NULL_ID2( field_type, "x_fd_null", 0 ) MAKE_NULL_ID2( furn_t, "f_null", 0 ) MAKE_NULL_ID2( MonsterGroup, "GROUP_NULL" ) MAKE_NULL_ID2( mission_type, "MISSION_NULL" ) diff --git a/src/trap.cpp b/src/trap.cpp index 64f158e3f4400..77c5cc20b8984 100644 --- a/src/trap.cpp +++ b/src/trap.cpp @@ -114,6 +114,7 @@ void trap::load( JsonObject &jo, const std::string & ) act = trap_function_from_string( jo.get_string( "action" ) ); optional( jo, was_loaded, "benign", benign, false ); + optional( jo, was_loaded, "always_invisible", always_invisible, false ); optional( jo, was_loaded, "funnel_radius", funnel_radius_mm, 0 ); assign( jo, "trigger_weight", trigger_weight ); optional( jo, was_loaded, "drops", components ); @@ -121,8 +122,7 @@ void trap::load( JsonObject &jo, const std::string & ) std::string trap::name() const { - // trap names can be empty, those are special always invisible traps. See player::search_surroundings - return name_.empty() ? name_ : _( name_ ); + return _( name_ ); } void trap::reset() diff --git a/src/trap.h b/src/trap.h index b8069122c1e2e..af4437630c6d8 100644 --- a/src/trap.h +++ b/src/trap.h @@ -76,6 +76,7 @@ struct trap { int avoidance; // 0 to ??, affects avoidance int difficulty; // 0 to ??, difficulty of assembly & disassembly bool benign = false; + bool always_invisible = false; trap_function act; std::string name_; /** @@ -86,6 +87,12 @@ struct trap { std::vector components; // For disassembly? public: std::string name() const; + /** + * There are special always invisible traps. See player::search_surroundings + */ + bool is_always_invisible() const { + return always_invisible; + } /** * How easy it is to spot the trap. Smaller values means it's easier to spot. */ diff --git a/src/turret.cpp b/src/turret.cpp index 19316c5c56a39..0c700060921f4 100644 --- a/src/turret.cpp +++ b/src/turret.cpp @@ -149,7 +149,7 @@ std::set turret_data::ammo_options() const } else { for( const auto &e : veh->fuels_left() ) { const itype *fuel = item::find_type( e.first ); - if( fuel->ammo && fuel->ammo->type.count( part->base.ammo_type() ) && + if( fuel->ammo && part->base.ammo_types().count( fuel->ammo->type ) && e.second >= part->base.ammo_required() ) { opts.insert( fuel->get_id() ); diff --git a/src/type_id.h b/src/type_id.h index 06d4b2b39e63c..036a566a06d61 100644 --- a/src/type_id.h +++ b/src/type_id.h @@ -20,6 +20,10 @@ using emit_id = string_id; class fault; using fault_id = string_id; +struct field_type; +using field_type_id = int_id; +using field_type_str_id = string_id; + struct furn_t; using furn_id = int_id; using furn_str_id = string_id; diff --git a/src/veh_type.cpp b/src/veh_type.cpp index 5af188f5b156c..29bdd288891c2 100644 --- a/src/veh_type.cpp +++ b/src/veh_type.cpp @@ -1055,13 +1055,13 @@ void vehicle_prototype::finalize() } else { for( const auto &e : pt.ammo_types ) { const auto ammo = item::find_type( e ); - if( !ammo->ammo && ammo->ammo->type.count( base->gun->ammo ) ) { + if( !ammo->ammo && base->gun->ammo.count( ammo->ammo->type ) ) { debugmsg( "init_vehicles: turret %s has invalid ammo_type %s in %s", pt.part.c_str(), e.c_str(), id.c_str() ); } } if( pt.ammo_types.empty() ) { - pt.ammo_types.insert( base->gun->ammo->default_ammotype() ); + pt.ammo_types.insert( ammotype( *base->gun->ammo.begin() )->default_ammotype() ); } } diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 8cf5b3eeda5dc..7b4244261502b 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -3274,8 +3274,10 @@ void vehicle::noise_and_smoke( int load, time_duration time ) { const std::array sound_levels = {{ 0, 15, 30, 60, 100, 140, 180, INT_MAX }}; const std::array sound_msgs = {{ - _( "hmm" ), _( "hummm!" ), _( "whirrr!" ), _( "vroom!" ), _( "roarrr!" ), - _( "ROARRR!" ), _( "BRRROARRR!" ), _( "BRUMBRUMBRUMBRUM!" ) + translate_marker( "hmm" ), translate_marker( "hummm!" ), + translate_marker( "whirrr!" ), translate_marker( "vroom!" ), + translate_marker( "roarrr!" ), translate_marker( "ROARRR!" ), + translate_marker( "BRRROARRR!" ), translate_marker( "BRUMBRUMBRUMBRUM!" ) } }; double noise = 0.0; @@ -3349,7 +3351,7 @@ void vehicle::noise_and_smoke( int load, time_duration time ) add_msg( m_debug, "VEH NOISE final: %d", static_cast( noise ) ); vehicle_noise = static_cast( noise ); if( has_engine_type_not( fuel_type_muscle, true ) ) { - sounds::sound( global_pos3(), noise, sounds::sound_t::movement, sound_msgs[lvl], true ); + sounds::sound( global_pos3(), noise, sounds::sound_t::movement, _( sound_msgs[lvl] ), true ); } } @@ -4395,7 +4397,7 @@ bool vehicle::add_item( int part, const item &itm ) // add creaking sounds and damage to overloaded vpart, outright break it past a certain point, or when hitting bumps etc if( parts[ part ].base.is_gun() ) { - if( !itm.is_ammo() || itm.ammo_type() != parts[ part ].base.ammo_type() ) { + if( !itm.is_ammo() || !parts[ part ].base.ammo_types().count( itm.ammo_type() ) ) { return false; } } @@ -4551,7 +4553,7 @@ void vehicle::place_spawn_items() e.contents.emplace_back( e.magazine_default(), e.birthday() ); } if( spawn_ammo ) { - e.ammo_set( e.ammo_type()->default_ammotype() ); + e.ammo_set( e.ammo_default() ); } } add_item( part, e ); diff --git a/src/vehicle_move.cpp b/src/vehicle_move.cpp index f3bf9dc49c725..6fe30ddea6b3a 100644 --- a/src/vehicle_move.cpp +++ b/src/vehicle_move.cpp @@ -39,7 +39,7 @@ const efftype_id effect_stunned( "stunned" ); const efftype_id effect_harnessed( "harnessed" ); const skill_id skill_driving( "driving" ); -#define dbg(x) DebugLog((DebugLevel)(x),D_MAP) << __FILE__ << ":" << __LINE__ << ": " +#define dbg(x) DebugLog((x),D_MAP) << __FILE__ << ":" << __LINE__ << ": " // tile height in meters static const float tile_height = 4; @@ -1273,7 +1273,7 @@ vehicle *vehicle::act_on_map() g->m.on_vehicle_moved( smz ); // Destroy vehicle (sank to nowhere) g->m.destroy_vehicle( this ); - return this; + return nullptr; } // It needs to fall when it has no support OR was falling before @@ -1410,7 +1410,7 @@ vehicle *vehicle::act_on_map() new_pointer = g->m.move_vehicle( *new_pointer, tripoint( dp.x, dp.y, 0 ), mdir ); } - if( dp.z != 0 ) { + if( new_pointer != nullptr && dp.z != 0 ) { new_pointer = g->m.move_vehicle( *new_pointer, tripoint( 0, 0, dp.z ), mdir ); is_falling = false; } diff --git a/src/vehicle_part.cpp b/src/vehicle_part.cpp index 04e7abbbf8688..f2c9e74b072d2 100644 --- a/src/vehicle_part.cpp +++ b/src/vehicle_part.cpp @@ -463,7 +463,7 @@ bool vehicle_part::is_tank() const bool vehicle_part::is_battery() const { - return base.is_magazine() && base.ammo_type() == "battery"; + return base.is_magazine() && base.ammo_types().count( ammotype( "battery" ) ); } bool vehicle_part::is_reactor() const diff --git a/src/worldfactory.cpp b/src/worldfactory.cpp index 9ecd26389486a..dd052b4140d5c 100644 --- a/src/worldfactory.cpp +++ b/src/worldfactory.cpp @@ -770,15 +770,15 @@ int worldfactory::show_worldgen_tab_modselection( const catacurses::window &win, input_context ctxt( "MODMANAGER_DIALOG" ); ctxt.register_updown(); - ctxt.register_action( "LEFT", _( "Switch to other list" ) ); - ctxt.register_action( "RIGHT", _( "Switch to other list" ) ); + ctxt.register_action( "LEFT", translate_marker( "Switch to other list" ) ); + ctxt.register_action( "RIGHT", translate_marker( "Switch to other list" ) ); ctxt.register_action( "HELP_KEYBINDINGS" ); ctxt.register_action( "QUIT" ); ctxt.register_action( "NEXT_CATEGORY_TAB" ); ctxt.register_action( "PREV_CATEGORY_TAB" ); ctxt.register_action( "NEXT_TAB" ); ctxt.register_action( "PREV_TAB" ); - ctxt.register_action( "CONFIRM", _( "Activate / deactivate mod" ) ); + ctxt.register_action( "CONFIRM", translate_marker( "Activate / deactivate mod" ) ); ctxt.register_action( "ADD_MOD" ); ctxt.register_action( "REMOVE_MOD" ); ctxt.register_action( "SAVE_DEFAULT_MODS" ); diff --git a/tests/bionics_test.cpp b/tests/bionics_test.cpp index 60965fb37c83e..1f8b27c9dc34c 100644 --- a/tests/bionics_test.cpp +++ b/tests/bionics_test.cpp @@ -80,7 +80,7 @@ static void test_consumable_ammo( player &p, std::string &itemname, bool when_em INFO( "consume \'" + it.tname() + "\' with " + std::to_string( it.ammo_remaining() ) + " charges" ); REQUIRE( p.can_consume( it ) == when_empty ); - it.ammo_set( it.ammo_type()->default_ammotype(), -1 ); // -1 -> full + it.ammo_set( it.ammo_default(), -1 ); // -1 -> full INFO( "consume \'" + it.tname() + "\' with " + std::to_string( it.ammo_remaining() ) + " charges" ); REQUIRE( p.can_consume( it ) == when_full ); } diff --git a/tests/reload_magazine.cpp b/tests/reload_magazine.cpp index 2c2bcac58261e..5ec8277612d73 100644 --- a/tests/reload_magazine.cpp +++ b/tests/reload_magazine.cpp @@ -27,7 +27,7 @@ TEST_CASE( "reload_magazine", "[magazine] [visitable] [item] [item_location]" ) const itype_id bad_ammo = "9mm"; // any type of incompatible ammo const itype_id mag_id = "stanag30"; // must be set to default magazine const itype_id bad_mag = "glockmag"; // any incompatible magazine - const int mag_cap = 30; + const int mag_cap = 30; CHECK( ammo_id != alt_ammo ); CHECK( ammo_id != bad_ammo ); @@ -51,7 +51,7 @@ TEST_CASE( "reload_magazine", "[magazine] [visitable] [item] [item_location]" ) CHECK( p.can_reload( mag, ammo_id ) == true ); CHECK( p.can_reload( mag, alt_ammo ) == true ); CHECK( p.can_reload( mag, bad_ammo ) == false ); - CHECK( mag.ammo_type() == gun_ammo ); + CHECK( mag.ammo_types().count( gun_ammo ) ); CHECK( mag.ammo_capacity() == mag_cap ); CHECK( mag.ammo_current() == "null" ); CHECK( mag.ammo_data() == nullptr ); @@ -184,7 +184,7 @@ TEST_CASE( "reload_magazine", "[magazine] [visitable] [item] [item_location]" ) CHECK( gun.magazine_default() == mag_id ); CHECK( gun.magazine_compatible().count( mag_id ) == 1 ); CHECK( gun.magazine_current() == nullptr ); - CHECK( gun.ammo_type() == gun_ammo ); + CHECK( gun.ammo_types().count( gun_ammo ) ); CHECK( gun.ammo_capacity() == 0 ); CHECK( gun.ammo_remaining() == 0 ); CHECK( gun.ammo_current() == "null" ); @@ -211,7 +211,7 @@ TEST_CASE( "reload_magazine", "[magazine] [visitable] [item] [item_location]" ) REQUIRE( gun.magazine_current()->typeId() == mag_id ); } AND_THEN( "the ammo type for the gun remains unchanged" ) { - REQUIRE( gun.ammo_type() == gun_ammo ); + REQUIRE( gun.ammo_types().count( gun_ammo ) ); } AND_THEN( "the ammo capacity is correctly set" ) { REQUIRE( gun.ammo_capacity() == mag_cap ); @@ -238,7 +238,7 @@ TEST_CASE( "reload_magazine", "[magazine] [visitable] [item] [item_location]" ) REQUIRE( gun.magazine_current()->typeId() == mag_id ); } AND_THEN( "the ammo type for the gun remains unchanged" ) { - REQUIRE( gun.ammo_type() == gun_ammo ); + REQUIRE( gun.ammo_types().count( gun_ammo ) ); } AND_THEN( "the ammo capacity is correctly set" ) { REQUIRE( gun.ammo_capacity() == mag_cap ); diff --git a/tests/reloading_test.cpp b/tests/reloading_test.cpp index cb4237ead387b..aa75e9ebef4ae 100644 --- a/tests/reloading_test.cpp +++ b/tests/reloading_test.cpp @@ -89,10 +89,10 @@ TEST_CASE( "reload_gun_with_swappable_magazine", "[reload],[gun]" ) const item mag( "glockmag", 0, 0 ); const cata::optional &magazine_type = mag.type->magazine; REQUIRE( magazine_type ); - REQUIRE( ammo_type->type.count( magazine_type->type ) != 0 ); + REQUIRE( magazine_type->type.count( ammo_type->type ) != 0 ); item &gun = dummy.i_add( item( "glock_19", 0, item::default_charges_tag{} ) ); - REQUIRE( ammo_type->type.count( gun.ammo_type() ) != 0 ); + REQUIRE( gun.ammo_types().count( ammo_type->type ) != 0 ); gun.put_in( mag ); @@ -195,7 +195,7 @@ TEST_CASE( "automatic_reloading_action", "[reload],[gun]" ) item &mag = dummy.i_add( item( "glockmag", 0, 0 ) ); const cata::optional &magazine_type = mag.type->magazine; REQUIRE( magazine_type ); - REQUIRE( ammo_type->type.count( magazine_type->type ) != 0 ); + REQUIRE( magazine_type->type.count( ammo_type->type ) != 0 ); REQUIRE( mag.ammo_remaining() == 0 ); dummy.weapon = item( "glock_19", 0, 0 ); @@ -230,7 +230,7 @@ TEST_CASE( "automatic_reloading_action", "[reload],[gun]" ) item &mag2 = dummy.i_add( item( "glockbigmag", 0, 0 ) ); const cata::optional &magazine_type2 = mag2.type->magazine; REQUIRE( magazine_type2 ); - REQUIRE( ammo_type->type.count( magazine_type2->type ) != 0 ); + REQUIRE( magazine_type2->type.count( ammo_type->type ) != 0 ); REQUIRE( mag2.ammo_remaining() == 0 ); WHEN( "the player triggers auto reload" ) { diff --git a/tests/vehicle_turrets.cpp b/tests/vehicle_turrets.cpp index 1e21b164ec47e..caf7b46019630 100644 --- a/tests/vehicle_turrets.cpp +++ b/tests/vehicle_turrets.cpp @@ -46,7 +46,7 @@ static const vpart_info *biggest_tank( const ammotype &ammo ) } const itype *fuel = item::find_type( vp.fuel_type ); - if( fuel->ammo && fuel->ammo->type.count( ammo ) ) { + if( fuel->ammo && fuel->ammo->type == ammo ) { res.push_back( &vp ); } } @@ -74,7 +74,7 @@ TEST_CASE( "vehicle_turret", "[vehicle] [gun] [magazine] [.]" ) REQUIRE( veh->install_part( point( 0, 0 ), vpart_id( "storage_battery" ), true ) >= 0 ); veh->charge_battery( 10000 ); - auto ammo = veh->turret_query( veh->parts[idx] ).base()->ammo_type(); + auto ammo = ammotype( veh->turret_query( veh->parts[idx] ).base()->ammo_default() ); if( veh->part_flag( idx, "USE_TANKS" ) ) { auto *tank = biggest_tank( ammo ); diff --git a/tools/format/getpost.h b/tools/format/getpost.h index 3e584cb837db8..407449bbe19d1 100644 --- a/tools/format/getpost.h +++ b/tools/format/getpost.h @@ -44,7 +44,7 @@ inline std::string urlDecode( std::string str ) tmp[2] = str[i + 1]; tmp[3] = str[i + 2]; tmp[4] = '\0'; - tmpchar = ( char )strtol( tmp, NULL, 0 ); + tmpchar = static_cast( strtol( tmp, NULL, 0 ) ); temp += tmpchar; i += 2; continue; @@ -116,7 +116,8 @@ inline void initializePost( std::map &Post ) Post.clear(); return; } - if( fread( buffer, sizeof( char ), content_length, stdin ) != ( unsigned int )content_length ) { + if( fread( buffer, sizeof( char ), content_length, + stdin ) != static_cast( content_length ) ) { Post.clear(); return; }