From d99ad585d5db3a2944f5eb86dcd6d2fef393aa84 Mon Sep 17 00:00:00 2001 From: Bardcore <72810820+acssimpson@users.noreply.github.com> Date: Sat, 22 Jan 2022 02:00:05 -0600 Subject: [PATCH] Changes to Web Rope, Web Diver mutations and new Web Bridge mutation with supporting suspension mechanics. (#1234) * Changes to Web Rope and Web Diver mutations changes to web rope and web diver to allow for more unique benefits for become a spoder. * Added WEB BRIDGE MECHANICS and fixed some linting problems Added a new mutation called Arachnitect, which lets you spin cool web bridges between ledges Included a suspension mechanic to let you 'hang' tiles between other tiles, and get destroyed if they lose a logical support system. linting changes. * Double entry in the header file, whoops * Removing the doubled function from the previous double header entry whoops * Installed AStyle plugin, made changes to all the cpp files to match standard * Never will the formatting changes end * I guess we use two spaces after periods in json files. * Update src/trapfunc.cpp Co-authored-by: Olanti * Update src/iexamine.cpp Co-authored-by: Olanti * Update map.cpp * Review feedback changes. * Un removing the things I just removed, because they broke my build when I removed them * Added generic mutation usage functions, switch rope climb to >, optimized t_web_bridge listings. * switched ropes to no longer be grabbable, juggled mutation tree for Spiders to give them some more thematic negatives * arachnitect point change * increased kcal cost to reflect now defunct nutrition cost * Review feedback * Added a global suspension check just before floor cache is built. * minor change to fix compile error * minor style changes * Added suspension checking on sublevel load, some other stuff * Found hanging debug comment, and updated AStyle changes/ * Added killer drive as a possible mutation for Lupine, because that was failing for some reason. * Removing unused code, updating function call in activate_mutation to use _warn * Moved collapse check to a more optimal location * removing unused var * This shit is really just too picky * Updating a stupid syntax problem for this needy clang tidy check * Various bugfixes resulting from refactors * Changing line spacing for STYLE * Last changes at last :') Co-authored-by: Olanti --- .../furniture-tools.json | 23 ++++- .../furniture_and_terrain/terrain-roofs.json | 21 +++++ data/json/mutations/mutations.json | 27 ++++-- src/character.h | 3 + src/game.cpp | 38 ++++++-- src/iexamine.cpp | 36 ++++++++ src/map.cpp | 90 +++++++++++++++++-- src/map.h | 9 +- src/mapdata.cpp | 3 +- src/mapdata.h | 1 + src/mutation.cpp | 85 +++++++++++------- src/mutation.h | 6 ++ src/mutation_ui.cpp | 7 +- src/trapfunc.cpp | 15 ++++ 14 files changed, 308 insertions(+), 56 deletions(-) diff --git a/data/json/furniture_and_terrain/furniture-tools.json b/data/json/furniture_and_terrain/furniture-tools.json index e1b47c4b1ddd..9aa18ee99cb1 100644 --- a/data/json/furniture_and_terrain/furniture-tools.json +++ b/data/json/furniture_and_terrain/furniture-tools.json @@ -1130,7 +1130,7 @@ "symbol": "<", "color": "white", "move_cost_mod": 1, - "required_str": 10, + "required_str": -1, "flags": [ "LADDER", "TRANSPARENT", "SEEN_FROM_ABOVE" ], "examine_action": "deployed_furniture", "deployed_item": "grapnel", @@ -1141,5 +1141,26 @@ "sound_fail": "whump.", "items": [ { "item": "grip_hook", "count": [ 4, 4 ] }, { "item": "rope_30", "count": [ 1, 1 ] } ] } + }, + { + "type": "furniture", + "id": "f_rope_up_web", + "name": "web rope leading up", + "looks_like": "t_rope_up", + "description": "A web rope. You could climb it up.", + "symbol": "<", + "color": "white", + "move_cost_mod": 1, + "required_str": -1, + "flags": [ "LADDER", "TRANSPARENT", "SEEN_FROM_ABOVE" ], + "examine_action": "deployed_furniture", + "deployed_item": "rope_30", + "bash": { + "str_min": 3, + "str_max": 40, + "sound": "smash!", + "sound_fail": "whump.", + "items": [ { "item": "rope_30", "count": [ 1, 1 ] } ] + } } ] diff --git a/data/json/furniture_and_terrain/terrain-roofs.json b/data/json/furniture_and_terrain/terrain-roofs.json index 9557141b6151..155666cd0c79 100644 --- a/data/json/furniture_and_terrain/terrain-roofs.json +++ b/data/json/furniture_and_terrain/terrain-roofs.json @@ -354,5 +354,26 @@ "ter_set": "t_open_air", "bash_below": true } + }, + { + "type": "terrain", + "id": "t_web_bridge", + "name": "web bridge", + "description": "A hammock like bridge spun from spider silk.", + "symbol": "w", + "looks_like": "fd_web", + "color": "white", + "move_cost": 2, + "flags": [ "TRANSPARENT", "FLAT", "FLAMMABLE", "SUSPENDED", "COLLAPSES" ], + "bash": { + "str_min": 1, + "str_max": 6, + "sound": "rrrrip!", + "sound_fail": "slap!", + "sound_vol": 8, + "sound_fail_vol": 4, + "ter_set": "t_open_air", + "bash_below": true + } } ] diff --git a/data/json/mutations/mutations.json b/data/json/mutations/mutations.json index 271bccde8539..7a39f8fe2854 100644 --- a/data/json/mutations/mutations.json +++ b/data/json/mutations/mutations.json @@ -559,6 +559,7 @@ "valid": false, "social_modifiers": { "intimidate": 10 }, "prereqs": [ "PSYCHOPATH" ], + "category": [ "LUPINE" ], "cancels": [ "PACIFIST" ], "flags": [ "CANNIBAL" ] }, @@ -3613,7 +3614,7 @@ "id": "WEB_RAPPEL", "name": { "str": "Web Diver" }, "points": 1, - "description": "Your webbing is easily strong enough to support your weight. You'll use it to descend down any sheer drops you may encounter.", + "description": "Your webbing is easily strong enough to support your weight. You'll use it to descend down any sheer drops you may encounter, and to catch yourself should you fall.", "prereqs": [ "WEB_WEAVER" ], "threshreq": [ "THRESH_SPIDER" ], "category": [ "SPIDER" ] @@ -3623,7 +3624,7 @@ "id": "WEB_ROPE", "name": { "str": "Rope Webs" }, "points": 2, - "description": "With spinnerets like THESE, who needs rope?! Activate to produce rope.", + "description": "With spinnerets like THESE, who needs rope?! Activate to sling a web rope that you can climb, and can be disassembled into rope.", "prereqs": [ "WEB_WEAVER" ], "threshreq": [ "THRESH_SPIDER" ], "category": [ "SPIDER" ], @@ -3633,6 +3634,19 @@ "thirst": true, "spawn_item": { "type": "rope_30", "message": "You spin a rope from your silk." } }, + { + "type": "mutation", + "id": "WEB_BRIDGE", + "name": { "str": "Arachnitect" }, + "points": 2, + "description": "The pinnacle of silk spinning. Spin suspended walkways between ledges to span small gaps.", + "prereqs": [ "WEB_ROPE" ], + "threshreq": [ "THRESH_SPIDER" ], + "category": [ "SPIDER" ], + "cost": 60, + "hunger": true, + "thirst": true + }, { "type": "mutation", "id": "WHISKERS", @@ -4243,7 +4257,7 @@ "cancels": [ "PRETTY", "BEAUTIFUL", "BEAUTIFUL2", "BEAUTIFUL3" ], "prereqs": [ "UGLY" ], "changes_to": [ "DEFORMED2" ], - "category": [ "FISH", "CATTLE", "INSECT", "CEPHALOPOD", "FELINE", "LUPINE" ] + "category": [ "FISH", "CATTLE", "INSECT", "CEPHALOPOD", "FELINE", "LUPINE", "SPIDER" ] }, { "type": "mutation", @@ -5168,7 +5182,7 @@ "description": "Your muscle response is dependent on ambient temperatures. You lose 1% of your speed for every 5 (2.8) degrees below 65 F (18.3 C). This sluggishness helps you conserve energy, however.", "changes_to": [ "COLDBLOOD2" ], "types": [ "METABOLISM" ], - "category": [ "FISH", "CEPHALOPOD", "SPIDER" ], + "category": [ "FISH", "CEPHALOPOD" ], "temperature_speed_modifier": 0.2, "metabolism_modifier": -0.333 }, @@ -5182,7 +5196,7 @@ "prereqs": [ "COLDBLOOD" ], "changes_to": [ "COLDBLOOD3" ], "types": [ "METABOLISM" ], - "category": [ "RAPTOR", "PLANT" ], + "category": [ "RAPTOR", "PLANT", "SPIDER" ], "temperature_speed_modifier": 0.333, "metabolism_modifier": -0.5 }, @@ -5244,7 +5258,7 @@ "mixed_effect": true, "points": -1, "description": "You hiss when speaking. Persuading NPCs will be more difficult, but threatening them will be easier.", - "category": [ "LIZARD", "RAPTOR" ], + "category": [ "LIZARD", "RAPTOR", "SPIDER" ], "social_modifiers": { "persuade": -20, "lie": -10, "intimidate": 10 } }, { @@ -5306,7 +5320,6 @@ "types": [ "HANDS" ], "changes_to": [ "INSECT_ARMS_OK", "ARACHNID_ARMS" ], "prereqs": [ "CHITIN", "CHITIN2", "CHITIN3", "CHITIN_FUR2", "CHITIN_FUR3" ], - "prereqs2": [ "ANTENNAE" ], "threshreq": [ "THRESH_INSECT", "THRESH_SPIDER" ], "category": [ "INSECT", "SPIDER" ], "restricts_gear": [ "torso" ], diff --git a/src/character.h b/src/character.h index e85134c95df1..e9090f41eebd 100644 --- a/src/character.h +++ b/src/character.h @@ -760,6 +760,9 @@ class Character : public Creature, public visitable void activate_mutation( const trait_id &mutation ); void deactivate_mutation( const trait_id &mut ); + /** Removes the appropriate costs (NOTE: will reapply mods & recalc sightlines in case of newly activated mutation). */ + void mutation_spend_resources( const trait_id &mut ); + /** Converts a body_part to an hp_part */ static hp_part bp_to_hp( body_part bp ); /** Converts an hp_part to a body_part */ diff --git a/src/game.cpp b/src/game.cpp index 46bafbd49f87..dd7ca055f81b 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -113,6 +113,7 @@ #include "monstergenerator.h" #include "morale_types.h" #include "mtype.h" +#include "mutation.h" #include "npc.h" #include "npc_class.h" #include "omdata.h" @@ -246,6 +247,7 @@ static const trait_id trait_PARKOUR( "PARKOUR" ); static const trait_id trait_VINES2( "VINES2" ); static const trait_id trait_VINES3( "VINES3" ); static const trait_id trait_THICKSKIN( "THICKSKIN" ); +static const trait_id trait_WEB_ROPE( "WEB_ROPE" ); static const trait_id trait_WAYFARER( "WAYFARER" ); static const trap_str_id tr_unfinished_construction( "tr_unfinished_construction" ); @@ -10399,12 +10401,6 @@ void game::vertical_move( int movez, bool force, bool peeking ) return; } - const int cost = u.climbing_cost( u.pos(), stairs ); - if( cost == 0 ) { - add_msg( m_info, _( "You can't climb here - you need walls and/or furniture to brace against." ) ); - return; - } - std::vector pts; for( const auto &pt : m.points_in_radius( stairs, 1 ) ) { if( m.passable( pt ) && @@ -10413,6 +10409,36 @@ void game::vertical_move( int movez, bool force, bool peeking ) } } + const int cost = u.climbing_cost( u.pos(), stairs ); + + if( cost == 0 ) { + if( u.has_trait( trait_WEB_ROPE ) ) { + if( pts.empty() ) { + add_msg( m_info, _( "There is nothing above you that you can attach a web to." ) ); + } else if( can_use_mutation_warn( trait_WEB_ROPE, u ) ) { + if( g->m.move_cost( u.pos() ) != 2 && g->m.move_cost( u.pos() ) != 3 ) { + add_msg( m_info, _( "You can't spin a web rope there." ) ); + } else if( g->m.has_furn( u.pos() ) ) { + add_msg( m_info, _( "There is already furniture at that location." ) ); + } else { + if( query_yn( "Spin a rope and climb?" ) ) { + add_msg( m_good, _( "You spin a rope of web." ) ); + g->m.furn_set( u.pos(), furn_str_id( "f_rope_up_web" ) ); + u.mod_moves( to_turns( 2_seconds ) ); + u.mutation_spend_resources( trait_WEB_ROPE ); + vertical_move( movez, force, peeking ); + } + } + } + + } else { + add_msg( m_info, _( "You can't climb here - you need walls and/or furniture to brace against." ) ); + + } + return; + + } + if( cost <= 0 || pts.empty() ) { add_msg( m_info, _( "You can't climb here - there is no terrain above you that would support your weight." ) ); diff --git a/src/iexamine.cpp b/src/iexamine.cpp index fad86345f370..92b82f544e20 100644 --- a/src/iexamine.cpp +++ b/src/iexamine.cpp @@ -66,6 +66,7 @@ #include "mission_companion.h" #include "monster.h" #include "mtype.h" +#include "mutation.h" #include "npc.h" #include "options.h" #include "output.h" @@ -163,6 +164,7 @@ static const skill_id skill_mechanics( "mechanics" ); static const skill_id skill_survival( "survival" ); static const ter_str_id t_dimensional_portal( "t_dimensional_portal" ); +static const ter_str_id t_web_bridge( "t_web_bridge" ); static const trait_id trait_AMORPHOUS( "AMORPHOUS" ); static const trait_id trait_ARACHNID_ARMS_OK( "ARACHNID_ARMS_OK" ); @@ -181,6 +183,7 @@ static const trait_id trait_PARKOUR( "PARKOUR" ); static const trait_id trait_PROBOSCIS( "PROBOSCIS" ); static const trait_id trait_THRESH_MARLOSS( "THRESH_MARLOSS" ); static const trait_id trait_THRESH_MYCUS( "THRESH_MYCUS" ); +static const trait_id trait_WEB_BRDIGE( "WEB_BRIDGE" ); static const quality_id qual_ANESTHESIA( "ANESTHESIA" ); static const quality_id qual_DIG( "DIG" ); @@ -4342,6 +4345,9 @@ void iexamine::ledge( player &p, const tripoint &examp ) cmenu.text = _( "There is a ledge here. What do you want to do?" ); cmenu.addentry( 1, true, 'j', _( "Jump over." ) ); cmenu.addentry( 2, true, 'c', _( "Climb down." ) ); + if( p.has_trait( trait_WEB_BRDIGE ) ) { + cmenu.addentry( 3, true, 'w', _( "Spin Web Bridge." ) ); + } cmenu.query(); @@ -4426,6 +4432,36 @@ void iexamine::ledge( player &p, const tripoint &examp ) g->m.creature_on_trap( p ); break; } + case 3: { + + if( !can_use_mutation_warn( trait_WEB_BRDIGE, p ) ) { + break; + } + const int range = 6; //this means we could web across a gap of 5. + int success_range = 0; + bool success = false; + for( int i = 2; i <= range; i++ ) { + //break at the first non empty space encountered + if( g->m.ter( tripoint( p.posx() + i * sgn( examp.x - p.posx() ), + p.posy() + i * sgn( examp.y - p.posy() ), p.posz() ) ) != t_open_air ) { + success_range = i; + success = true; + break; + } + } + if( !success ) { + p.add_msg_if_player( _( "There is nothing for your to attach your web to!" ) ); + } else { + for( int i = 1; i < success_range; i++ ) { + tripoint dest( p.posx() + i * sgn( examp.x - p.posx() ), p.posy() + i * sgn( examp.y - p.posy() ), + p.posz() ); + + g->m.ter_set( dest, t_web_bridge ); + } + p.mutation_spend_resources( trait_WEB_BRDIGE ); + } + break; + } default: popup( _( "You decided to step back from the ledge." ) ); break; diff --git a/src/map.cpp b/src/map.cpp index 59b491c84c33..dabb4d128a38 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -2037,6 +2037,10 @@ bool map::has_floor_or_support( const tripoint &p ) const void map::drop_everything( const tripoint &p ) { + //Do a suspension check so that there won't be a floor there for the rest of this check. + if( has_flag( "SUSPENDED", p ) ) { + collapse_invalid_suspension( p ); + } if( has_floor( p ) ) { return; } @@ -2905,11 +2909,11 @@ void map::collapse_at( const tripoint &p, const bool silent, const bool was_supp continue; } // if a wall collapses, walls without support from below risk collapsing and - //propogate the collapse upwards + //propagate the collapse upwards if( zlevels && wall && p == t && has_flag( "WALL", tz ) ) { collapse_at( tz, silent ); } - // floors without support from below risk collapsing into open air and can propogate + // floors without support from below risk collapsing into open air and can propagate // the collapse horizontally but not vertically if( p != t && ( has_flag( "SUPPORTS_ROOF", t ) && has_flag( "COLLAPSES", t ) ) ) { collapse_at( t, silent ); @@ -2919,6 +2923,7 @@ void map::collapse_at( const tripoint &p, const bool silent, const bool was_supp if( zlevels ) { ter_set( tz, t_open_air ); furn_set( tz, f_null ); + propagate_suspension_check( tz ); } } } @@ -2926,6 +2931,46 @@ void map::collapse_at( const tripoint &p, const bool silent, const bool was_supp // that's not handled for now } +void map::propagate_suspension_check( const tripoint &point ) +{ + for( const tripoint &neighbor : points_in_radius( point, 1 ) ) { + if( neighbor != point && has_flag( "SUSPENDED", neighbor ) ) { + collapse_invalid_suspension( neighbor ); + } + } +} + +void map::collapse_invalid_suspension( const tripoint &point ) +{ + if( !is_suspension_valid( point ) ) { + ter_set( point, t_open_air ); + furn_set( point, f_null ); + + propagate_suspension_check( point ); + } +} + +bool map::is_suspension_valid( const tripoint &point ) +{ + if( ter( point + tripoint_east ) != t_open_air + && ter( point + tripoint_west ) != t_open_air ) { + return true; + } + if( ter( point + tripoint_south_east ) != t_open_air + && ter( point + tripoint_north_west ) != t_open_air ) { + return true; + } + if( ter( point + tripoint_south ) != t_open_air + && ter( point + tripoint_north ) != t_open_air ) { + return true; + } + if( ter( point + tripoint_north_east ) != t_open_air + && ter( point + tripoint_south_west ) != t_open_air ) { + return true; + } + return false; +} + void map::smash_items( const tripoint &p, const int power, const std::string &cause_message ) { if( !has_items( p ) ) { @@ -3262,7 +3307,7 @@ void map::bash_ter_furn( const tripoint &p, bash_params ¶ms ) // Set this now in case the ter_set below changes this const bool will_collapse = smash_ter && has_flag( "SUPPORTS_ROOF", p ) && !has_flag( "INDOORS", p ); const bool tent = smash_furn && !bash->tent_centers.empty(); - + const bool suspended = smash_ter && has_flag( "SUSPENDED", p ); // Special code to collapse the tent if destroyed if( tent ) { // Get ids of possible centers @@ -3372,7 +3417,13 @@ void map::bash_ter_furn( const tripoint &p, bash_params ¶ms ) if( will_collapse && !has_flag( "SUPPORTS_ROOF", p ) ) { collapse_at( p, params.silent, true, bash->explosive > 0 ); } - + if( suspended ) { + // Its important that we change the ter value before recursing, otherwise we'll hit an infinite loop. + // This could be prevented by assembling a visited list, but in order to avoid that cost, we're going + // build our recursion to just be resilient. + ter_set( p, t_open_air ); + propagate_suspension_check( p ); + } params.did_bash = true; params.success |= success; // Not always true, so that we can tell when to stop destroying params.bashed_solid = true; @@ -7851,7 +7902,6 @@ bool map::build_floor_cache( const int zlev ) &floor_cache[0][0], ( MAPSIZE_X ) * ( MAPSIZE_Y ), true ); bool lowest_z_lev = zlev <= -OVERMAP_DEPTH; - for( int smx = 0; smx < my_MAPSIZE; ++smx ) { for( int smy = 0; smy < my_MAPSIZE; ++smy ) { const submap *cur_submap = get_submap_at_grid( { smx, smy, zlev } ); @@ -7897,6 +7947,35 @@ void map::build_floor_caches() } } +void map::add_susensions_to_cache( const int &z ) +{ + for( int smx = 0; smx < my_MAPSIZE; ++smx ) { + for( int smy = 0; smy < my_MAPSIZE; ++smy ) { + const submap *cur_submap = get_submap_at_grid( { smx, smy, z } ); + + if( cur_submap == nullptr ) { + debugmsg( "Tried to run suspension check at (%d,%d,%d) but the submap is not loaded", smx, smy, + z ); + continue; + } + + for( int sx = 0; sx < SEEX; ++sx ) { + for( int sy = 0; sy < SEEY; ++sy ) { + point sp( sx, sy ); + const ter_t &terrain = cur_submap->get_ter( sp ).obj(); + if( terrain.has_flag( TFLAG_SUSPENDED ) ) { + tripoint loc( coords::project_combine( point_om_sm( point( smx, smy ) ), point_sm_ms( sp ) ).raw(), + z ); + if( !is_suspension_valid( loc ) ) { + support_dirty( loc ); + } + } + } + } + } + } +} + static void vehicle_caching_internal( level_cache &zch, const vpart_reference &vp, vehicle *v ) { auto &outside_cache = zch.outside_cache; @@ -7962,6 +8041,7 @@ void map::build_map_cache( const int zlev, bool skip_lightmap ) const bool affects_seen_cache = z == zlev || fov_3d; build_outside_cache( z ); build_transparency_cache( z ); + add_susensions_to_cache( z ); seen_cache_dirty |= ( build_floor_cache( z ) && affects_seen_cache ); seen_cache_dirty |= get_cache( z ).seen_cache_dirty && affects_seen_cache; } diff --git a/src/map.h b/src/map.h index 7e8fbe024bc2..bfbd9c55762d 100644 --- a/src/map.h +++ b/src/map.h @@ -1099,6 +1099,12 @@ class map /** Causes a collapse at p, such as from destroying a wall */ void collapse_at( const tripoint &p, bool silent, bool was_supporting = false, bool destroy_pos = true ); + /** Checks surrounding tiles for suspension, and has them check for collapse. !!Should only be called after the tile at this point has been destroyed!!*/ + void propagate_suspension_check( const tripoint &point ); + /** Triggers a recursive collapse of suspended tiles based on their support validity*/ + void collapse_invalid_suspension( const tripoint &point ); + /** Checks the four orientations in which a suspended tile could be valid, and returns if the tile is valid*/ + bool is_suspension_valid( const tripoint &point ); /** Tries to smash the items at the given tripoint. Used by the explosion code */ void smash_items( const tripoint &p, int power, const std::string &cause_message ); /** @@ -1751,7 +1757,8 @@ class map 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(); - + // Checks all tiles on a z level and adds those that are invalid to the support_dirty_cache */ + void add_susensions_to_cache( const int &z ); protected: void generate_lightmap( int zlev ); void build_seen_cache( const tripoint &origin, int target_z ); diff --git a/src/mapdata.cpp b/src/mapdata.cpp index fac7ee502e46..7ecd5f6924cf 100644 --- a/src/mapdata.cpp +++ b/src/mapdata.cpp @@ -183,7 +183,8 @@ static const std::unordered_map ter_bitflags_map = { { "THIN_OBSTACLE", TFLAG_THIN_OBSTACLE }, // Passable by players and monsters. Vehicles destroy it. { "SMALL_PASSAGE", TFLAG_SMALL_PASSAGE }, // A small passage, that large or huge things cannot pass through { "Z_TRANSPARENT", TFLAG_Z_TRANSPARENT }, // Doesn't block vision passing through the z-level - { "SUN_ROOF_ABOVE", TFLAG_SUN_ROOF_ABOVE } // This furniture has a "fake roof" above, that blocks sunlight (see #44421). + { "SUN_ROOF_ABOVE", TFLAG_SUN_ROOF_ABOVE }, // This furniture has a "fake roof" above, that blocks sunlight (see #44421). + { "SUSPENDED", TFLAG_SUSPENDED } // This furniture is suspended between other terrain, and will cause a cascading failure on break. } }; diff --git a/src/mapdata.h b/src/mapdata.h index a60289c47e24..2367d9ba8069 100644 --- a/src/mapdata.h +++ b/src/mapdata.h @@ -253,6 +253,7 @@ enum ter_bitflags : int { TFLAG_SMALL_PASSAGE, TFLAG_Z_TRANSPARENT, TFLAG_SUN_ROOF_ABOVE, + TFLAG_SUSPENDED, NUM_TERFLAGS }; diff --git a/src/mutation.cpp b/src/mutation.cpp index 5206ee8aa02c..1799fddd2f34 100644 --- a/src/mutation.cpp +++ b/src/mutation.cpp @@ -481,41 +481,13 @@ void Character::activate_mutation( const trait_id &mut ) { const mutation_branch &mdata = mut.obj(); trait_data &tdata = my_mutations[mut]; - int cost = mdata.cost; // You can take yourself halfway to Near Death levels of hunger/thirst. // Fatigue can go to Exhausted. - if( ( mdata.hunger && get_kcal_percent() < 0.5f ) || ( mdata.thirst && - get_thirst() >= thirst_levels::dehydrated ) || - ( mdata.fatigue && get_fatigue() >= fatigue_levels::exhausted ) ) { - // Insufficient Foo to *maintain* operation is handled in player::suffer - add_msg_if_player( m_warning, _( "You feel like using your %s would kill you!" ), - mdata.name() ); + if( !can_use_mutation_warn( mut, *this ) ) { return; } - if( tdata.powered && tdata.charge > 0 ) { - // Already-on units just lose a bit of charge - tdata.charge--; - } else { - // Not-on units, or those with zero charge, have to pay the power cost - if( mdata.cooldown > 0 ) { - tdata.charge = mdata.cooldown - 1; - } - if( mdata.hunger ) { - // burn some energy - mod_stored_nutr( cost ); - } - if( mdata.thirst ) { - mod_thirst( cost ); - } - if( mdata.fatigue ) { - mod_fatigue( cost ); - } - tdata.powered = true; - - // Handle stat changes from activation - apply_mods( mut, true ); - recalc_sight_limits(); - } + mutation_spend_resources( mut ); + tdata.powered = true; if( !mut->enchantments.empty() ) { recalculate_enchantment_cache(); @@ -1737,6 +1709,57 @@ bool contains_trait( std::vector> traits, const trait return std::find( traits.begin(), traits.end(), trait ) != traits.end(); } +bool can_use_mutation( const trait_id &mut, const Character &character ) +{ + const mutation_branch &mdata = mut.obj(); + // You can take yourself halfway to Near Death levels of hunger/thirst. + // Fatigue can go to Exhausted. + return !( ( mdata.hunger && character.get_kcal_percent() < 0.5f ) || + ( mdata.thirst && character.get_thirst() >= thirst_levels::dehydrated ) || + ( mdata.fatigue && character.get_fatigue() >= fatigue_levels::exhausted ) ); +} + +bool can_use_mutation_warn( const trait_id &mut, const Character &character ) +{ + const bool result = can_use_mutation( mut, character ); + if( !result ) { + character.add_msg_if_player( m_warning, _( "You feel like using your %s would kill you!" ), + mut.obj().name() ); + } + + return result; +} + +void Character::mutation_spend_resources( const trait_id &mut ) +{ + const mutation_branch &mdata = mut.obj(); + trait_data &tdata = my_mutations[mut]; + int cost = mdata.cost; + if( tdata.powered && tdata.charge > 0 ) { + // Already-on units just lose a bit of charge + tdata.charge--; + } else { + // Not-on units, or those with zero charge, have to pay the power cost + if( mdata.cooldown > 0 ) { + tdata.charge = mdata.cooldown - 1; + } + if( mdata.hunger ) { + // burn some energy + mod_stored_kcal( -cost * 6 ); + } + if( mdata.thirst ) { + mod_thirst( cost ); + } + if( mdata.fatigue ) { + mod_fatigue( cost ); + } + + // Handle stat changes from activation + apply_mods( mut, true ); + recalc_sight_limits(); + } +} + std::string Character::visible_mutations( const int visibility_cap ) const { const std::vector &my_muts = get_mutations(); diff --git a/src/mutation.h b/src/mutation.h index 66fa2b10dc1c..ca8c7945444c 100644 --- a/src/mutation.h +++ b/src/mutation.h @@ -493,6 +493,12 @@ bool are_opposite_traits( const trait_id &trait_a, const trait_id &trait_b ); bool are_same_type_traits( const trait_id &trait_a, const trait_id &trait_b ); bool contains_trait( std::vector> traits, const trait_id &trait ); +/** Check to see if the specified character has enough resources to use that mutation. */ +bool can_use_mutation( const trait_id &mut, const Character &character ); + +/** Calls can_use_mutation and if it fails, print a standard message. */ +bool can_use_mutation_warn( const trait_id &mut, const Character &character ); + enum class mutagen_technique : int { consumed_mutagen, injected_mutagen, diff --git a/src/mutation_ui.cpp b/src/mutation_ui.cpp index d27cab84dede..4752c1cef6c4 100644 --- a/src/mutation_ui.cpp +++ b/src/mutation_ui.cpp @@ -355,9 +355,7 @@ player::power_mut_ui_result player::power_mutations_ui() ret.cmd = power_mut_ui_cmd::Deactivate; ret.mut = mut_id; exit = true; - } else if( ( !mut_data.hunger || get_kcal_percent() >= 0.8f ) && - ( !mut_data.thirst || get_thirst() <= thirst_levels::dehydrated ) && - ( !mut_data.fatigue || get_fatigue() <= 400 ) ) { + } else if( can_use_mutation_warn( mut_id, *this ) ) { if( trans && !trans->msg_transform.empty() ) { add_msg_if_player( m_neutral, trans->msg_transform ); } else { @@ -367,7 +365,8 @@ player::power_mut_ui_result player::power_mutations_ui() ret.mut = mut_id; exit = true; } else { - popup( _( "You don't have enough in you to activate your %s!" ), mut_data.name() ); + popup( _( "You feel like using your %s would kill you!" ), + mut_data.name() ); } } else { popup( _( "You cannot activate %s! To read a description of " diff --git a/src/trapfunc.cpp b/src/trapfunc.cpp index 1630e7a2e297..77ba484411aa 100644 --- a/src/trapfunc.cpp +++ b/src/trapfunc.cpp @@ -55,6 +55,7 @@ static const itype_id itype_rope_30( "rope_30" ); static const trait_id trait_WINGS_BIRD( "WINGS_BIRD" ); static const trait_id trait_WINGS_BUTTERFLY( "WINGS_BUTTERFLY" ); +static const trait_id trait_WEB_RAPPEL( "WEB_RAPPEL" ); static const mtype_id mon_blob( "mon_blob" ); static const mtype_id mon_shadow( "mon_shadow" ); @@ -766,6 +767,8 @@ bool trapfunc::pit( const tripoint &p, Creature *c, item * ) if( ( n->has_trait( trait_WINGS_BIRD ) ) || ( ( one_in( 2 ) ) && ( n->has_trait( trait_WINGS_BUTTERFLY ) ) ) ) { n->add_msg_if_player( _( "You flap your wings and flutter down gracefully." ) ); + } else if( n->has_trait( trait_WEB_RAPPEL ) ) { + n->add_msg_if_player( _( "You quickly spin a line of silk and rappel down." ) ); } else if( n->has_active_bionic( bio_shock_absorber ) ) { n->add_msg_if_player( m_info, _( "You hit the ground hard, but your shock absorbers handle the impact admirably!" ) ); @@ -815,6 +818,8 @@ bool trapfunc::pit_spikes( const tripoint &p, Creature *c, item * ) if( ( n->has_trait( trait_WINGS_BIRD ) ) || ( ( one_in( 2 ) ) && ( n->has_trait( trait_WINGS_BUTTERFLY ) ) ) ) { n->add_msg_if_player( _( "You flap your wings and flutter down gracefully." ) ); + } else if( n->has_trait( trait_WEB_RAPPEL ) ) { + n->add_msg_if_player( _( "You quickly spin a line of silk and rappel down." ) ); } else if( n->has_active_bionic( bio_shock_absorber ) ) { n->add_msg_if_player( m_info, _( "You hit the ground hard, but your shock absorbers handle the impact admirably!" ) ); @@ -892,6 +897,8 @@ bool trapfunc::pit_glass( const tripoint &p, Creature *c, item * ) if( ( n->has_trait( trait_WINGS_BIRD ) ) || ( ( one_in( 2 ) ) && ( n->has_trait( trait_WINGS_BUTTERFLY ) ) ) ) { n->add_msg_if_player( _( "You flap your wings and flutter down gracefully." ) ); + } else if( n->has_trait( trait_WEB_RAPPEL ) ) { + n->add_msg_if_player( _( "You quickly spin a line of silk and rappel down." ) ); } else if( n->has_active_bionic( bio_shock_absorber ) ) { n->add_msg_if_player( m_info, _( "You hit the ground hard, but your shock absorbers handle the impact admirably!" ) ); @@ -1077,6 +1084,9 @@ bool trapfunc::sinkhole( const tripoint &p, Creature *c, item *i ) } else if( query_for_item( pl, itype_rope_30, _( "You step into a sinkhole! Throw your rope out to try to catch something?" ) ) ) { success = sinkhole_safety_roll( pl, itype_rope_30, 12 ); + } else if( pl->has_trait( trait_WEB_RAPPEL ) && query_yn( + _( "You step into a sinkhole! Throw a web out to try to catch something?" ) ) ) { + success = sinkhole_safety_roll( pl, itype_rope_30, 3 ); } pl->add_msg_player_or_npc( m_warning, _( "The sinkhole collapses!" ), @@ -1114,6 +1124,8 @@ bool trapfunc::ledge( const tripoint &p, Creature *c, item * ) if( g->u.has_trait( trait_WINGS_BIRD ) || ( one_in( 2 ) && g->u.has_trait( trait_WINGS_BUTTERFLY ) ) ) { add_msg( _( "You flap your wings and flutter down gracefully." ) ); + } else if( g->u.has_trait( trait_WEB_RAPPEL ) ) { + add_msg( _( "You quickly spin a line of silk and rappel down." ) ); } else if( g->u.has_active_bionic( bio_shock_absorber ) ) { add_msg( m_info, _( "You hit the ground hard, but your shock absorbers handle the impact admirably!" ) ); @@ -1195,6 +1207,9 @@ bool trapfunc::ledge( const tripoint &p, Creature *c, item * ) pl->has_trait( trait_WINGS_BUTTERFLY ) ) ) { pl->add_msg_player_or_npc( _( "You flap your wings and flutter down gracefully." ), _( " flaps their wings and flutters down gracefully." ) ); + } else if( pl->has_trait( trait_WEB_RAPPEL ) ) { + pl->add_msg_player_or_npc( _( "You quickly spin a line of silk and rappel down." ), + _( " quickly spins a line of silk and rappels down." ) ); } else if( pl->has_active_bionic( bio_shock_absorber ) ) { pl->add_msg_if_player( m_info, _( "You hit the ground hard, but your shock absorbers handle the impact admirably!" ) );