diff --git a/data/json/construction/covers.json b/data/json/construction/covers.json new file mode 100644 index 0000000000000..db5ecc73e26b1 --- /dev/null +++ b/data/json/construction/covers.json @@ -0,0 +1,65 @@ +[ + { + "type": "construction", + "id": "constr_plank_over_gap", + "group": "place_plank", + "category": "CONSTRUCT", + "required_skills": [ [ "fabrication", 0 ] ], + "time": "1 s", + "components": [ [ [ "2x4", 1 ] ] ], + "pre_terrain": "t_open_air", + "pre_note": "Must be supported on at least two opposite sides.", + "pre_special": [ "check_opposite_floor_pair" ], + "post_terrain": "t_plank_gap" + }, + { + "type": "construction", + "id": "cover_pit_grass", + "group": "place_cover_pit", + "category": "CONSTRUCT", + "required_skills": [ [ "survival", 2 ] ], + "time": "5 m", + "qualities": [ [ { "id": "HAMMER", "level": 1 } ] ], + "components": [ [ [ "straw_pile", 12 ] ], [ [ "cordage_long", 8, "LIST" ] ], [ [ "splinter", 16 ], [ "nails", 16, "LIST" ] ] ], + "pre_terrain": "t_pit", + "post_terrain": "t_pit_conceal_grass" + }, + { + "type": "construction", + "id": "cover_pit_spiked_grass", + "group": "place_cover_pit", + "category": "CONSTRUCT", + "required_skills": [ [ "survival", 2 ] ], + "time": "5 m", + "qualities": [ [ { "id": "HAMMER", "level": 1 } ] ], + "components": [ [ [ "straw_pile", 12 ] ], [ [ "cordage_long", 8, "LIST" ] ], [ [ "splinter", 16 ], [ "nails", 16, "LIST" ] ] ], + "pre_terrain": "t_pit_spiked", + "post_terrain": "t_pit_spiked_conceal_grass" + }, + { + "type": "construction", + "id": "cover_pit_glass_grass", + "group": "place_cover_pit", + "category": "CONSTRUCT", + "required_skills": [ [ "survival", 2 ] ], + "time": "5 m", + "qualities": [ [ { "id": "HAMMER", "level": 1 } ] ], + "components": [ [ [ "straw_pile", 12 ] ], [ [ "cordage_long", 8, "LIST" ] ], [ [ "splinter", 16 ], [ "nails", 16, "LIST" ] ] ], + "pre_terrain": "t_pit_glass", + "post_terrain": "t_pit_glass_conceal_grass" + }, + { + "type": "construction", + "id": "cover_air_grass", + "group": "place_cover_air", + "category": "CONSTRUCT", + "required_skills": [ [ "survival", 2 ] ], + "time": "5 m", + "qualities": [ [ { "id": "HAMMER", "level": 1 } ] ], + "components": [ [ [ "straw_pile", 12 ] ], [ [ "cordage_long", 8, "LIST" ] ], [ [ "splinter", 16 ], [ "nails", 16, "LIST" ] ] ], + "pre_terrain": "t_open_air", + "pre_special": [ "check_opposite_floor_pair" ], + "pre_orth": [ [ "t_grass", "t_grass_dead", "t_dirt", "t_moss", "t_forestfloor", "t_grass_long", "t_grass_tall", "t_grass_golf" ] ], + "post_terrain": "t_air_cover_grass" + } +] diff --git a/data/json/construction/terrain.json b/data/json/construction/terrain.json index 3bca760ef5d3a..ccdd61d3e120c 100644 --- a/data/json/construction/terrain.json +++ b/data/json/construction/terrain.json @@ -366,5 +366,41 @@ "byproducts": [ { "group": "digging_soil_loam_50L", "count": 5 } ], "activity_level": "EXTRA_EXERCISE", "do_turn_special": "do_turn_shovel" + }, + { + "type": "construction", + "id": "constr_water_channel_still", + "skill": "survival", + "group": "dig_a_water_channel", + "category": "CONSTRUCT", + "difficulty": 1, + "time": "20m", + "on_display": true, + "qualities": [ { "id": "DIG", "level": 1 } ], + "pre_flags": [ "DIGGABLE", "FLAT" ], + "pre_special": [ "check_empty" ], + "pre_orth": "t_water_sh", + "post_terrain": "t_water_sh", + "byproducts": [ { "group": "digging_soil_loam_50L", "count": 5 } ], + "activity_level": "EXTRA_EXERCISE", + "do_turn_special": "do_turn_shovel" + }, + { + "type": "construction", + "id": "constr_water_channel_salt", + "skill": "survival", + "group": "dig_a_water_channel", + "category": "CONSTRUCT", + "difficulty": 1, + "time": "20m", + "on_display": true, + "qualities": [ { "id": "DIG", "level": 1 } ], + "pre_flags": [ "DIGGABLE", "FLAT" ], + "pre_special": [ "check_empty" ], + "pre_orth": "t_swater_sh", + "post_terrain": "t_swater_sh", + "byproducts": [ { "group": "digging_soil_loam_50L", "count": 5 } ], + "activity_level": "EXTRA_EXERCISE", + "do_turn_special": "do_turn_shovel" } ] diff --git a/data/json/construction_group.json b/data/json/construction_group.json index 8fcc399629f04..ea0ed79e62127 100644 --- a/data/json/construction_group.json +++ b/data/json/construction_group.json @@ -1370,6 +1370,21 @@ "id": "place_planer", "name": "Place Planer" }, + { + "type": "construction_group", + "id": "place_plank", + "name": "Place Plank Across Gap" + }, + { + "type": "construction_group", + "id": "place_cover_air", + "name": "Place Cover Over Gap" + }, + { + "type": "construction_group", + "id": "place_cover_pit", + "name": "Place Cover Over Pit" + }, { "type": "construction_group", "id": "place_jointer", diff --git a/data/json/furniture_and_terrain/terrain-roofs.json b/data/json/furniture_and_terrain/terrain-roofs.json index 2b5c2f0625c8f..12a0ffd5569b3 100644 --- a/data/json/furniture_and_terrain/terrain-roofs.json +++ b/data/json/furniture_and_terrain/terrain-roofs.json @@ -182,7 +182,7 @@ "color": "cyan", "move_cost": 2, "//": "TODO: Should break when you step on it if there's no supporting terrain below, might want some warning akin to examine_action ledge", - "flags": [ "TRANSPARENT", "TRANSPARENT_FLOOR" ], + "flags": [ "TRANSPARENT", "TRANSPARENT_FLOOR", "FRAGILE" ], "bash": { "str_min": 3, "str_max": 6, @@ -192,6 +192,19 @@ "items": [ { "item": "glass_shard", "count": [ 21, 29 ] } ] } }, + { + "type": "terrain", + "id": "t_ice", + "name": "thin ice", + "description": "A thin,transparent sheet of ice is here. Watch your step.", + "symbol": "o", + "looks_like": "t_linoleum_white", + "color": "cyan", + "move_cost": 2, + "//": "TODO: Should break when you step on it if there's no supporting terrain below, might want some warning akin to examine_action ledge", + "flags": [ "TRANSPARENT", "TRANSPARENT_FLOOR", "FRAGILE" ], + "bash": { "str_min": 3, "str_max": 6, "sound": "crunch!", "sound_fail": "crack!", "ter_set": "t_null" } + }, { "type": "terrain", "id": "t_rock_roof", diff --git a/data/json/furniture_and_terrain/terrain-traps.json b/data/json/furniture_and_terrain/terrain-traps.json index ad08e6ca9c07d..e700d8b11f849 100644 --- a/data/json/furniture_and_terrain/terrain-traps.json +++ b/data/json/furniture_and_terrain/terrain-traps.json @@ -10,6 +10,25 @@ "flags": [ "TRANSPARENT", "DIGGABLE", "DIGGABLE_CAN_DEEPEN" ], "bash": { "sound": "thump", "ter_set": "t_null", "str_min": 50, "str_max": 100, "str_min_supported": 100, "bash_below": true } }, + { + "type": "terrain", + "id": "t_plank_gap", + "name": "plank", + "description": "A 2x4 precariously positioned. Mind the gap.", + "looks_like": "2x4", + "symbol": "l", + "color": "yellow", + "move_cost": 2, + "flags": [ "TRANSPARENT", "TRANSPARENT_FLOOR", "FRAGILE" ], + "bash": { + "sound": "thump", + "ter_set": "t_open_air", + "str_min": 12, + "str_max": 30, + "items": [ { "item": "plank_short", "count": [ 1, 2 ] } ] + }, + "examine_action": "ledge" + }, { "type": "terrain", "id": "t_pit", @@ -40,15 +59,21 @@ { "type": "terrain", "id": "t_pit_covered", - "name": "covered pit", + "name": "board over a pit", "description": "A deep pit with a plank placed across it, looks sturdy enough to cross safely. The plank could be removed to make it dangerous again.", "symbol": "#", "color": "light_red", "connect_groups": "PIT_DEEP", "connects_to": "PIT_DEEP", "move_cost": 2, - "flags": [ "TRANSPARENT", "ROAD" ], - "bash": { "sound": "thump", "ter_set": "t_null", "str_min": 40, "str_max": 100, "str_min_supported": 100, "bash_below": true }, + "flags": [ "TRANSPARENT", "ROAD", "OPEN_PIT", "FRAGILE" ], + "bash": { + "sound": "crack", + "ter_set": "t_pit", + "str_min": 12, + "str_max": 30, + "items": [ { "item": "plank_short", "count": [ 1, 2 ] } ] + }, "examine_action": "pit_covered" }, { @@ -69,15 +94,21 @@ { "type": "terrain", "id": "t_pit_spiked_covered", - "name": "covered spiked pit", + "name": "board over a spiked pit", "description": "Menacing with sharp spears along the bottom, this pit has a plank across it to allow someone or something to cross safely. The plank could be removed to make it dangerous again.", "symbol": "#", "color": "light_red", "connect_groups": "PIT_DEEP", "connects_to": "PIT_DEEP", "move_cost": 2, - "flags": [ "TRANSPARENT", "ROAD" ], - "bash": { "sound": "thump", "ter_set": "t_null", "str_min": 40, "str_max": 100, "str_min_supported": 100, "bash_below": true }, + "flags": [ "TRANSPARENT", "ROAD", "OPEN_PIT", "FRAGILE" ], + "bash": { + "sound": "crack", + "ter_set": "t_pit_spiked", + "str_min": 12, + "str_max": 30, + "items": [ { "item": "plank_short", "count": [ 1, 2 ] } ] + }, "examine_action": "pit_covered" }, { @@ -98,17 +129,119 @@ { "type": "terrain", "id": "t_pit_glass_covered", - "name": "covered glass pit", + "name": "board over a glass pit", "description": "A plank has been placed carefully to allow traversal over this ditch full of large glass shards. The plank could be removed to make it dangerous again.", "symbol": "#", "color": "light_cyan", "connect_groups": "PIT_DEEP", "connects_to": "PIT_DEEP", "move_cost": 2, - "flags": [ "TRANSPARENT", "ROAD" ], - "bash": { "sound": "thump", "ter_set": "t_null", "str_min": 40, "str_max": 100, "str_min_supported": 100, "bash_below": true }, + "flags": [ "TRANSPARENT", "ROAD", "OPEN_PIT", "FRAGILE" ], + "bash": { + "sound": "crack", + "ter_set": "t_pit_glass", + "str_min": 12, + "str_max": 30, + "items": [ { "item": "plank_short", "count": [ 1, 2 ] } ] + }, "examine_action": "pit_covered" }, + { + "type": "terrain", + "id": "t_pit_glass_conceal_grass", + "looks_like": "t_grass_dead", + "name": "concealed glass pit", + "description": "A cover of grass has been placed carefully over this ditch full of large glass shards. Watch your step.", + "symbol": ".", + "color": "brown", + "connect_groups": "PIT_DEEP", + "connects_to": "PIT_DEEP", + "move_cost": 2, + "flags": [ "TRANSPARENT", "ROAD", "FRAGILE", "PLAYER_MADE" ], + "bash": { + "sound": "snap!", + "ter_set": "t_pit_glass", + "str_min": 1, + "str_max": 2, + "items": [ + { "item": "straw_pile", "count": [ 6, 11 ] }, + { "item": "cordage_6", "count": [ 0, 24 ] }, + { "item": "cordage_36", "count": [ 0, 4 ] } + ] + } + }, + { + "type": "terrain", + "id": "t_pit_spiked_conceal_grass", + "looks_like": "t_grass_dead", + "name": "concealed spike pit", + "description": "A cover of grass has been placed carefully over this ditch full of upright spears. Watch your step.", + "symbol": ".", + "color": "brown", + "connect_groups": "PIT_DEEP", + "connects_to": "PIT_DEEP", + "move_cost": 2, + "flags": [ "TRANSPARENT", "ROAD", "FRAGILE", "PLAYER_MADE" ], + "bash": { + "sound": "snap!", + "ter_set": "t_pit_spiked", + "str_min": 1, + "str_max": 2, + "items": [ + { "item": "straw_pile", "count": [ 6, 11 ] }, + { "item": "cordage_6", "count": [ 0, 24 ] }, + { "item": "cordage_36", "count": [ 0, 4 ] } + ] + } + }, + { + "type": "terrain", + "id": "t_pit_conceal_grass", + "looks_like": "t_grass_dead", + "name": "concealed pit", + "description": "A cover of grass has been placed carefully over this ditch. Watch your step.", + "symbol": ".", + "color": "brown", + "connect_groups": "PIT_DEEP", + "connects_to": "PIT_DEEP", + "move_cost": 2, + "flags": [ "TRANSPARENT", "ROAD", "FRAGILE", "PLAYER_MADE" ], + "bash": { + "sound": "snap!", + "ter_set": "t_pit", + "str_min": 1, + "str_max": 2, + "items": [ + { "item": "straw_pile", "count": [ 6, 11 ] }, + { "item": "cordage_6", "count": [ 0, 24 ] }, + { "item": "cordage_36", "count": [ 0, 4 ] } + ] + } + }, + { + "type": "terrain", + "id": "t_air_cover_grass", + "looks_like": "t_grass_dead", + "name": "concealed gap", + "description": "A cover of grass has been placed carefully over this gap. Watch your step.", + "symbol": ".", + "color": "brown", + "connect_groups": "PIT_DEEP", + "connects_to": "PIT_DEEP", + "move_cost": 2, + "flags": [ "TRANSPARENT", "FRAGILE", "PLAYER_MADE" ], + "bash": { + "sound": "snap!", + "ter_set": "t_open_air", + "str_min": 1, + "str_max": 2, + "items": [ + { "item": "straw_pile", "count": [ 6, 11 ] }, + { "item": "cordage_6", "count": [ 0, 24 ] }, + { "item": "cordage_36", "count": [ 0, 4 ] } + ] + } + }, { "type": "terrain", "id": "t_pit_BEM_human_sample", diff --git a/data/json/furniture_and_terrain/terrain-windows.json b/data/json/furniture_and_terrain/terrain-windows.json index 521f97f6be129..48506df64466d 100644 --- a/data/json/furniture_and_terrain/terrain-windows.json +++ b/data/json/furniture_and_terrain/terrain-windows.json @@ -4457,7 +4457,10 @@ "move_cost": 2, "coverage": 0, "examine_action": "ledge", - "deconstruct": { "ter_set": "t_null", "items": [ { "item": "2x4", "count": [ 3, 4 ] }, { "item": "nail", "charges": [ 12, 16 ] } ] }, + "deconstruct": { + "ter_set": "t_open_air", + "items": [ { "item": "2x4", "count": [ 3, 4 ] }, { "item": "nail", "charges": [ 12, 16 ] } ] + }, "bash": { "str_min": 1, "str_max": 1, @@ -4465,7 +4468,7 @@ "sound_fail": "whack!", "sound_vol": 12, "sound_fail_vol": 8, - "ter_set": "t_null", + "ter_set": "t_open_air", "items": [ { "item": "2x4", "count": [ 2, 4 ] } ] }, "flags": [ "TRANSPARENT", "NO_FLOOR", "FLAMMABLE" ] diff --git a/data/json/move_modes.json b/data/json/move_modes.json index 5fdf737e80844..058715a613b46 100644 --- a/data/json/move_modes.json +++ b/data/json/move_modes.json @@ -34,7 +34,8 @@ "sound_multiplier": 1.5, "move_speed_multiplier": 2.0, "stamina_multiplier": 7.0, - "swim_speed_mod": -80 + "swim_speed_mod": -80, + "bash_weight_modifier": 2 }, { "type": "movement_mode", @@ -68,6 +69,7 @@ "move_type": "prone", "sound_multiplier": 0.2, "move_speed_multiplier": 0.2, - "swim_speed_mod": 50 + "swim_speed_mod": 50, + "bash_weight_modifier": 0.25 } ] diff --git a/data/json/requirements/materials.json b/data/json/requirements/materials.json index 58896451ce317..d85f3a0ae94d2 100644 --- a/data/json/requirements/materials.json +++ b/data/json/requirements/materials.json @@ -172,6 +172,12 @@ "//": "Materials used for tying items, primitive bowstrings, and other uses involving string or makeshift cordage, 36 g/90 cm of cordage per unit.", "components": [ [ [ "string_36", 1 ], [ "cordage_36", 1 ], [ "cordage_36_leather", 1 ], [ "cordage_short", 6, "LIST" ] ] ] }, + { + "id": "cordage_long", + "type": "requirement", + "//": "Materials used for tying items, primitive bowstrings, and other uses involving string or makeshift cordage, 36 g/90 cm of cordage per unit.", + "components": [ [ [ "string_36", 1 ], [ "cordage_36", 1 ], [ "cordage_36_leather", 1 ] ] ] + }, { "id": "cordage_short", "type": "requirement", diff --git a/src/character.cpp b/src/character.cpp index 9590207a9687d..73860dc97351b 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -1976,6 +1976,11 @@ move_mode_id Character::current_movement_mode() const return move_mode; } +float Character::fragile_terrain_weight_modifier() const +{ + return move_mode->get_bash_weight_modifier(); +} + bool Character::movement_mode_is( const move_mode_id &mode ) const { return move_mode == mode; @@ -7695,7 +7700,18 @@ int Character::impact( const int force, const tripoint_bub_ms &p ) bool Character::can_fly() { - if( !move_effects( false ) || has_effect( effect_stunned ) ) { + if( !move_effects( false ) ) { + return false; + } + if( flies() ) { + return true; + } + return false; +} + +bool Character::flies() const +{ + if( has_effect( effect_stunned ) ) { return false; } // GLIDE is for artifacts or things like jetpacks that don't care if you're tired or hurt. diff --git a/src/character.h b/src/character.h index c2086565e7b41..e17e0d26fa485 100644 --- a/src/character.h +++ b/src/character.h @@ -43,6 +43,7 @@ #include "item_location.h" #include "memory_fast.h" #include "monster.h" +#include "move_mode.h" #include "pimpl.h" #include "player_activity.h" #include "pocket_type.h" @@ -1063,6 +1064,8 @@ class Character : public Creature, public visitable bool movement_mode_is( const move_mode_id &mode ) const; move_mode_id current_movement_mode() const; + float fragile_terrain_weight_modifier() const override; + bool is_running() const; bool is_walking() const; bool is_crouching() const; @@ -2702,6 +2705,7 @@ class Character : public Creature, public visitable int impact( int force, const tripoint_bub_ms &pos ) override; /** Checks to see if the character is able to use their wings properly */ bool can_fly(); + bool flies() const override; /** Knocks the player to a specified tile */ void knock_back_to( const tripoint_bub_ms &to ) override; diff --git a/src/construction.cpp b/src/construction.cpp index 78d01b57cac18..eec25da8f9cbe 100644 --- a/src/construction.cpp +++ b/src/construction.cpp @@ -180,6 +180,7 @@ static bool check_support( const tripoint_bub_ms & ); // at least two orthogonal supports or from below static bool check_support_below( const tripoint_bub_ms & ); // at least two orthogonal supports at the level below or from below +static bool check_opposite_floor_pair( const tripoint_bub_ms &p ); static bool check_single_support( const tripoint_bub_ms &p ); // Only support from directly below matters static bool check_stable( const tripoint_bub_ms & ); // tile below has a SUPPORTS_ROOF flag @@ -1244,6 +1245,72 @@ static std::string has_pre_flags_colorize( const construction &con ) return colorize( enumerate_as_string( flags_colorized ), color ); } +static bool has_pre_orth( const construction &con, const tripoint_bub_ms &p ) +{ + map &here = get_map(); + // grouped format + if( !con.pre_orth_list.empty() ) { + + // bracket and groups + for( const std::vector &or_group : con.pre_orth_list ) { + + bool group_passed = false; + + // bracket logic or groups + for( const std::string &id : or_group ) { + + bool is_terrain = id.rfind( "t_", 0 ) == 0; + bool is_furniture = id.rfind( "f_", 0 ) == 0; + + for( const point &offset : four_adjacent_offsets ) { + const tripoint_bub_ms q = p + offset; + + if( is_terrain && here.ter( q ) == ter_id( id ) ) { + group_passed = true; + break; + } + if( is_furniture && here.furn( q ) == furn_id( id ) ) { + group_passed = true; + break; + } + } + if( group_passed ) { + break; + } + } + + if( !group_passed ) { + return false; //bracket logic failed at this step, because one AND is not satisfied + } + } + + return true; // all groups passed + } + + // single-ID format --- + if( con.pre_orth.empty() ) { + return true; + } //default if empty + + //else search orth adjacent terrain/furniture + const bool is_terrain = con.pre_orth.rfind( "t_", 0 ) == 0; + const bool is_furniture = con.pre_orth.rfind( "f_", 0 ) == 0; + + for( const point &offset : four_adjacent_offsets ) { + const tripoint_bub_ms q = p + offset; + + if( is_terrain && here.ter( q ) == ter_id( con.pre_orth ) ) { + return true; + } + if( is_furniture && here.furn( q ) == furn_id( con.pre_orth ) ) { + return true; + } + } + + return false; +} + + bool can_construct( const construction &con, const tripoint_bub_ms &p ) { const map &here = get_map(); @@ -1258,8 +1325,9 @@ bool can_construct( const construction &con, const tripoint_bub_ms &p ) } else if( !con.pre_special( p ) ) { // pre-function return false; } - if( !has_pre_terrain( con, p ) || // terrain type - !has_pre_flags( con, f, t ) ) { // flags + if( !has_pre_terrain( con, p ) || // terrain type at tile + !has_pre_flags( con, f, t ) || // flags at tile + !has_pre_orth( con, p ) ) { // require orth terr or furniture return false; } if( !con.post_terrain.empty() ) { // make sure the construction would actually do something @@ -1633,6 +1701,50 @@ bool construct::check_support_below( const tripoint_bub_ms &p ) return num_supports >= 2; } +static bool construct::check_opposite_floor_pair( const tripoint_bub_ms &p ) +{ + map &here = get_map(); + + // floor is not no_floor + auto is_floor = [&]( const tripoint_bub_ms & q ) { + return !here.has_flag( ter_furn_flag::TFLAG_NO_FLOOR, q ); + }; + + // neighbors (orthogonal + diagonal), expressed as point offsets + const point north( 0, -1 ); + const point south( 0, 1 ); + const point east( 1, 0 ); + const point west( -1, 0 ); + + const point northeast( 1, -1 ); + const point southeast( 1, 1 ); + const point southwest( -1, 1 ); + const point northwest( -1, -1 ); + + // Move in bubble coords by adding point offsets (this is what your original code does) + const tripoint_bub_ms N = p + north; + const tripoint_bub_ms S = p + south; + const tripoint_bub_ms E = p + east; + const tripoint_bub_ms W = p + west; + + const tripoint_bub_ms NE = p + northeast; + const tripoint_bub_ms SW = p + southwest; + const tripoint_bub_ms NW = p + northwest; + const tripoint_bub_ms SE = p + southeast; + + bool ns_ok = false; + bool ew_ok = false; + bool nesw_ok = false; + bool nwse_ok = false; + + ns_ok = is_floor( N ) && is_floor( S ); + ew_ok = is_floor( E ) && is_floor( W ); + nesw_ok = is_floor( NE ) && is_floor( SW ); + nwse_ok = is_floor( NW ) && is_floor( SE ); + + return ns_ok || ew_ok || nesw_ok || nwse_ok; +} + bool construct::check_single_support( const tripoint_bub_ms &p ) { map &here = get_map(); @@ -2363,6 +2475,25 @@ void load_construction( const JsonObject &jo ) } } } + if( jo.has_array( "pre_orth" ) ) { + for( const JsonArray &or_group : jo.get_array( "pre_orth" ) ) { + + std::vector group; + + for( const JsonValue &v : or_group ) { + group.push_back( v.get_string() ); + } + + con.pre_orth_list.push_back( group ); + } + } else if( jo.has_string( "pre_orth" ) ) { + con.pre_orth = jo.get_string( "pre_orth" ); + } + // single-ID format + else if( jo.has_string( "pre_orth" ) ) { + con.pre_orth = jo.get_string( "pre_orth" ); + } + con.post_flags = jo.get_tags( "post_flags" ); @@ -2379,6 +2510,7 @@ void load_construction( const JsonObject &jo ) { "check_unblocked", construct::check_unblocked }, { "check_support", construct::check_support }, { "check_support_below", construct::check_support_below }, + { "check_opposite_floor_pair", construct::check_opposite_floor_pair }, { "check_single_support", construct::check_single_support }, { "check_stable", construct::check_stable }, { "check_floor_above", construct::check_floor_above }, diff --git a/src/construction.h b/src/construction.h index 49f07959d5ab9..81ef07abec7df 100644 --- a/src/construction.h +++ b/src/construction.h @@ -81,6 +81,9 @@ struct construction { // Custom constructibility check bool ( *pre_special )( const tripoint_bub_ms & ); std::vector pre_specials; + //optional: require an adjacent orthogonal terrain or furniture + std::vector> pre_orth_list; //combined bracket logic version of below + std::string pre_orth; // e.g., "t_rock_wall" // Custom while constructing effects void ( *do_turn_special )( const tripoint_bub_ms &, Character & ); // Custom after-effects diff --git a/src/creature.cpp b/src/creature.cpp index 2a3ff61ce0df5..44c514f346b6b 100644 --- a/src/creature.cpp +++ b/src/creature.cpp @@ -211,12 +211,47 @@ tripoint_bub_ms Creature::pos_bub( const map &here ) const return here.get_bub( location ); } +void Creature::maybe_break_fragile_underfoot( Creature &cr, const tripoint_bub_ms &p ) +{ + map &here = get_map(); + if( const optional_vpart_position vp = here.veh_at( p ) ) { + return; + } + if( const Character *person = cr.as_character() ) { + if( person->in_vehicle ) { + return; + } + } + if( !here.has_flag( ter_furn_flag::TFLAG_FRAGILE, p ) ) { + return; + } + if( cr.is_hallucination() || cr.flies() || cr.get_weight() < 5_kilogram ) { + return; + } + std::string who_name = cr.disp_name(); + const int weight_to_bash = std::max( 1, static_cast( cr.get_weight() / 10_kilogram ) ); + int bash_strength = static_cast( weight_to_bash * cr.fragile_terrain_weight_modifier() ); + //store terrain name for message + const std::string old_name = here.tername( p ); + //damage fragile terrain + const bash_params res = here.bash( p, bash_strength, false, false, false, nullptr, false ); + // if broken output message + if( res.success ) { + add_msg( m_warning, + string_format( _( "The %s breaks under the weight of %s!" ), + old_name, who_name ) ); + } + return; +} + void Creature::setpos( map &here, const tripoint_bub_ms &p, bool check_gravity/* = true*/ ) { const tripoint_abs_ms old_loc = pos_abs(); set_pos_abs_only( here.get_abs( p ) ); on_move( old_loc ); + if( check_gravity ) { + maybe_break_fragile_underfoot( *this, p ); gravity_check( &here ); } } @@ -227,6 +262,10 @@ void Creature::setpos( const tripoint_abs_ms &p, bool check_gravity/* = true*/ ) set_pos_abs_only( p ); on_move( old_loc ); if( check_gravity ) { + map &here = get_map(); + if( here.inbounds( p ) ) { + maybe_break_fragile_underfoot( *this, here.get_bub( p ) ); + } gravity_check(); } } diff --git a/src/creature.h b/src/creature.h index 8c4533872d94f..d1490b6057430 100644 --- a/src/creature.h +++ b/src/creature.h @@ -323,6 +323,7 @@ class Creature : public viewer } virtual void gravity_check(); virtual void gravity_check( map *here ); + void maybe_break_fragile_underfoot( Creature &cr, const tripoint_bub_ms &p ); void setpos( map &here, const tripoint_bub_ms &p, bool check_gravity = true ); void setpos( const tripoint_abs_ms &p, bool check_gravity = true ); @@ -608,7 +609,14 @@ class Creature : public viewer virtual float fall_damage_mod() const = 0; /** Deals falling/collision damage with terrain/creature at pos */ virtual int impact( int force, const tripoint_bub_ms &pos ) = 0; - + // Used by maybe_break_fragile_underfoot; + virtual float fragile_terrain_weight_modifier() const { + return 1.0; + } + //virtual check for if creature flies + virtual bool flies() const { + return false; + } /** * This function checks the creatures @ref is_dead_state and (if true) calls @ref die. * You can either call this function after hitting this creature, or let the game diff --git a/src/map.cpp b/src/map.cpp index c2f2a77aac079..51baffe2ce6e4 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -4270,7 +4270,12 @@ void map::bash_ter_furn( const tripoint_bub_ms &p, bash_params ¶ms, bool rep set_to_air = roof_of_below_tile; //do not add the roof for the tile below if it was already removed furn_set( p, furn_str_id::NULL_ID() ); - if( repair_missing_ground && ter_below.has_flag( "NATURAL_UNDERGROUND" ) ) { + + //respect recommended terrain setting by bash results! + if( ter_bash.ter_set != ter_str_id( "t_null" ) ) { + ter_set( p, ter_bash.ter_set ); + } //else resort to repairing natural terrain + else if( repair_missing_ground && ter_below.has_flag( "NATURAL_UNDERGROUND" ) ) { ter_set( p, ter_below.roof ); } else { ter_set( p, ter_t_open_air ); diff --git a/src/mapdata.cpp b/src/mapdata.cpp index c9cf1743ab281..3c26a16456813 100644 --- a/src/mapdata.cpp +++ b/src/mapdata.cpp @@ -325,6 +325,9 @@ std::string enum_to_string( ter_furn_flag data ) case ter_furn_flag::TFLAG_WIRED_WALL: return "WIRED_WALL"; case ter_furn_flag::TFLAG_MON_AVOID_STRICT: return "MON_AVOID_STRICT"; case ter_furn_flag::TFLAG_REGION_PSEUDO: return "REGION_PSEUDO"; + case ter_furn_flag::TFLAG_FRAGILE: return "FRAGILE"; + case ter_furn_flag::TFLAG_OPEN_PIT: return "OPEN_PIT"; + case ter_furn_flag::TFLAG_PLAYER_MADE: return "PLAYER_MADE"; // *INDENT-ON* case ter_furn_flag::NUM_TFLAG_FLAGS: diff --git a/src/mapdata.h b/src/mapdata.h index 05ff5bdbee3fe..b497a613c0a1b 100644 --- a/src/mapdata.h +++ b/src/mapdata.h @@ -375,6 +375,9 @@ enum class ter_furn_flag : int { TFLAG_WIRED_WALL, TFLAG_MON_AVOID_STRICT, TFLAG_REGION_PSEUDO, + TFLAG_FRAGILE, + TFLAG_OPEN_PIT, + TFLAG_PLAYER_MADE, NUM_TFLAG_FLAGS }; diff --git a/src/monster.cpp b/src/monster.cpp index c457884bf8a06..66f823f87d672 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -4219,6 +4219,10 @@ std::function monster::get_path_avoid() const if( rl_dist( p, pos_bub() ) <= radius && get_creature_tracker().creature_at( p ) ) { return true; } + if( should_avoid_fragile_tile( this, here, p ) ) { + return true; + } + return false; }; } diff --git a/src/monster.h b/src/monster.h index a000f38668cf5..f7ceb62c8804f 100644 --- a/src/monster.h +++ b/src/monster.h @@ -173,7 +173,7 @@ class monster : public Creature bool digging() const override; // digs() or can_dig() and diggable terrain bool can_dig() const; bool digs() const; - bool flies() const; + bool flies() const override; bool climbs() const; bool swims() const; diff --git a/src/move_mode.cpp b/src/move_mode.cpp index 7bb3b5a7cdd74..5a7178c09143b 100644 --- a/src/move_mode.cpp +++ b/src/move_mode.cpp @@ -75,6 +75,8 @@ void move_mode::load( const JsonObject &jo, std::string_view/*src*/ ) optional( jo, was_loaded, "move_speed_multiplier", _move_speed_mult, 1.0 ); optional( jo, was_loaded, "mech_power_use", _mech_power_use, 2 ); optional( jo, was_loaded, "swim_speed_mod", _swim_speed_mod, 0 ); + optional( jo, was_loaded, "bash_weight_modifier", _bash_weight_modifier, + numeric_bound_reader( 0.0f ), 1.0 ); optional( jo, was_loaded, "stop_hauling", _stop_hauling ); } @@ -191,6 +193,11 @@ int move_mode::swim_speed_mod() const return _swim_speed_mod; } +float move_mode::get_bash_weight_modifier() const +{ + return _bash_weight_modifier; +} + nc_color move_mode::panel_color() const { return _panel_color; diff --git a/src/move_mode.h b/src/move_mode.h index 10259914cf3be..413a17192bf19 100644 --- a/src/move_mode.h +++ b/src/move_mode.h @@ -58,10 +58,12 @@ class move_mode float _move_speed_mult = 0.0f; float _sound_multiplier = 0.0f; float _stamina_multiplier = 0.0f; + float _bash_weight_modifier = 1.0f; int _mech_power_use = 0; int _swim_speed_mod = 0; + nc_color _panel_color; nc_color _symbol_color; uint32_t _panel_letter; @@ -92,6 +94,7 @@ class move_mode units::energy mech_power_use() const; int swim_speed_mod() const; + float get_bash_weight_modifier() const; nc_color panel_color() const; nc_color symbol_color() const; diff --git a/src/npc.cpp b/src/npc.cpp index ee8fb7979d8cb..4d7d888c7698f 100644 --- a/src/npc.cpp +++ b/src/npc.cpp @@ -3532,6 +3532,9 @@ std::function npc::get_path_avoid() const if( here.is_open_air( p ) ) { return true; } + if( should_avoid_fragile_tile( this, here, p ) ) { + return true; + } if( rules.has_flag( ally_rule::hold_the_line ) && rl_dist( p, get_avatar().pos_bub() ) == 1 && ( here.close_door( p, true, true ) || @@ -3549,6 +3552,7 @@ std::function npc::get_path_avoid() const }; } + mfaction_id npc::get_monster_faction() const { if( my_fac ) { diff --git a/src/pathfinding.cpp b/src/pathfinding.cpp index 05915632e7679..57a2f1c0053d6 100644 --- a/src/pathfinding.cpp +++ b/src/pathfinding.cpp @@ -15,6 +15,7 @@ #include "cata_utility.h" #include "coordinates.h" #include "creature.h" +#include "npc.h" #include "damage.h" #include "debug.h" #include "game.h" @@ -23,9 +24,11 @@ #include "map_scale_constants.h" #include "mapdata.h" #include "maptile_fwd.h" +#include "monster.h" #include "point.h" #include "submap.h" #include "trap.h" +#include "units.h" #include "veh_type.h" #include "vehicle.h" #include "vpart_position.h" @@ -675,3 +678,68 @@ bool pathfinding_target::contains( const tripoint_bub_ms &p ) const } return square_dist( center, p ) <= r; } + +bool should_avoid_fragile_tile( const Creature *who, const map &m, const tripoint_bub_ms &p ) +{ + if( !who ) { + return false; + } + + //early break if not fragile for some reason + if( !m.has_flag_ter_or_furn( ter_furn_flag::TFLAG_FRAGILE, p ) ) { + return false; + } + const bool is_seethrough = m.has_flag_ter_or_furn( ter_furn_flag::TFLAG_TRANSPARENT_FLOOR, p ); + const bool is_pit_with_board = m.has_flag_ter_or_furn( ter_furn_flag::TFLAG_OPEN_PIT, p ); + const bool is_player_pitfall = m.has_flag_ter_or_furn( ter_furn_flag::TFLAG_PLAYER_MADE, p ); + + // if transparent floor or player made or board pit + if( !( is_seethrough || is_pit_with_board || is_player_pitfall ) ) { + return false; + } + + //NPCs and smart monsters avoid transparent + bool smart = false; + //friendly npcs and monsters avoid our pit traps + bool ally = false; + + if( who->is_npc() ) { + smart = true; + + const npc *g = dynamic_cast( who ); + if( g && g->is_player_ally() ) { + ally = true; + } + + } else if( who->is_monster() ) { + const monster *mon = static_cast( who ); + if( mon->has_mind() ) { + smart = true; + } + if( mon->attitude_raw_string( mon->attitude_to( get_player_character() ) ) == "friendly" ) { + ally = true; + } + } + + // if they cant see they will step on it + if( !who->sees( m, p ) ) { + return false; + } + + if( ( is_seethrough || is_pit_with_board ) && smart ) { + // smart creatures can tell if they will break the seethrough floor or board + const int effective_bash = who->get_weight() / 10_kilogram; + const int bash_min = m.bash_resistance( p ); + + // will creature break? + if( effective_bash > bash_min ) { + return true; + } + } + // allied creatures don't fall through our traps + // avoids player frustration when using them for defense + if( ally && is_player_pitfall ) { + return true; + } + return false; +} diff --git a/src/pathfinding.h b/src/pathfinding.h index e5a2ee4a23015..d43c4a5bfde2b 100644 --- a/src/pathfinding.h +++ b/src/pathfinding.h @@ -8,10 +8,14 @@ #include #include "coordinates.h" +#include "creature.h" +#include "map.h" #include "mdarray.h" #include "point.h" #include "type_id.h" + + enum class creature_size : int; // An attribute of a particular map square that is of interest in pathfinding. @@ -190,4 +194,6 @@ struct pathfinding_target { } }; +bool should_avoid_fragile_tile( const Creature *who, const map &m, const tripoint_bub_ms &p ); + #endif // CATA_SRC_PATHFINDING_H diff --git a/src/vehicle.cpp b/src/vehicle.cpp index aab5acb1f810c..4ff709cd9999d 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -828,6 +828,101 @@ void vehicle::drive_to_local_target( map *here, const tripoint_abs_ms &target, selfdrive( *here, turn_x, accel_y ); } +void vehicle::damage_fragile_under_vehicle( map &here ) +{ + // total weight + const int veh_weight = weight_on_wheels( here ) / 1_kilogram; + // wheel coords + std::vector wheel_contacts; + wheel_contacts.reserve( wheelcache.size() ); + for( int i : wheelcache ) { + const vehicle_part &vp = part( i ); + const tripoint_bub_ms p = bub_part_pos( here, i ); + if( vp.is_broken() || here.has_flag_ter( ter_furn_flag::TFLAG_NO_FLOOR, p ) ) { + continue; + } + wheel_contacts.push_back( p ); + } + + add_msg( m_info, "vehicle '%s' total weight: %d kg, wheels: %d", + name, veh_weight, static_cast( wheel_contacts.size() ) ); + + //if the vehicle has no wheel contacts, have it crush the tiles with its body + if( wheel_contacts.empty() ) { + + // no duplicate body part positions + std::unordered_set unique_tiles; + unique_tiles.reserve( parts.size() ); + int structural_parts = 0; + for( size_t i = 0; i < parts.size(); i++ ) { + const vehicle_part &vp = parts[i]; + if( vp.removed || vp.is_broken() ) { + continue; + } + + // count structural parts to compensate for decorative parts creating excess support + if( vp.info().location.str() == "structure" ) { + ++structural_parts; + } + + + unique_tiles.insert( bub_part_pos( here, static_cast( i ) ) ); + } + if( unique_tiles.empty() ) { + return; // No physical body tiles + } + + if( structural_parts <= 0 ) { + structural_parts = 1; + } + //if a vehicle is supported by non-structural collision fields, compensate + // by multiplying the final damage by the total tiles "under" the vehicle + // over the number of actual structural tiles + const double stress_multiplier = static_cast( unique_tiles.size() ) / structural_parts; + + //to vector for iteration + std::vector body_contacts( unique_tiles.begin(), unique_tiles.end() ); + + //unsupported tiles of the vehicle should not count towards the divided weight of the vehicle + int supported_tiles = 0; + for( const tripoint_bub_ms &p : body_contacts ) { + if( !here.has_flag_ter_or_furn( ter_furn_flag::TFLAG_NO_FLOOR, p ) ) { + ++supported_tiles; + } + } + if( supported_tiles <= 0 ) { + return; + } + const int kg_per_tile = static_cast( ( veh_weight / supported_tiles ) * stress_multiplier ); + //add_msg(m_info, "avg body load per tile: %d kg and %d supported tiles. %d sturctural parts and %d total parts", kg_per_tile,supported_tiles, structural_parts, unique_tiles.size()); + + for( const tripoint_bub_ms &p : body_contacts ) { + const int bash_strength = kg_per_tile / 10; + if( here.has_flag_ter( ter_furn_flag::TFLAG_FRAGILE, p ) && bash_strength > 0 ) { + //add_msg(m_info, "bashing fragile terrain at %s, strength=%d", p.to_string(), bash_strength); + + here.bash( p, bash_strength, false, false, false, this, false ); + } else if( here.has_flag_ter_or_furn( ter_furn_flag::TFLAG_FRAGILE, p ) ) { + //add_msg(m_info, "bashing fragile terrain at %s, strength=%d", p.to_string(), bash_strength); + } + } + return; + } else { + //distribute load per wheel + const int bash_strength = ( veh_weight / wheel_contacts.size() ) / 10; + //bash to fragile tiles under each wheel + for( const tripoint_bub_ms &p : wheel_contacts ) { + if( here.has_flag_ter_or_furn( ter_furn_flag::TFLAG_FRAGILE, p ) && bash_strength > 0 ) { + //add_msg(m_info, "bashing fragile terrain at %s, strength=%d", p.to_string(), bash_strength); + here.bash( p, bash_strength, false, false, false, this, false ); + } + } + } + return; +} + + + bool vehicle::precollision_check( units::angle &angle, map &here, bool follow_protocol ) { if( !precollision_on ) { diff --git a/src/vehicle.h b/src/vehicle.h index 7753ad632ec22..e727c8f72c9cf 100644 --- a/src/vehicle.h +++ b/src/vehicle.h @@ -2179,6 +2179,7 @@ class vehicle vehicle *act_on_map( map &here ); // check if the vehicle should be falling or is in water void check_falling_or_floating(); + void damage_fragile_under_vehicle( map &here ); /** Precalculate vehicle turn. Counts wheels that will land on ter_flag_to_check * new_turn_dir - turn direction to calculate diff --git a/src/vehicle_move.cpp b/src/vehicle_move.cpp index 397ba8d6f562b..da1ece2a8e0c2 100644 --- a/src/vehicle_move.cpp +++ b/src/vehicle_move.cpp @@ -2150,6 +2150,9 @@ void vehicle::check_falling_or_floating() return; } + //damage fragile terrain before doing other falling stuff + damage_fragile_under_vehicle( here ); + is_falling = true; is_flying = false;