From 99d0d298ee02f8be84ddccd90187e39ca8d07b15 Mon Sep 17 00:00:00 2001 From: shmakota <47482311+shmakota@users.noreply.github.com> Date: Tue, 24 Feb 2026 18:59:39 -0600 Subject: [PATCH 01/12] climbing --- data/json/monstergroups/zombie_upgrades.json | 1 + data/json/monsters/insect_spider.json | 18 +- data/json/monsters/zed_misc.json | 10 + docs/en/mod/json/reference/json_flags.md | 1 + src/monmove.cpp | 186 ++++++++++++++++--- src/monster.cpp | 7 +- src/monster.h | 4 +- src/monstergenerator.cpp | 5 +- src/mtype.cpp | 6 +- src/mtype.h | 1 + 10 files changed, 203 insertions(+), 36 deletions(-) diff --git a/data/json/monstergroups/zombie_upgrades.json b/data/json/monstergroups/zombie_upgrades.json index 494c8bbfa325..9f79af513467 100644 --- a/data/json/monstergroups/zombie_upgrades.json +++ b/data/json/monstergroups/zombie_upgrades.json @@ -33,6 +33,7 @@ { "monster": "mon_zombie_grabber", "freq": 15, "cost_multiplier": 5 }, { "monster": "mon_zombie_grappler", "freq": 45, "cost_multiplier": 7 }, { "monster": "mon_zombie_hunter", "freq": 30, "cost_multiplier": 5 }, + { "monster": "mon_zombie_climber", "freq": 15, "cost_multiplier": 5 }, { "monster": "mon_skeleton", "freq": 45, "cost_multiplier": 5 }, { "monster": "mon_zombie_smoker", "freq": 15, "cost_multiplier": 5 }, { "monster": "mon_zombie_shady", "freq": 15, "cost_multiplier": 5 }, diff --git a/data/json/monsters/insect_spider.json b/data/json/monsters/insect_spider.json index 9ff5040d95a3..2b0570c2bc14 100644 --- a/data/json/monsters/insect_spider.json +++ b/data/json/monsters/insect_spider.json @@ -769,7 +769,7 @@ "anger_triggers": [ "STALK", "PLAYER_WEAK", "PLAYER_CLOSE" ], "death_function": [ "NORMAL" ], "fungalize_into": "mon_spider_fungus", - "flags": [ "SEES", "SMELLS", "HEARS", "VENOM", "WEBWALK", "CLIMBS", "HARDTOSHOOT", "PUSH_MON", "PATH_AVOID_FIRE" ], + "flags": [ "SEES", "SMELLS", "HEARS", "VENOM", "WEBWALK", "CLIMBS", "CLIMBS_WALLS", "HARDTOSHOOT", "PUSH_MON", "PATH_AVOID_FIRE" ], "//": "No, they are not in fact the most venomous spider in the world." }, { @@ -803,7 +803,7 @@ "harvest": "arachnid", "anger_triggers": [ "STALK", "PLAYER_WEAK", "PLAYER_CLOSE" ], "death_function": [ "NORMAL" ], - "flags": [ "SEES", "SMELLS", "HEARS", "WEBWALK", "CLIMBS", "HARDTOSHOOT", "PATH_AVOID_FIRE" ] + "flags": [ "SEES", "SMELLS", "HEARS", "WEBWALK", "CLIMBS", "CLIMBS_WALLS", "HARDTOSHOOT", "PATH_AVOID_FIRE" ] }, { "id": "mon_spider_jumping_giant", @@ -838,7 +838,7 @@ "anger_triggers": [ "PLAYER_CLOSE" ], "death_function": [ "NORMAL" ], "fungalize_into": "mon_spider_fungus", - "flags": [ "SEES", "SMELLS", "HEARS", "VENOM", "HIT_AND_RUN", "CLIMBS", "PATH_AVOID_DANGER_1" ] + "flags": [ "SEES", "SMELLS", "HEARS", "VENOM", "HIT_AND_RUN", "CLIMBS", "CLIMBS_WALLS", "PATH_AVOID_DANGER_1" ] }, { "id": "mon_spider_trapdoor_giant", @@ -871,7 +871,7 @@ "harvest": "arachnid", "death_function": [ "NORMAL" ], "fungalize_into": "mon_spider_fungus", - "flags": [ "SEES", "SMELLS", "HEARS", "VENOM", "GRABS", "CAN_DIG", "WEBWALK", "CLIMBS", "PATH_AVOID_FIRE" ] + "flags": [ "SEES", "SMELLS", "HEARS", "VENOM", "GRABS", "CAN_DIG", "WEBWALK", "CLIMBS", "CLIMBS_WALLS", "PATH_AVOID_FIRE" ] }, { "id": "mon_spider_web", @@ -905,7 +905,7 @@ "harvest": "arachnid", "death_function": [ "NORMAL" ], "fungalize_into": "mon_spider_fungus", - "flags": [ "SEES", "SMELLS", "HEARS", "VENOM", "WEBWALK", "CLIMBS", "PATH_AVOID_FIRE", "PATH_AVOID_FALL" ] + "flags": [ "SEES", "SMELLS", "HEARS", "VENOM", "WEBWALK", "CLIMBS", "CLIMBS_WALLS", "PATH_AVOID_FIRE", "PATH_AVOID_FALL" ] }, { "id": "mon_spider_web_s", @@ -937,7 +937,7 @@ "vision_night": 5, "harvest": "arachnid", "death_function": [ "NORMAL" ], - "flags": [ "SEES", "SMELLS", "HEARS", "WEBWALK", "STUMBLES", "CLIMBS", "PATH_AVOID_FIRE" ] + "flags": [ "SEES", "SMELLS", "HEARS", "WEBWALK", "STUMBLES", "CLIMBS", "CLIMBS_WALLS", "PATH_AVOID_FIRE" ] }, { "id": "mon_spider_widow_giant", @@ -972,7 +972,7 @@ "anger_triggers": [ "PLAYER_WEAK", "PLAYER_CLOSE" ], "death_function": [ "NORMAL" ], "fungalize_into": "mon_spider_fungus", - "flags": [ "SEES", "SMELLS", "HEARS", "BADVENOM", "WEBWALK", "CLIMBS", "PATH_AVOID_FIRE" ] + "flags": [ "SEES", "SMELLS", "HEARS", "BADVENOM", "WEBWALK", "CLIMBS", "CLIMBS_WALLS", "PATH_AVOID_FIRE" ] }, { "id": "mon_spider_widow_giant_s", @@ -1006,7 +1006,7 @@ "harvest": "arachnid", "anger_triggers": [ "PLAYER_WEAK", "PLAYER_CLOSE" ], "death_function": [ "NORMAL" ], - "flags": [ "SEES", "SMELLS", "HEARS", "VENOM", "WEBWALK", "CLIMBS", "PATH_AVOID_FIRE" ] + "flags": [ "SEES", "SMELLS", "HEARS", "VENOM", "WEBWALK", "CLIMBS", "CLIMBS_WALLS", "PATH_AVOID_FIRE" ] }, { "id": "mon_spider_wolf_giant", @@ -1041,7 +1041,7 @@ "anger_triggers": [ "STALK", "PLAYER_WEAK", "HURT", "PLAYER_CLOSE" ], "death_function": [ "NORMAL" ], "fungalize_into": "mon_spider_fungus", - "flags": [ "SEES", "SMELLS", "HEARS", "VENOM", "CLIMBS", "PATH_AVOID_FIRE", "PATH_AVOID_FALL" ] + "flags": [ "SEES", "SMELLS", "HEARS", "VENOM", "CLIMBS", "CLIMBS_WALLS", "PATH_AVOID_FIRE", "PATH_AVOID_FALL" ] }, { "id": "mon_wasp_larva", diff --git a/data/json/monsters/zed_misc.json b/data/json/monsters/zed_misc.json index 5e563bdf8c07..9aed17dbe7a9 100644 --- a/data/json/monsters/zed_misc.json +++ b/data/json/monsters/zed_misc.json @@ -560,6 +560,16 @@ "upgrades": { "half_life": 28, "into": "mon_zombie_predator" }, "flags": [ "SEES", "HEARS", "SMELLS", "WARM", "BASHES", "POISON", "NO_BREATHE", "REVIVES", "CLIMBS", "PUSH_MON" ] }, + { + "id": "mon_zombie_climber", + "type": "MONSTER", + "copy-from": "mon_zombie_hunter", + "name": { "str": "climbing zombie" }, + "description": "This lithe zombie scuttles up vertical surfaces on all fours, clawing into cracks and ledges to haul itself to higher ground.", + "color": "light_gray", + "speed": 120, + "extend": { "flags": [ "CLIMBS_WALLS" ] } + }, { "id": "mon_zombie_jackson", "type": "MONSTER", diff --git a/docs/en/mod/json/reference/json_flags.md b/docs/en/mod/json/reference/json_flags.md index 1aad069da3c8..04f3206389ae 100644 --- a/docs/en/mod/json/reference/json_flags.md +++ b/docs/en/mod/json/reference/json_flags.md @@ -1065,6 +1065,7 @@ Multiple death functions can be used. Not all combinations make sense. - `CBM_TECH` May produce a CBM or two from 'bionics_tech' item group and a power CBM when butchered. - `CHITIN` May produce chitin when butchered. - `CLIMBS` Can climb. +- `CLIMBS_WALLS` Can climb walls and other sheer surfaces. - `CURRENT` this water is flowing. - `DESTROYS` Bashes down walls and more. (2.5x bash multiplier, where base is the critter's max melee bashing) diff --git a/src/monmove.cpp b/src/monmove.cpp index b945f1e85507..c0da3c9c06a3 100644 --- a/src/monmove.cpp +++ b/src/monmove.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include "avatar.h" @@ -80,6 +81,96 @@ static const species_id SPIDER( "SPIDER" ); static const species_id ZOMBIE( "ZOMBIE" ); static const std::string flag_AUTODOC_COUCH( "AUTODOC_COUCH" ); + +namespace +{ + +auto has_wall_support( const map &here, const tripoint &anchor ) -> bool +{ + const auto neighbor_range = points_in_radius( anchor, 1 ); + const std::vector neighbors( neighbor_range.begin(), neighbor_range.end() ); + + return std::ranges::any_of( neighbors, [&anchor, &here]( const tripoint &pt ) { + return pt.z == anchor.z && pt != anchor && here.impassable_ter_furn( pt ); + } ); +} + +auto anchored_on_wall( const map &here, const tripoint &pos ) -> bool +{ + if( !here.has_flag( TFLAG_NO_FLOOR, pos ) ) { + return false; + } + + if( has_wall_support( here, pos ) ) { + return true; + } + + const tripoint below = pos + tripoint_below; + return here.inbounds( below ) && here.impassable( below ); +} + +auto report_missing_lua_ai( const std::string &method ) -> void +{ + static auto warned = std::unordered_set{}; + if( !warned.insert( method ).second ) { + return; + } + debugmsg( "Lua monster AI function '%s' is not defined", method ); +} + +auto report_invalid_lua_ai_return( const std::string &method, const sol::object &value, + sol::state &lua ) -> void +{ + static auto warned = std::unordered_set{}; + if( !warned.insert( method ).second ) { + return; + } + const auto type_name = get_luna_type( value ); + const auto raw_name = type_name.value_or( + std::string( sol::type_name( lua, value.get_type() ) ) ); + debugmsg( "Lua monster AI function '%s' returned %s, expected boolean or nil", + method, raw_name ); +} + +auto run_lua_monster_ai( monster &mon ) -> bool +{ + const auto &lua_method = mon.type->lua_ai; + if( !lua_method ) { + return false; + } + + auto *lua_state = DynamicDataLoader::get_instance().lua.get(); + if( lua_state == nullptr ) { + return false; + } + + sol::state &lua = lua_state->lua; + sol::object ref = lua.globals()["game"]["monster_ai_functions"][*lua_method]; + if( ref.get_type() != sol::type::function ) { + report_missing_lua_ai( *lua_method ); + return false; + } + + auto func = ref.as(); + sol::protected_function_result res = func( &mon ); + check_func_result( res ); + if( !res.valid() ) { + return false; + } + + const auto value = res.get(); + if( value.get_type() == sol::type::lua_nil ) { + return false; + } + if( value.get_type() != sol::type::boolean ) { + report_invalid_lua_ai_return( *lua_method, value, lua ); + return false; + } + + return value.as(); +} + +} // namespace static const std::string flag_LIQUID( "LIQUID" ); enum { @@ -256,19 +347,20 @@ bool monster::can_reach_to( const tripoint &p ) const return true; } + const auto can_wall_climb = can_wall_climb_to( p ); const bool is_going_up = p.z > pos().z; if( is_going_up ) { const bool has_up_ramp = here.has_flag( TFLAG_RAMP_UP, p + tripoint_below ); const bool has_stairs = here.has_flag( TFLAG_GOES_UP, pos() ); const bool can_fly_there = this->flies() && here.has_flag( TFLAG_NO_FLOOR, p ); - return has_up_ramp || has_stairs || can_fly_there; + return has_up_ramp || has_stairs || can_fly_there || can_wall_climb; } else { const bool has_down_ramp = here.has_flag( TFLAG_RAMP_DOWN, p + tripoint_above ); const bool has_stairs = here.has_flag( TFLAG_GOES_DOWN, pos() ); const bool can_fly_there = this->flies() && here.has_flag( TFLAG_NO_FLOOR, this->pos() ); - return has_down_ramp || has_stairs || can_fly_there; + return has_down_ramp || has_stairs || can_fly_there || can_wall_climb; } } @@ -281,7 +373,39 @@ bool monster::can_squeeze_to( const tripoint &p ) const bool monster::can_move_to( const tripoint &p ) const { - return can_reach_to( p ) && will_move_to( p ) && !has_flag( MF_STATIONARY ); + return can_reach_to( p ) && will_move_to( p ); +} + +auto monster::can_wall_climb_to( const tripoint &p ) const -> bool +{ + if( !climbs_walls() ) { + return false; + } + + const map &here = get_map(); + + if( !here.has_zlevels() ) { + return false; + } + + if( !here.inbounds( p ) || !here.passable( p ) ) { + return false; + } + + const auto from = pos(); + const auto dz = p.z - from.z; + + if( std::abs( dz ) != 1 ) { + return false; + } + + const auto &anchor = dz > 0 ? from : p; + const auto climb_diff = here.climb_difficulty( anchor ); + if( climb_diff > 10 ) { + return false; + } + + return has_wall_support( here, anchor ); } void monster::set_dest( const tripoint &p ) @@ -851,8 +975,9 @@ void monster::move() // Give nursebots a chance to do surgery. nursebot_operate( dragged_foe ); - // The monster can sometimes hang in air due to last fall being blocked - if( !flies() && g->m.has_flag( TFLAG_NO_FLOOR, pos() ) ) { + // Allow wall-climbers to hang in open air when anchored; others should resolve traps/falls + if( !flies() && !( climbs_walls() && anchored_on_wall( g->m, pos() ) ) && + g->m.has_flag( TFLAG_NO_FLOOR, pos() ) ) { g->m.creature_on_trap( *this, false ); if( is_dead() ) { return; @@ -1059,18 +1184,22 @@ void monster::move() const bool is_z_move = candidate.z != posz(); if( is_z_move ) { bool can_z_attack = fov_3d; + const auto wall_climb_move = can_wall_climb_to( candidate ); if( !here.valid_move( pos(), candidate, false, true, via_ramp ) ) { // Can't phase through floor - can_z_move = false; - can_z_attack = false; + can_z_move = wall_climb_move; + can_z_attack = wall_climb_move && can_z_attack; } // If we're trying to go up but can't fly, check if we can climb. If we can't, then don't // This prevents non-climb/fly enemies running up walls - if( can_z_move && candidate.z > posz() && !( via_ramp || flies() ) && - ( !can_climb() || !here.has_floor_or_support( candidate ) ) ) { - // Can't "jump" up a whole z-level - can_z_move = false; + if( can_z_move && candidate.z > posz() && !( via_ramp || flies() ) ) { + if( wall_climb_move ) { + // Wall climbers intentionally ignore floor/support checks + } else if( !can_climb() || !here.has_floor_or_support( candidate ) ) { + // Can't "jump" up a whole z-level + can_z_move = false; + } } // We can still do the z-level stair teleport bullshit that isn't removed yet @@ -1482,10 +1611,13 @@ int monster::calc_climb_cost( const tripoint &f, const tripoint &t ) const return 100; } - if( climbs() && !g->m.has_flag( TFLAG_NO_FLOOR, t ) ) { - const int diff = g->m.climb_difficulty( f ); + if( climbs() ) { + const auto diff = g->m.climb_difficulty( f ); if( diff <= 10 ) { - return 150; + if( g->m.has_flag( TFLAG_NO_FLOOR, t ) && !climbs_walls() ) { + return 0; + } + return g->m.has_flag( TFLAG_NO_FLOOR, t ) ? 200 : 150; } } @@ -1714,6 +1846,7 @@ bool monster::move_to( const tripoint &p, bool force, bool step_on_critter, const bool going_up = p.z > pos().z; tripoint destination = p; + const bool destination_has_no_floor = g->m.has_flag( TFLAG_NO_FLOOR, destination ); // This is stair teleportation hackery. // TODO: Remove this in favor of stair alignment @@ -1752,6 +1885,7 @@ bool monster::move_to( const tripoint &p, bool force, bool step_on_critter, } } } + const auto wall_climb_move = can_wall_climb_to( destination ); if( critter != nullptr && !step_on_critter ) { return false; @@ -1771,10 +1905,11 @@ bool monster::move_to( const tripoint &p, bool force, bool step_on_critter, // is consistent even if the monster stumbles, // and the same regardless of the distance measurement mode. // Note: Keep this as float here or else it will cancel valid moves - const float cost = stagger_adjustment * - static_cast( climbs() && - g->m.has_flag( TFLAG_NO_FLOOR, p ) ? calc_climb_cost( pos(), destination ) : calc_movecost( pos(), - destination ) ); + const auto use_climb_cost = wall_climb_move || destination_has_no_floor; + const float base_cost = use_climb_cost ? + static_cast( calc_climb_cost( pos(), destination ) ) : + static_cast( calc_movecost( pos(), destination ) ); + const float cost = stagger_adjustment * base_cost; if( cost > 0.0f ) { moves -= static_cast( std::ceil( cost ) ); } else { @@ -1806,6 +1941,11 @@ bool monster::move_to( const tripoint &p, bool force, bool step_on_critter, } } + if( wall_climb_move && z_move && g->u.sees( *this ) ) { + add_msg( _( "The %1$s climbs %2$s the wall." ), name(), + destination.z > posz() ? _( "up" ) : _( "down" ) ); + } + setpos( destination ); footsteps( destination ); set_underwater( will_be_water ); @@ -1844,9 +1984,13 @@ bool monster::move_to( const tripoint &p, bool force, bool step_on_critter, remove_effect( effect_no_sight ); } - g->m.creature_on_trap( *this ); - if( is_dead() ) { - return true; + const bool anchored_on_wall_move = climbs_walls() && destination_has_no_floor && + anchored_on_wall( g->m, destination ); + if( !( anchored_on_wall_move && on_ground ) ) { + g->m.creature_on_trap( *this ); + if( is_dead() ) { + return true; + } } if( !will_be_water && ( digs() || can_dig() ) ) { set_underwater( g->m.ter( pos() )->is_diggable() ); diff --git a/src/monster.cpp b/src/monster.cpp index 97e19efb0400..3d65d5c69f66 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -1147,7 +1147,12 @@ bool monster::flies() const bool monster::climbs() const { - return has_flag( MF_CLIMBS ); + return has_flag( MF_CLIMBS ) || climbs_walls(); +} + +auto monster::climbs_walls() const -> bool +{ + return has_flag( MF_CLIMBS_WALLS ); } bool monster::swims() const diff --git a/src/monster.h b/src/monster.h index 8915f3973c50..3c6784427c8a 100644 --- a/src/monster.h +++ b/src/monster.h @@ -167,12 +167,13 @@ class monster : public Creature, public location_visitable bool can_hear() const; // MF_HEARS and no MF_DEAF bool can_submerge() const; // MF_AQUATIC or swims() or MF_NO_BREATH, and not MF_ELECTRONIC bool can_drown() const; // MF_AQUATIC or swims() or MF_NO_BREATHE or flies() - bool can_climb() const; // climbs() or flies() + bool can_climb() const; // climbs() or climbs_walls() or flies() bool digging() const override; // digs() or can_dig() and diggable terrain bool can_dig() const; bool digs() const; bool flies() const; bool climbs() const; + bool climbs_walls() const; bool swims() const; // Returns false if the monster is stunned, has 0 moves or otherwise wouldn't act this turn bool can_act() const; @@ -208,6 +209,7 @@ class monster : public Creature, public location_visitable bool can_move_to( const tripoint &p ) const; bool can_reach_to( const tripoint &p ) const; bool will_move_to( const tripoint &p ) const; + bool can_wall_climb_to( const tripoint &p ) const; bool can_squeeze_to( const tripoint &p ) const; bool will_reach( point p ); // Do we have plans to get to (x, y)? diff --git a/src/monstergenerator.cpp b/src/monstergenerator.cpp index 986807ef5ef7..227e3e725531 100644 --- a/src/monstergenerator.cpp +++ b/src/monstergenerator.cpp @@ -156,6 +156,7 @@ std::string enum_to_string( m_flag data ) case MF_CBM_SUBS: return "CBM_SUBS"; case MF_SWARMS: return "SWARMS"; case MF_CLIMBS: return "CLIMBS"; + case MF_CLIMBS_WALLS: return "CLIMBS_WALLS"; case MF_GROUP_MORALE: return "GROUP_MORALE"; case MF_INTERIOR_AMMO: return "INTERIOR_AMMO"; case MF_NIGHT_INVISIBILITY: return "NIGHT_INVISIBILITY"; @@ -1086,12 +1087,12 @@ void mtype::setup_pathfinding_deferred() const bool default_override = get_option( "PATHFINDING_DEFAULT_IS_OVERRIDE" ); const float range_mult = get_option( "PATHFINDING_RANGE_MULT" ); - if( this->has_flag( MF_CLIMBS ) ) { + if( this->has_flag( MF_CLIMBS ) || this->has_flag( MF_CLIMBS_WALLS ) ) { this->legacy_path_settings.climb_cost = 3; this->path_settings.climb_cost = 3.0; } - if( this->has_flag( MF_FLIES ) ) { + if( this->has_flag( MF_FLIES ) || this->has_flag( MF_CLIMBS_WALLS ) ) { this->path_settings.can_fly = true; } diff --git a/src/mtype.cpp b/src/mtype.cpp index 553ee4f68937..2e07e2f10872 100644 --- a/src/mtype.cpp +++ b/src/mtype.cpp @@ -355,7 +355,9 @@ void mtype::faction_display( catacurses::window &w, const point &top_left, const if( has_flag( MF_DIGS ) ) { trim_and_print( w, top_left + point( 0, ++y ), width, c_white, _( "It can burrow underground." ) ); } - if( has_flag( MF_CLIMBS ) ) { + if( has_flag( MF_CLIMBS_WALLS ) ) { + trim_and_print( w, top_left + point( 0, ++y ), width, c_white, _( "It can climb walls." ) ); + } else if( has_flag( MF_CLIMBS ) ) { trim_and_print( w, top_left + point( 0, ++y ), width, c_white, _( "It can climb." ) ); } if( has_flag( MF_GRABS ) ) { @@ -369,4 +371,4 @@ void mtype::faction_display( catacurses::window &w, const point &top_left, const } // Description fold_and_print( w, top_left + point( 0, y + 2 ), width, c_light_gray, get_description() ); -} \ No newline at end of file +} diff --git a/src/mtype.h b/src/mtype.h index aeedca3c7148..2382df6fedaa 100644 --- a/src/mtype.h +++ b/src/mtype.h @@ -157,6 +157,7 @@ enum m_flag : int { MF_GROUP_MORALE, // Monsters that are more courageous when near friends MF_INTERIOR_AMMO, // Monster contain's its ammo inside itself, no need to load on launch. Prevents ammo from being dropped on disable. MF_CLIMBS, // Monsters that can climb certain terrain and furniture + MF_CLIMBS_WALLS, // Monsters that can climb walls and other vertical surfaces MF_PACIFIST, // Monsters that will never use melee attack, useful for having them use grab without attacking the player MF_PUSH_MON, // Monsters that can push creatures out of their way MF_PUSH_VEH, // Monsters that can push vehicles out of their way From 0b04157100463d07f6ae1c089e72bccae98220a6 Mon Sep 17 00:00:00 2001 From: shmakota <47482311+shmakota@users.noreply.github.com> Date: Tue, 24 Feb 2026 20:38:55 -0600 Subject: [PATCH 02/12] pt2 --- data/json/monsters/zed_misc.json | 8 +++ src/monmove.cpp | 93 ++++++++++---------------------- src/trapfunc.cpp | 4 +- 3 files changed, 38 insertions(+), 67 deletions(-) diff --git a/data/json/monsters/zed_misc.json b/data/json/monsters/zed_misc.json index 9aed17dbe7a9..6b7581205feb 100644 --- a/data/json/monsters/zed_misc.json +++ b/data/json/monsters/zed_misc.json @@ -568,6 +568,14 @@ "description": "This lithe zombie scuttles up vertical surfaces on all fours, clawing into cracks and ledges to haul itself to higher ground.", "color": "light_gray", "speed": 120, + "special_attacks": [ + { "id": "scratch", "damage_max_instance": [ { "damage_type": "cut", "amount": 12 } ] }, + { + "type": "bite", + "cooldown": 10, + "damage_max_instance": [ { "damage_type": "stab", "amount": 12, "armor_multiplier": 0.7 } ] + } + ], "extend": { "flags": [ "CLIMBS_WALLS" ] } }, { diff --git a/src/monmove.cpp b/src/monmove.cpp index c0da3c9c06a3..f5e39319c4b9 100644 --- a/src/monmove.cpp +++ b/src/monmove.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -85,89 +86,37 @@ static const std::string flag_AUTODOC_COUCH( "AUTODOC_COUCH" ); namespace { -auto has_wall_support( const map &here, const tripoint &anchor ) -> bool +auto get_wall_support( const map &here, const tripoint &anchor ) -> std::optional { const auto neighbor_range = points_in_radius( anchor, 1 ); const std::vector neighbors( neighbor_range.begin(), neighbor_range.end() ); - return std::ranges::any_of( neighbors, [&anchor, &here]( const tripoint &pt ) { + const auto support = std::ranges::find_if( neighbors, [&anchor, &here]( const tripoint &pt ) { return pt.z == anchor.z && pt != anchor && here.impassable_ter_furn( pt ); } ); -} - -auto anchored_on_wall( const map &here, const tripoint &pos ) -> bool -{ - if( !here.has_flag( TFLAG_NO_FLOOR, pos ) ) { - return false; - } - if( has_wall_support( here, pos ) ) { - return true; + if( support == neighbors.end() ) { + return std::nullopt; } - const tripoint below = pos + tripoint_below; - return here.inbounds( below ) && here.impassable( below ); + return *support; } -auto report_missing_lua_ai( const std::string &method ) -> void -{ - static auto warned = std::unordered_set{}; - if( !warned.insert( method ).second ) { - return; - } - debugmsg( "Lua monster AI function '%s' is not defined", method ); -} - -auto report_invalid_lua_ai_return( const std::string &method, const sol::object &value, - sol::state &lua ) -> void +auto has_wall_support( const map &here, const tripoint &anchor ) -> bool { - static auto warned = std::unordered_set{}; - if( !warned.insert( method ).second ) { - return; - } - const auto type_name = get_luna_type( value ); - const auto raw_name = type_name.value_or( - std::string( sol::type_name( lua, value.get_type() ) ) ); - debugmsg( "Lua monster AI function '%s' returned %s, expected boolean or nil", - method, raw_name ); + return get_wall_support( here, anchor ).has_value(); } -auto run_lua_monster_ai( monster &mon ) -> bool +auto anchored_on_wall( const map &here, const tripoint &pos ) -> bool { - const auto &lua_method = mon.type->lua_ai; - if( !lua_method ) { - return false; - } - - auto *lua_state = DynamicDataLoader::get_instance().lua.get(); - if( lua_state == nullptr ) { - return false; - } - - sol::state &lua = lua_state->lua; - sol::object ref = lua.globals()["game"]["monster_ai_functions"][*lua_method]; - if( ref.get_type() != sol::type::function ) { - report_missing_lua_ai( *lua_method ); - return false; - } - - auto func = ref.as(); - sol::protected_function_result res = func( &mon ); - check_func_result( res ); - if( !res.valid() ) { + if( !here.has_flag( TFLAG_NO_FLOOR, pos ) ) { return false; } - const auto value = res.get(); - if( value.get_type() == sol::type::lua_nil ) { - return false; - } - if( value.get_type() != sol::type::boolean ) { - report_invalid_lua_ai_return( *lua_method, value, lua ); - return false; + if( has_wall_support( here, pos ) ) { + return true; } - - return value.as(); + return false; } } // namespace @@ -344,6 +293,12 @@ bool monster::can_reach_to( const tripoint &p ) const const bool is_z_move = p.z != pos().z; if( !is_z_move || is_moving_out_of_reality ) { + if( here.has_flag( TFLAG_NO_FLOOR, p ) && !flies() ) { + if( climbs_walls() ) { + return anchored_on_wall( here, p ); + } + return false; + } return true; } @@ -1942,8 +1897,14 @@ bool monster::move_to( const tripoint &p, bool force, bool step_on_critter, } if( wall_climb_move && z_move && g->u.sees( *this ) ) { - add_msg( _( "The %1$s climbs %2$s the wall." ), name(), - destination.z > posz() ? _( "up" ) : _( "down" ) ); + const auto anchor = destination.z > posz() ? pos() : destination; + const auto support = get_wall_support( g->m, anchor ); + const std::string wall_name = support ? + g->m.disp_name( *support ) : + _( "the wall" ); + add_msg( _( "The %1$s climbs %2$s %3$s." ), name(), + destination.z > posz() ? _( "up" ) : _( "down" ), + wall_name ); } setpos( destination ); diff --git a/src/trapfunc.cpp b/src/trapfunc.cpp index c1050f6fb562..d29005247087 100644 --- a/src/trapfunc.cpp +++ b/src/trapfunc.cpp @@ -3,8 +3,10 @@ #include #include #include +#include #include #include +#include #include "avatar.h" #include "bodypart.h" @@ -1123,7 +1125,7 @@ bool trapfunc::ledge( const tripoint &p, Creature *c, item * ) return false; } monster *m = dynamic_cast( c ); - if( m != nullptr && m->flies() ) { + if( m != nullptr && ( m->flies() || m->climbs_walls() ) ) { return false; } if( !g->m.has_zlevels() ) { From 87fc10c8d05d83575fc0be842217dc22e6618c2d Mon Sep 17 00:00:00 2001 From: shmakota <47482311+shmakota@users.noreply.github.com> Date: Wed, 25 Feb 2026 00:37:12 -0600 Subject: [PATCH 03/12] add mons --- data/json/monstergroups/zombie_upgrades.json | 2 +- data/json/monsters/defense_bot.json | 3 +- data/json/monsters/fungus.json | 4 +-- data/json/monsters/insect_spider.json | 20 +++++------ data/json/monsters/mutant_animal.json | 4 +-- data/json/monsters/zed_misc.json | 35 +++++++++----------- 6 files changed, 32 insertions(+), 36 deletions(-) diff --git a/data/json/monstergroups/zombie_upgrades.json b/data/json/monstergroups/zombie_upgrades.json index 9f79af513467..74c08f693fe8 100644 --- a/data/json/monstergroups/zombie_upgrades.json +++ b/data/json/monstergroups/zombie_upgrades.json @@ -33,7 +33,6 @@ { "monster": "mon_zombie_grabber", "freq": 15, "cost_multiplier": 5 }, { "monster": "mon_zombie_grappler", "freq": 45, "cost_multiplier": 7 }, { "monster": "mon_zombie_hunter", "freq": 30, "cost_multiplier": 5 }, - { "monster": "mon_zombie_climber", "freq": 15, "cost_multiplier": 5 }, { "monster": "mon_skeleton", "freq": 45, "cost_multiplier": 5 }, { "monster": "mon_zombie_smoker", "freq": 15, "cost_multiplier": 5 }, { "monster": "mon_zombie_shady", "freq": 15, "cost_multiplier": 5 }, @@ -130,6 +129,7 @@ "//": "crawling zombie upgrades", "monsters": [ { "monster": "mon_zombie_crawler_pupa", "freq": 500, "cost_multiplier": 2 }, + { "monster": "mon_zombie_crawler_clinger", "freq": 250, "cost_multiplier": 3 }, { "monster": "mon_zombie_crawler_pupa_decoy", "freq": 500, "cost_multiplier": 2 } ] }, diff --git a/data/json/monsters/defense_bot.json b/data/json/monsters/defense_bot.json index f4839ee2a16c..f96a6e5b3434 100644 --- a/data/json/monsters/defense_bot.json +++ b/data/json/monsters/defense_bot.json @@ -236,7 +236,7 @@ "special_attacks": [ [ "TAZER", 5 ] ], "death_drops": { "groups": [ [ "robots", 4 ], [ "skitterbot", 1 ] ] }, "death_function": [ "BROKEN" ], - "flags": [ "SEES", "HEARS", "GOODHEARING", "ELECTRONIC", "COLDPROOF", "NO_BREATHE", "PATH_AVOID_DANGER_1", "BIOPROOF" ] + "flags": [ "SEES", "HEARS", "GOODHEARING", "ELECTRONIC", "COLDPROOF", "CLIMBS", "NO_BREATHE", "PATH_AVOID_DANGER_1", "BIOPROOF" ] }, { "id": "mon_science_bot", @@ -280,6 +280,7 @@ "ELECTRONIC", "COLDPROOF", "ACIDPROOF", + "CLIMBS", "NO_BREATHE", "PRIORITIZE_TARGETS", "PATH_AVOID_DANGER_2", diff --git a/data/json/monsters/fungus.json b/data/json/monsters/fungus.json index 76262237ab11..8479b91fd6da 100644 --- a/data/json/monsters/fungus.json +++ b/data/json/monsters/fungus.json @@ -520,7 +520,7 @@ "harvest": "arachnid", "special_attacks": [ [ "FUNGUS", 200 ] ], "death_function": [ "NORMAL", "FUNGUS" ], - "flags": [ "SEES", "SMELLS", "POISON", "CLIMBS", "PATH_AVOID_FIRE", "PATH_AVOID_FALL" ] + "flags": [ "SEES", "SMELLS", "POISON", "CLIMBS", "CLIMBS_WALLS", "PATH_AVOID_FIRE" ] }, { "id": "mon_spider_fungus", @@ -553,6 +553,6 @@ "harvest": "arachnid", "special_attacks": [ [ "FUNGAL_TRAIL", 3 ] ], "death_function": [ "NORMAL", "FUNGUS" ], - "flags": [ "SEES", "SMELLS", "VENOM", "WEBWALK", "CLIMBS", "PATH_AVOID_FIRE" ] + "flags": [ "SEES", "SMELLS", "VENOM", "WEBWALK", "CLIMBS", "CLIMBS_WALLS", "PATH_AVOID_FIRE" ] } ] diff --git a/data/json/monsters/insect_spider.json b/data/json/monsters/insect_spider.json index 2b0570c2bc14..cae948feb085 100644 --- a/data/json/monsters/insect_spider.json +++ b/data/json/monsters/insect_spider.json @@ -905,7 +905,7 @@ "harvest": "arachnid", "death_function": [ "NORMAL" ], "fungalize_into": "mon_spider_fungus", - "flags": [ "SEES", "SMELLS", "HEARS", "VENOM", "WEBWALK", "CLIMBS", "CLIMBS_WALLS", "PATH_AVOID_FIRE", "PATH_AVOID_FALL" ] + "flags": [ "SEES", "SMELLS", "HEARS", "VENOM", "WEBWALK", "CLIMBS", "CLIMBS_WALLS", "PATH_AVOID_FIRE" ] }, { "id": "mon_spider_web_s", @@ -1041,7 +1041,7 @@ "anger_triggers": [ "STALK", "PLAYER_WEAK", "HURT", "PLAYER_CLOSE" ], "death_function": [ "NORMAL" ], "fungalize_into": "mon_spider_fungus", - "flags": [ "SEES", "SMELLS", "HEARS", "VENOM", "CLIMBS", "CLIMBS_WALLS", "PATH_AVOID_FIRE", "PATH_AVOID_FALL" ] + "flags": [ "SEES", "SMELLS", "HEARS", "VENOM", "CLIMBS", "CLIMBS_WALLS", "PATH_AVOID_FIRE" ] }, { "id": "mon_wasp_larva", @@ -1453,7 +1453,7 @@ "death_function": [ "NORMAL" ], "fungalize_into": "mon_ant_fungus", "special_attacks": [ [ "EAT_FOOD", 3600 ] ], - "flags": [ "SEES", "HEARS", "SMELLS", "CLIMBS", "PATH_AVOID_FIRE", "PATH_AVOID_FALL" ] + "flags": [ "SEES", "HEARS", "SMELLS", "CLIMBS", "CLIMBS_WALLS", "PATH_AVOID_FIRE" ] }, { "id": "mon_ant_acid", @@ -1488,7 +1488,7 @@ "anger_triggers": [ "FRIEND_ATTACKED", "FRIEND_DIED", "HURT", "PLAYER_CLOSE" ], "death_function": [ "ACID", "NORMAL" ], "harvest": "arachnid_acid", - "flags": [ "ACIDPROOF", "CLIMBS", "HEARS", "POISON", "SEES", "SMELLS", "PATH_AVOID_FIRE", "PATH_AVOID_FALL" ] + "flags": [ "ACIDPROOF", "CLIMBS", "CLIMBS_WALLS", "HEARS", "POISON", "SEES", "SMELLS", "PATH_AVOID_FIRE" ] }, { "id": "mon_ant_acid_larva", @@ -1514,7 +1514,7 @@ "upgrades": { "age_grow": 3, "into": "mon_ant_acid" }, "death_function": [ "ACID", "NORMAL" ], "harvest": "arachnid_acid", - "flags": [ "ACIDPROOF", "LARVA", "POISON", "SMELLS" ] + "flags": [ "ACIDPROOF", "CLIMBS", "CLIMBS_WALLS", "LARVA", "POISON", "SMELLS" ] }, { "id": "mon_ant_acid_queen", @@ -1546,7 +1546,7 @@ "anger_triggers": [ "FRIEND_ATTACKED", "FRIEND_DIED", "HURT", "PLAYER_CLOSE" ], "death_function": [ "NORMAL" ], "harvest": "acidant_queen", - "flags": [ "ACIDPROOF", "CLIMBS", "HEARS", "QUEEN", "SEES", "SMELLS", "PATH_AVOID_FIRE", "PATH_AVOID_FALL" ] + "flags": [ "ACIDPROOF", "CLIMBS", "CLIMBS_WALLS", "HEARS", "QUEEN", "SEES", "SMELLS", "PATH_AVOID_FIRE" ] }, { "id": "mon_ant_acid_soldier", @@ -1581,7 +1581,7 @@ "anger_triggers": [ "FRIEND_ATTACKED", "FRIEND_DIED", "HURT", "PLAYER_CLOSE" ], "death_function": [ "ACID", "NORMAL" ], "harvest": "arachnid_acid", - "flags": [ "ACIDPROOF", "SHORTACIDTRAIL", "CLIMBS", "HEARS", "POISON", "SEES", "SMELLS", "PATH_AVOID_FIRE", "PATH_AVOID_FALL" ] + "flags": [ "ACIDPROOF", "SHORTACIDTRAIL", "CLIMBS", "CLIMBS_WALLS", "HEARS", "POISON", "SEES", "SMELLS", "PATH_AVOID_FIRE" ] }, { "id": "mon_ant_larva", @@ -1607,7 +1607,7 @@ "harvest": "arachnid", "upgrades": { "age_grow": 3, "into": "mon_ant" }, "death_function": [ "NORMAL" ], - "flags": [ "SMELLS", "LARVA" ] + "flags": [ "CLIMBS", "CLIMBS_WALLS", "SMELLS", "LARVA" ] }, { "id": "mon_ant_queen", @@ -1638,7 +1638,7 @@ "anger_triggers": [ "FRIEND_ATTACKED", "FRIEND_DIED", "HURT" ], "death_function": [ "NORMAL" ], "fungalize_into": "mon_ant_fungus", - "flags": [ "SMELLS", "QUEEN", "CLIMBS", "PATH_AVOID_FIRE", "PATH_AVOID_FALL" ] + "flags": [ "SMELLS", "QUEEN", "CLIMBS", "CLIMBS_WALLS", "PATH_AVOID_FIRE" ] }, { "id": "mon_ant_soldier", @@ -1672,7 +1672,7 @@ "anger_triggers": [ "FRIEND_ATTACKED", "FRIEND_DIED", "HURT", "PLAYER_CLOSE" ], "death_function": [ "NORMAL" ], "fungalize_into": "mon_ant_fungus", - "flags": [ "SEES", "HEARS", "SMELLS", "CLIMBS", "PATH_AVOID_FIRE", "PATH_AVOID_FALL" ] + "flags": [ "SEES", "HEARS", "SMELLS", "CLIMBS", "CLIMBS_WALLS", "PATH_AVOID_FIRE" ] }, { "id": "mon_locust", diff --git a/data/json/monsters/mutant_animal.json b/data/json/monsters/mutant_animal.json index fd04f026842e..0561283b4899 100644 --- a/data/json/monsters/mutant_animal.json +++ b/data/json/monsters/mutant_animal.json @@ -359,7 +359,7 @@ "//": " 201 days gestation period. The fawn will stay with its mother for approximately one year, suckling for three to four months.", "baby_flags": [ "SPRING", "SUMMER" ], "special_attacks": [ [ "EAT_CROP", 7200 ] ], - "flags": [ "SEES", "HEARS", "SMELLS", "ANIMAL", "PATH_AVOID_DANGER_1", "WARM" ] + "flags": [ "SEES", "HEARS", "SMELLS", "ANIMAL", "CLIMBS", "PATH_AVOID_DANGER_1", "WARM" ] }, { "id": "mon_deer_mutant_spider_fawn", @@ -391,7 +391,7 @@ "fear_triggers": [ "SOUND", "PLAYER_CLOSE" ], "death_function": [ "NORMAL" ], "upgrades": { "age_grow": 330, "into": "mon_deer_mutant_spider" }, - "flags": [ "SEES", "HEARS", "SMELLS", "ANIMAL", "PATH_AVOID_DANGER_1", "WARM", "STUMBLES" ] + "flags": [ "SEES", "HEARS", "SMELLS", "ANIMAL", "CLIMBS", "PATH_AVOID_DANGER_1", "WARM", "STUMBLES" ] }, { "id": "mon_dog_mutant_mongrel", diff --git a/data/json/monsters/zed_misc.json b/data/json/monsters/zed_misc.json index 6b7581205feb..cc90fe0b67aa 100644 --- a/data/json/monsters/zed_misc.json +++ b/data/json/monsters/zed_misc.json @@ -560,24 +560,6 @@ "upgrades": { "half_life": 28, "into": "mon_zombie_predator" }, "flags": [ "SEES", "HEARS", "SMELLS", "WARM", "BASHES", "POISON", "NO_BREATHE", "REVIVES", "CLIMBS", "PUSH_MON" ] }, - { - "id": "mon_zombie_climber", - "type": "MONSTER", - "copy-from": "mon_zombie_hunter", - "name": { "str": "climbing zombie" }, - "description": "This lithe zombie scuttles up vertical surfaces on all fours, clawing into cracks and ledges to haul itself to higher ground.", - "color": "light_gray", - "speed": 120, - "special_attacks": [ - { "id": "scratch", "damage_max_instance": [ { "damage_type": "cut", "amount": 12 } ] }, - { - "type": "bite", - "cooldown": 10, - "damage_max_instance": [ { "damage_type": "stab", "amount": 12, "armor_multiplier": 0.7 } ] - } - ], - "extend": { "flags": [ "CLIMBS_WALLS" ] } - }, { "id": "mon_zombie_jackson", "type": "MONSTER", @@ -717,7 +699,7 @@ "id": "mon_zombie_predator", "type": "MONSTER", "name": { "str": "zombie predator" }, - "description": "With its joints in odd places and angles, this humanoid creature prowls across the landscape with surprising speed. Its teeth and arms are sharpened into fine points, and black ooze seeps out from cuts between its muscles.", + "description": "With its joints in odd places and angles, this humanoid creature prowls across the landscape with surprising speed, clawing up walls as easily as it bounds across open ground. Its teeth and arms are sharpened into fine points, and black ooze seeps out from cuts between its muscles.", "default_faction": "zombie", "bodytype": "human", "species": [ "ZOMBIE", "HUMAN" ], @@ -754,7 +736,20 @@ "death_drops": "default_zombie_death_drops", "death_function": [ "NORMAL" ], "burn_into": "mon_zombie_scorched", - "flags": [ "SEES", "HEARS", "SMELLS", "WARM", "BASHES", "POISON", "NO_BREATHE", "REVIVES", "PUSH_MON" ] + "flags": [ "SEES", "HEARS", "SMELLS", "WARM", "BASHES", "POISON", "NO_BREATHE", "REVIVES", "CLIMBS", "CLIMBS_WALLS", "PUSH_MON" ] + }, + { + "id": "mon_zombie_crawler_clinger", + "type": "MONSTER", + "copy-from": "mon_zombie_crawler", + "name": { "str": "clinger crawler" }, + "description": "Only the upper half remains of this corpse, making it eerily lightweight. It drags itself with raw arms, wedging shattered bone into cracks to slowly clamber up walls and ceilings.", + "color": "light_gray", + "volume": "40000 ml", + "weight": "45000 g", + "hp": 50, + "speed": 30, + "extend": { "flags": [ "CLIMBS", "CLIMBS_WALLS" ] } }, { "id": "mon_zombie_screecher", From e7478049c163292b593b032c3daf0e4dee320289 Mon Sep 17 00:00:00 2001 From: shmakota <47482311+shmakota@users.noreply.github.com> Date: Wed, 25 Feb 2026 02:49:02 -0600 Subject: [PATCH 04/12] wall coverage & visible effect --- data/json/effects.json | 9 ++++ data/json/monsters/mutant_animal.json | 9 +++- src/handle_action.cpp | 8 +++- src/monmove.cpp | 62 ++++++++++++++++++++++++--- src/trapfunc.cpp | 10 ++++- 5 files changed, 86 insertions(+), 12 deletions(-) diff --git a/data/json/effects.json b/data/json/effects.json index cfbd430bae55..6ca7d501571a 100644 --- a/data/json/effects.json +++ b/data/json/effects.json @@ -396,6 +396,15 @@ "rating": "bad", "permanent": true }, + { + "type": "effect_type", + "id": "wall_clinging", + "name": [ "Clinging" ], + "desc": [ "Clinging to a vertical surface." ], + "rating": "neutral", + "show_in_info": true, + "permanent": true + }, { "type": "effect_type", "id": "blind", diff --git a/data/json/monsters/mutant_animal.json b/data/json/monsters/mutant_animal.json index 0561283b4899..0b33321b2fb9 100644 --- a/data/json/monsters/mutant_animal.json +++ b/data/json/monsters/mutant_animal.json @@ -359,7 +359,12 @@ "//": " 201 days gestation period. The fawn will stay with its mother for approximately one year, suckling for three to four months.", "baby_flags": [ "SPRING", "SUMMER" ], "special_attacks": [ [ "EAT_CROP", 7200 ] ], - "flags": [ "SEES", "HEARS", "SMELLS", "ANIMAL", "CLIMBS", "PATH_AVOID_DANGER_1", "WARM" ] + "petfood": { + "food": [ "CATTLEFOOD" ], + "feed": "The %s seems to like you!", + "pet": "The %s makes a happy arachnid-like chitter at you as you pat its head." + }, + "flags": [ "SEES", "HEARS", "SMELLS", "ANIMAL", "CLIMBS", "CLIMBS_WALLS", "PET_MOUNTABLE", "PATH_AVOID_DANGER_1", "WARM" ] }, { "id": "mon_deer_mutant_spider_fawn", @@ -391,7 +396,7 @@ "fear_triggers": [ "SOUND", "PLAYER_CLOSE" ], "death_function": [ "NORMAL" ], "upgrades": { "age_grow": 330, "into": "mon_deer_mutant_spider" }, - "flags": [ "SEES", "HEARS", "SMELLS", "ANIMAL", "CLIMBS", "PATH_AVOID_DANGER_1", "WARM", "STUMBLES" ] + "flags": [ "SEES", "HEARS", "SMELLS", "ANIMAL", "CLIMBS", "CLIMBS_WALLS", "PATH_AVOID_DANGER_1", "WARM", "STUMBLES" ] }, { "id": "mon_dog_mutant_mongrel", diff --git a/src/handle_action.cpp b/src/handle_action.cpp index ef6b99b6fd7f..bb0f66e9c714 100644 --- a/src/handle_action.cpp +++ b/src/handle_action.cpp @@ -1841,7 +1841,9 @@ bool game::handle_action() const bool can_use_stairs = mon->has_flag( MF_RIDEABLE_MECH ) || mon->has_flag( MF_MOUNTABLE_STAIRS ) || - mon->has_flag( MF_FLIES ); + mon->has_flag( MF_FLIES ) || + mon->has_flag( MF_CLIMBS ) || + mon->has_flag( MF_CLIMBS_WALLS ); if( !can_use_stairs ) { add_msg( m_info, _( "Your mount can't go downstairs while riding." ) ); @@ -1885,7 +1887,9 @@ bool game::handle_action() const bool can_use_stairs = mon->has_flag( MF_RIDEABLE_MECH ) || mon->has_flag( MF_MOUNTABLE_STAIRS ) || - mon->has_flag( MF_FLIES ); + mon->has_flag( MF_FLIES ) || + mon->has_flag( MF_CLIMBS ) || + mon->has_flag( MF_CLIMBS_WALLS ); if( !can_use_stairs ) { add_msg( m_info, _( "Your mount can't go upstairs or climb while riding." ) ); diff --git a/src/monmove.cpp b/src/monmove.cpp index f5e39319c4b9..dbc94e699831 100644 --- a/src/monmove.cpp +++ b/src/monmove.cpp @@ -73,6 +73,7 @@ static const efftype_id effect_pacified( "pacified" ); static const efftype_id effect_pushed( "pushed" ); static const efftype_id effect_stunned( "stunned" ); static const efftype_id effect_led_by_leash( "led_by_leash" ); +static const efftype_id effect_wall_clinging( "wall_clinging" ); static const itype_id itype_pressurized_tank( "pressurized_tank" ); @@ -102,21 +103,41 @@ auto get_wall_support( const map &here, const tripoint &anchor ) -> std::optiona return *support; } +auto wall_support_count( const map &here, const tripoint &anchor ) -> int +{ + const auto neighbor_range = points_in_radius( anchor, 1 ); + const std::vector neighbors( neighbor_range.begin(), neighbor_range.end() ); + + return std::ranges::count_if( neighbors, [&anchor, &here]( const tripoint &pt ) { + const bool same_level = pt.z == anchor.z; + const bool cardinal = pt.x == anchor.x || pt.y == anchor.y; + return same_level && cardinal && pt != anchor && here.impassable_ter_furn( pt ); + } ); +} + auto has_wall_support( const map &here, const tripoint &anchor ) -> bool { - return get_wall_support( here, anchor ).has_value(); + return wall_support_count( here, anchor ) >= 1; } auto anchored_on_wall( const map &here, const tripoint &pos ) -> bool { - if( !here.has_flag( TFLAG_NO_FLOOR, pos ) ) { - return false; - } + const auto supports = wall_support_count( here, pos ); + const tripoint roof_above = pos + tripoint_above; - if( has_wall_support( here, pos ) ) { + if( here.has_flag( "ROOF", roof_above ) && here.inbounds( roof_above ) ) { return true; } - return false; + + if( here.has_flag( TFLAG_NO_FLOOR, pos ) ) { + const tripoint below = pos + tripoint_below; + if( here.has_floor( below ) ) { + return supports >= 1; + } + return supports >= 2; + } + + return supports >= 1; } } // namespace @@ -930,8 +951,11 @@ void monster::move() // Give nursebots a chance to do surgery. nursebot_operate( dragged_foe ); + const bool anchored_on_wall_now = climbs_walls() && anchored_on_wall( g->m, pos() ); + const bool anchored_on_wall_then = has_effect( effect_wall_clinging ); + // Allow wall-climbers to hang in open air when anchored; others should resolve traps/falls - if( !flies() && !( climbs_walls() && anchored_on_wall( g->m, pos() ) ) && + if( !flies() && !anchored_on_wall_now && g->m.has_flag( TFLAG_NO_FLOOR, pos() ) ) { g->m.creature_on_trap( *this, false ); if( is_dead() ) { @@ -939,6 +963,22 @@ void monster::move() } } + if( anchored_on_wall_now ) { + if( !anchored_on_wall_then && g->u.sees( *this ) ) { + const std::string support_name = [&]() { + const auto support = get_wall_support( g->m, pos() ); + if( support.has_value() ) { + return g->m.disp_name( *support ); + } + return std::string( _( "the wall" ) ); + }(); + add_msg( _( "The %1$s begins to climb up %2$s." ), name(), support_name ); + } + add_effect( effect_wall_clinging, 1_turns ); + } else if( anchored_on_wall_then ) { + remove_effect( effect_wall_clinging ); + } + // if the monster is in a deep water tile, it has a chance to drown if( die_if_drowning( pos(), 10 ) ) { return; @@ -1953,6 +1993,14 @@ bool monster::move_to( const tripoint &p, bool force, bool step_on_critter, return true; } } + + const bool anchored_on_wall_here = climbs_walls() && anchored_on_wall( g->m, pos() ); + if( anchored_on_wall_here ) { + add_effect( effect_wall_clinging, 1_turns ); + } else if( has_effect( effect_wall_clinging ) ) { + remove_effect( effect_wall_clinging ); + } + if( !will_be_water && ( digs() || can_dig() ) ) { set_underwater( g->m.ter( pos() )->is_diggable() ); } diff --git a/src/trapfunc.cpp b/src/trapfunc.cpp index d29005247087..3abc79477611 100644 --- a/src/trapfunc.cpp +++ b/src/trapfunc.cpp @@ -1124,8 +1124,16 @@ bool trapfunc::ledge( const tripoint &p, Creature *c, item * ) if( c == nullptr ) { return false; } + if( Character *ch = dynamic_cast( c ) ) { + if( ch->is_mounted() ) { + monster *mount = ch->mounted_creature.get(); + if( mount != nullptr && ( mount->flies() || mount->climbs() || mount->climbs_walls() ) ) { + return false; + } + } + } monster *m = dynamic_cast( c ); - if( m != nullptr && ( m->flies() || m->climbs_walls() ) ) { + if( m != nullptr && ( m->flies() || m->climbs() || m->climbs_walls() ) ) { return false; } if( !g->m.has_zlevels() ) { From 63cba07dba4f860ab21f75437a355bc13ee0997b Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 25 Feb 2026 19:07:29 +0000 Subject: [PATCH 05/12] style(autofix.ci): automated formatting --- data/json/monsters/insect_spider.json | 13 ++++++++++++- src/monmove.cpp | 7 ++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/data/json/monsters/insect_spider.json b/data/json/monsters/insect_spider.json index cae948feb085..76cc1f44e178 100644 --- a/data/json/monsters/insect_spider.json +++ b/data/json/monsters/insect_spider.json @@ -769,7 +769,18 @@ "anger_triggers": [ "STALK", "PLAYER_WEAK", "PLAYER_CLOSE" ], "death_function": [ "NORMAL" ], "fungalize_into": "mon_spider_fungus", - "flags": [ "SEES", "SMELLS", "HEARS", "VENOM", "WEBWALK", "CLIMBS", "CLIMBS_WALLS", "HARDTOSHOOT", "PUSH_MON", "PATH_AVOID_FIRE" ], + "flags": [ + "SEES", + "SMELLS", + "HEARS", + "VENOM", + "WEBWALK", + "CLIMBS", + "CLIMBS_WALLS", + "HARDTOSHOOT", + "PUSH_MON", + "PATH_AVOID_FIRE" + ], "//": "No, they are not in fact the most venomous spider in the world." }, { diff --git a/src/monmove.cpp b/src/monmove.cpp index dbc94e699831..f4502acdc072 100644 --- a/src/monmove.cpp +++ b/src/monmove.cpp @@ -92,7 +92,7 @@ auto get_wall_support( const map &here, const tripoint &anchor ) -> std::optiona const auto neighbor_range = points_in_radius( anchor, 1 ); const std::vector neighbors( neighbor_range.begin(), neighbor_range.end() ); - const auto support = std::ranges::find_if( neighbors, [&anchor, &here]( const tripoint &pt ) { + const auto support = std::ranges::find_if( neighbors, [&anchor, &here]( const tripoint & pt ) { return pt.z == anchor.z && pt != anchor && here.impassable_ter_furn( pt ); } ); @@ -108,7 +108,7 @@ auto wall_support_count( const map &here, const tripoint &anchor ) -> int const auto neighbor_range = points_in_radius( anchor, 1 ); const std::vector neighbors( neighbor_range.begin(), neighbor_range.end() ); - return std::ranges::count_if( neighbors, [&anchor, &here]( const tripoint &pt ) { + return std::ranges::count_if( neighbors, [&anchor, &here]( const tripoint & pt ) { const bool same_level = pt.z == anchor.z; const bool cardinal = pt.x == anchor.x || pt.y == anchor.y; return same_level && cardinal && pt != anchor && here.impassable_ter_furn( pt ); @@ -971,7 +971,8 @@ void monster::move() return g->m.disp_name( *support ); } return std::string( _( "the wall" ) ); - }(); + } + (); add_msg( _( "The %1$s begins to climb up %2$s." ), name(), support_name ); } add_effect( effect_wall_clinging, 1_turns ); From 5dffb0b61c6f8ec5902eee96236edeac8af8e884 Mon Sep 17 00:00:00 2001 From: shmakota <47482311+shmakota@users.noreply.github.com> Date: Wed, 25 Feb 2026 22:45:31 -0600 Subject: [PATCH 06/12] fix monsters getting stuck, misc changes --- src/monstergenerator.cpp | 12 ++++++--- src/pathfinding.cpp | 58 +++++++++++++++++++++++++++++++++------- src/pathfinding.h | 5 +++- 3 files changed, 60 insertions(+), 15 deletions(-) diff --git a/src/monstergenerator.cpp b/src/monstergenerator.cpp index 227e3e725531..94905626dd95 100644 --- a/src/monstergenerator.cpp +++ b/src/monstergenerator.cpp @@ -1084,16 +1084,20 @@ void mtype::setup_pathfinding_deferred() this->route_settings.f_limit_based_on_max_dist = get_option( "PATHFINDING_MAX_F_LIMIT_BASED_ON_MAX_DIST" ); - const bool default_override = get_option( "PATHFINDING_DEFAULT_IS_OVERRIDE" ); - const float range_mult = get_option( "PATHFINDING_RANGE_MULT" ); + const auto default_override = get_option( "PATHFINDING_DEFAULT_IS_OVERRIDE" ); + const auto range_mult = get_option( "PATHFINDING_RANGE_MULT" ); - if( this->has_flag( MF_CLIMBS ) || this->has_flag( MF_CLIMBS_WALLS ) ) { + const auto climbs_walls = this->has_flag( MF_CLIMBS_WALLS ); + const auto flies = this->has_flag( MF_FLIES ); + + if( this->has_flag( MF_CLIMBS ) || climbs_walls ) { this->legacy_path_settings.climb_cost = 3; this->path_settings.climb_cost = 3.0; } - if( this->has_flag( MF_FLIES ) || this->has_flag( MF_CLIMBS_WALLS ) ) { + if( flies || climbs_walls ) { this->path_settings.can_fly = true; + this->path_settings.needs_wall_cling = climbs_walls && !flies; } const auto extract_into = [this]( std::string field, T & out ) { diff --git a/src/pathfinding.cpp b/src/pathfinding.cpp index 0fb80caf0735..31a5349f50de 100755 --- a/src/pathfinding.cpp +++ b/src/pathfinding.cpp @@ -55,12 +55,40 @@ static constexpr bool is_inf( float x ) #endif } +namespace +{ + +constexpr auto max_wall_climb_difficulty = 10; + +auto has_cardinal_wall_support( const map &here, const tripoint &anchor ) -> bool +{ + const auto neighbor_range = points_in_radius( anchor, 1 ); + return std::ranges::any_of( neighbor_range, [&anchor, &here]( const tripoint &pt ) { + const bool same_level = pt.z == anchor.z; + const bool cardinal = pt != anchor && ( pt.x == anchor.x || pt.y == anchor.y ); + return same_level && cardinal && here.impassable_ter_furn( pt ); + } ); +} + +auto can_wall_cling_to_change( const map &here, const Pathfinding::ZLevelChange &change, + bool we_go_up ) -> bool +{ + const tripoint &anchor = we_go_up ? change.from : change.to; + if( here.climb_difficulty( anchor ) > max_wall_climb_difficulty ) { + return false; + } + return has_cardinal_wall_support( here, anchor ); +} + +} // namespace + // PathfindingSettings impls int PathfindingSettings::z_move_type() const { int result = 0; result += this->can_fly ? 1 << 0 : 0; result += this->can_climb_stairs ? 1 << 1 : 0; + result += this->needs_wall_cling ? 1 << 2 : 0; return result; } // RouteSettings impls @@ -908,6 +936,8 @@ std::vector Pathfinding::get_route_3d( // Instead, we will **only** consider taking z_changes that bring us closer to target's Z level. const bool we_go_up = to.z > from.z; + const map &here = get_map(); + Pathfinding::update_z_caches( path_settings.can_fly ); // Determine our Z-path @@ -1001,18 +1031,26 @@ std::vector Pathfinding::get_route_3d( continue; } Pathfinding::ZLevelChangeOpenAirPair z_pair = target[p]; + const Pathfinding::ZLevelChange *candidate = nullptr; if( we_go_up && z_pair.reach_from_below.has_value() ) { - const float dist = rl_dist_exact( tripoint( cur_origin_point, 0 ), tripoint( p, 0 ) ); - if( dist < best_distance ) { - best_z_change = *z_pair.reach_from_below; - best_distance = dist; - } + candidate = &*z_pair.reach_from_below; } else if( !we_go_up && z_pair.reach_from_above.has_value() ) { - const float dist = rl_dist_exact( tripoint( cur_origin_point, 0 ), tripoint( p, 0 ) ); - if( dist < best_distance ) { - best_z_change = *z_pair.reach_from_above; - best_distance = dist; - } + candidate = &*z_pair.reach_from_above; + } + + if( candidate == nullptr ) { + continue; + } + + if( path_settings.needs_wall_cling && + !can_wall_cling_to_change( here, *candidate, we_go_up ) ) { + continue; + } + + const float dist = rl_dist_exact( tripoint( cur_origin_point, 0 ), tripoint( p, 0 ) ); + if( dist < best_distance ) { + best_z_change = *candidate; + best_distance = dist; } } } diff --git a/src/pathfinding.h b/src/pathfinding.h index bbfe62ce2738..d91a5a1cbd32 100755 --- a/src/pathfinding.h +++ b/src/pathfinding.h @@ -56,6 +56,10 @@ struct PathfindingSettings { // and travel over open air and go up and down from there bool can_fly = false; + // Do we require adjacent wall support to use open-air z-level changes? + // Used by wall climbers that are not true fliers. + bool needs_wall_cling = false; + // Can we climb stairs? `can_fly == true` overrides this value to be true. bool can_climb_stairs = false; @@ -337,4 +341,3 @@ class Pathfinding // such as change in terrain static void mark_dirty_z_cache(); }; - From e6840ab704ba0c7e4469adf10adf7db746a50cc8 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 26 Feb 2026 04:46:45 +0000 Subject: [PATCH 07/12] style(autofix.ci): automated formatting --- src/pathfinding.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pathfinding.cpp b/src/pathfinding.cpp index 31a5349f50de..7cfdec2d34ff 100755 --- a/src/pathfinding.cpp +++ b/src/pathfinding.cpp @@ -63,7 +63,7 @@ constexpr auto max_wall_climb_difficulty = 10; auto has_cardinal_wall_support( const map &here, const tripoint &anchor ) -> bool { const auto neighbor_range = points_in_radius( anchor, 1 ); - return std::ranges::any_of( neighbor_range, [&anchor, &here]( const tripoint &pt ) { + return std::ranges::any_of( neighbor_range, [&anchor, &here]( const tripoint & pt ) { const bool same_level = pt.z == anchor.z; const bool cardinal = pt != anchor && ( pt.x == anchor.x || pt.y == anchor.y ); return same_level && cardinal && here.impassable_ter_furn( pt ); From 11bf2ef2bd043283b6a5235f80c95e23218aa45c Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 02:10:30 +0000 Subject: [PATCH 08/12] style(autofix.ci): automated formatting --- src/monmove.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/monmove.cpp b/src/monmove.cpp index fec44e201f57..e399ee9edc4c 100644 --- a/src/monmove.cpp +++ b/src/monmove.cpp @@ -1498,7 +1498,8 @@ void monster::execute_action( const monster_action_t &action ) if( !anchored_on_wall_then && g->u.sees( *this ) ) { const auto support_name = [&here, this]() -> std::string { const auto support = get_wall_support( here, pos() ); - if( support.has_value() ) { + if( support.has_value() ) + { return here.disp_name( *support ); } return std::string( _( "the wall" ) ); From 5cb04aca9a2f5b3652b34e377aaa1023792fb9ee Mon Sep 17 00:00:00 2001 From: shmakota <47482311+shmakota@users.noreply.github.com> Date: Mon, 2 Mar 2026 21:56:15 -0600 Subject: [PATCH 09/12] all fixed (thanks AI) --- src/map.cpp | 15 ++++++++++++--- src/monmove.cpp | 31 ++++++++++++++++++++++++++----- src/pathfinding.cpp | 45 ++++++++++++++++++++++++++++++++++----------- 3 files changed, 72 insertions(+), 19 deletions(-) diff --git a/src/map.cpp b/src/map.cpp index f7bdfee5b2be..77927023b203 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -2298,8 +2298,8 @@ int map::climb_difficulty( const tripoint &p ) const return INT_MAX; } - int best_difficulty = INT_MAX; - int blocks_movement = 0; + auto best_difficulty = INT_MAX; + auto blocks_movement = 0; if( has_flag( "LADDER", p ) ) { // Really easy, but you have to stand on the tile return 1; @@ -2309,7 +2309,16 @@ int map::climb_difficulty( const tripoint &p ) const best_difficulty = 7; } - for( const auto &pt : points_in_radius( p, 1 ) ) { + const auto neighbor_range = points_in_radius( p, 1 ); + auto climb_points = std::vector( neighbor_range.begin(), neighbor_range.end() ); + + if( has_flag( TFLAG_NO_FLOOR, p ) ) { + const auto below = p + tripoint_below; + const auto below_range = points_in_radius( below, 1 ); + climb_points.insert( climb_points.end(), below_range.begin(), below_range.end() ); + } + + for( const tripoint &pt : climb_points ) { if( impassable_ter_furn( pt ) ) { // TODO: Non-hardcoded climbability best_difficulty = std::min( best_difficulty, 10 ); diff --git a/src/monmove.cpp b/src/monmove.cpp index e399ee9edc4c..57b6858d4041 100644 --- a/src/monmove.cpp +++ b/src/monmove.cpp @@ -108,13 +108,34 @@ auto get_wall_support( const map &here, const tripoint &anchor ) -> std::optiona auto wall_support_count( const map &here, const tripoint &anchor ) -> int { const auto neighbor_range = points_in_radius( anchor, 1 ); - const std::vector neighbors( neighbor_range.begin(), neighbor_range.end() ); + auto neighbors = std::vector( neighbor_range.begin(), neighbor_range.end() ); + + const auto is_cardinal_support = [&here]( const tripoint ¢er, + const tripoint &pt ) { + const bool same_level = pt.z == center.z; + const bool cardinal = pt.x == center.x || pt.y == center.y; + return same_level && cardinal && here.impassable_ter_furn( pt ); + }; - return std::ranges::count_if( neighbors, [&anchor, &here]( const tripoint & pt ) { - const bool same_level = pt.z == anchor.z; - const bool cardinal = pt.x == anchor.x || pt.y == anchor.y; - return same_level && cardinal && pt != anchor && here.impassable_ter_furn( pt ); + const auto same_level_supports = std::ranges::count_if( neighbors, + [&anchor, &is_cardinal_support]( const tripoint &pt ) { + return is_cardinal_support( anchor, pt ); } ); + + if( !here.has_flag( TFLAG_NO_FLOOR, anchor ) ) { + return same_level_supports; + } + + const auto below = anchor + tripoint_below; + const auto below_range = points_in_radius( below, 1 ); + neighbors.assign( below_range.begin(), below_range.end() ); + + const auto below_supports = std::ranges::count_if( neighbors, + [&below, &is_cardinal_support]( const tripoint &pt ) { + return is_cardinal_support( below, pt ); + } ); + + return same_level_supports + below_supports; } auto has_wall_support( const map &here, const tripoint &anchor ) -> bool diff --git a/src/pathfinding.cpp b/src/pathfinding.cpp index 7cfdec2d34ff..f4b2c7a9e309 100755 --- a/src/pathfinding.cpp +++ b/src/pathfinding.cpp @@ -63,21 +63,35 @@ constexpr auto max_wall_climb_difficulty = 10; auto has_cardinal_wall_support( const map &here, const tripoint &anchor ) -> bool { const auto neighbor_range = points_in_radius( anchor, 1 ); - return std::ranges::any_of( neighbor_range, [&anchor, &here]( const tripoint & pt ) { - const bool same_level = pt.z == anchor.z; - const bool cardinal = pt != anchor && ( pt.x == anchor.x || pt.y == anchor.y ); + auto neighbors = std::vector( neighbor_range.begin(), neighbor_range.end() ); + + const auto is_cardinal_support = [&here]( const tripoint ¢er, + const tripoint &pt ) { + const bool same_level = pt.z == center.z; + const bool cardinal = pt.x == center.x || pt.y == center.y; return same_level && cardinal && here.impassable_ter_furn( pt ); + }; + + const auto same_level_support = std::ranges::any_of( neighbors, + [&anchor, &is_cardinal_support]( const tripoint &pt ) { + return pt != anchor && is_cardinal_support( anchor, pt ); } ); -} -auto can_wall_cling_to_change( const map &here, const Pathfinding::ZLevelChange &change, - bool we_go_up ) -> bool -{ - const tripoint &anchor = we_go_up ? change.from : change.to; - if( here.climb_difficulty( anchor ) > max_wall_climb_difficulty ) { + if( same_level_support ) { + return true; + } + + if( !here.has_flag( TFLAG_NO_FLOOR, anchor ) ) { return false; } - return has_cardinal_wall_support( here, anchor ); + + const auto below = anchor + tripoint_below; + const auto below_range = points_in_radius( below, 1 ); + neighbors.assign( below_range.begin(), below_range.end() ); + + return std::ranges::any_of( neighbors, [&below, &is_cardinal_support]( const tripoint &pt ) { + return is_cardinal_support( below, pt ); + } ); } } // namespace @@ -940,6 +954,15 @@ std::vector Pathfinding::get_route_3d( Pathfinding::update_z_caches( path_settings.can_fly ); + const auto can_wall_cling_to_change = [&here]( const Pathfinding::ZLevelChange &change, + const bool we_go_up ) { + const tripoint &anchor = we_go_up ? change.from : change.to; + if( here.climb_difficulty( anchor ) > max_wall_climb_difficulty ) { + return false; + } + return has_cardinal_wall_support( here, anchor ); + }; + // Determine our Z-path std::vector z_path; { @@ -1043,7 +1066,7 @@ std::vector Pathfinding::get_route_3d( } if( path_settings.needs_wall_cling && - !can_wall_cling_to_change( here, *candidate, we_go_up ) ) { + !can_wall_cling_to_change( *candidate, we_go_up ) ) { continue; } From 228d1d721a367d827855a84bce40b9c41bc1ee30 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 03:57:35 +0000 Subject: [PATCH 10/12] style(autofix.ci): automated formatting --- src/monmove.cpp | 8 ++++---- src/pathfinding.cpp | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/monmove.cpp b/src/monmove.cpp index 57b6858d4041..f76a816909bc 100644 --- a/src/monmove.cpp +++ b/src/monmove.cpp @@ -110,15 +110,15 @@ auto wall_support_count( const map &here, const tripoint &anchor ) -> int const auto neighbor_range = points_in_radius( anchor, 1 ); auto neighbors = std::vector( neighbor_range.begin(), neighbor_range.end() ); - const auto is_cardinal_support = [&here]( const tripoint ¢er, - const tripoint &pt ) { + const auto is_cardinal_support = [&here]( const tripoint & center, + const tripoint & pt ) { const bool same_level = pt.z == center.z; const bool cardinal = pt.x == center.x || pt.y == center.y; return same_level && cardinal && here.impassable_ter_furn( pt ); }; const auto same_level_supports = std::ranges::count_if( neighbors, - [&anchor, &is_cardinal_support]( const tripoint &pt ) { + [&anchor, &is_cardinal_support]( const tripoint & pt ) { return is_cardinal_support( anchor, pt ); } ); @@ -131,7 +131,7 @@ auto wall_support_count( const map &here, const tripoint &anchor ) -> int neighbors.assign( below_range.begin(), below_range.end() ); const auto below_supports = std::ranges::count_if( neighbors, - [&below, &is_cardinal_support]( const tripoint &pt ) { + [&below, &is_cardinal_support]( const tripoint & pt ) { return is_cardinal_support( below, pt ); } ); diff --git a/src/pathfinding.cpp b/src/pathfinding.cpp index f4b2c7a9e309..4fa82c72091b 100755 --- a/src/pathfinding.cpp +++ b/src/pathfinding.cpp @@ -65,15 +65,15 @@ auto has_cardinal_wall_support( const map &here, const tripoint &anchor ) -> boo const auto neighbor_range = points_in_radius( anchor, 1 ); auto neighbors = std::vector( neighbor_range.begin(), neighbor_range.end() ); - const auto is_cardinal_support = [&here]( const tripoint ¢er, - const tripoint &pt ) { + const auto is_cardinal_support = [&here]( const tripoint & center, + const tripoint & pt ) { const bool same_level = pt.z == center.z; const bool cardinal = pt.x == center.x || pt.y == center.y; return same_level && cardinal && here.impassable_ter_furn( pt ); }; const auto same_level_support = std::ranges::any_of( neighbors, - [&anchor, &is_cardinal_support]( const tripoint &pt ) { + [&anchor, &is_cardinal_support]( const tripoint & pt ) { return pt != anchor && is_cardinal_support( anchor, pt ); } ); @@ -89,7 +89,7 @@ auto has_cardinal_wall_support( const map &here, const tripoint &anchor ) -> boo const auto below_range = points_in_radius( below, 1 ); neighbors.assign( below_range.begin(), below_range.end() ); - return std::ranges::any_of( neighbors, [&below, &is_cardinal_support]( const tripoint &pt ) { + return std::ranges::any_of( neighbors, [&below, &is_cardinal_support]( const tripoint & pt ) { return is_cardinal_support( below, pt ); } ); } @@ -954,7 +954,7 @@ std::vector Pathfinding::get_route_3d( Pathfinding::update_z_caches( path_settings.can_fly ); - const auto can_wall_cling_to_change = [&here]( const Pathfinding::ZLevelChange &change, + const auto can_wall_cling_to_change = [&here]( const Pathfinding::ZLevelChange & change, const bool we_go_up ) { const tripoint &anchor = we_go_up ? change.from : change.to; if( here.climb_difficulty( anchor ) > max_wall_climb_difficulty ) { From 421c89d10aa69ffcce94feb638611fce4dca13bc Mon Sep 17 00:00:00 2001 From: WishDuck Date: Thu, 26 Mar 2026 08:01:31 -0700 Subject: [PATCH 11/12] fix android build --- src/map.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/map.cpp b/src/map.cpp index e33783935c83..6ccb0261e3b1 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -2657,7 +2657,8 @@ int map::climb_difficulty( const tripoint &p ) const if( has_flag( TFLAG_NO_FLOOR, p ) ) { const auto below = p + tripoint_below; const auto below_range = points_in_radius( below, 1 ); - climb_points.insert( climb_points.end(), below_range.begin(), below_range.end() ); + const auto below_points = std::vector( below_range.begin(), below_range.end() ); + climb_points.insert( climb_points.end(), below_points.begin(), below_points.end() ); } for( const tripoint &pt : climb_points ) { From e3faf34b0d3ed2e30a5e94b6b61b6fc20dc07dd3 Mon Sep 17 00:00:00 2001 From: WishDuck Date: Fri, 27 Mar 2026 10:36:37 -0700 Subject: [PATCH 12/12] fix mac builds --- src/monmove.cpp | 2 +- src/pathfinding.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/monmove.cpp b/src/monmove.cpp index 8bd29b34ddcf..9b60201a38c1 100644 --- a/src/monmove.cpp +++ b/src/monmove.cpp @@ -128,7 +128,7 @@ auto wall_support_count( const map &here, const tripoint &anchor ) -> int const auto below = anchor + tripoint_below; const auto below_range = points_in_radius( below, 1 ); - neighbors.assign( below_range.begin(), below_range.end() ); + neighbors = std::vector( below_range.begin(), below_range.end() ); const auto below_supports = std::ranges::count_if( neighbors, [&below, &is_cardinal_support]( const tripoint & pt ) { diff --git a/src/pathfinding.cpp b/src/pathfinding.cpp index f7a31eac25ac..7668e5ac3652 100755 --- a/src/pathfinding.cpp +++ b/src/pathfinding.cpp @@ -88,7 +88,7 @@ auto has_cardinal_wall_support( const map &here, const tripoint &anchor ) -> boo const auto below = anchor + tripoint_below; const auto below_range = points_in_radius( below, 1 ); - neighbors.assign( below_range.begin(), below_range.end() ); + neighbors = std::vector( below_range.begin(), below_range.end() ); return std::ranges::any_of( neighbors, [&below, &is_cardinal_support]( const tripoint & pt ) { return is_cardinal_support( below, pt );