From 8c4263185e419725de24aba24b8100a6086717a4 Mon Sep 17 00:00:00 2001 From: Chaosvolt Date: Fri, 1 Mar 2024 19:21:21 -0600 Subject: [PATCH] feat(UI): move remaining deactivate and manhack functions to pet menus, sanity-check docile behavior, rework pheromone function into generic monster culling (#4247) * commit for remote * commit for remote * Final fixing up * Update json_flags.md * Idea per feedback * Update monexamine.cpp * Update game.cpp --- data/json/monsters/drones.json | 2 +- .../docs/en/mod/json/reference/json_flags.md | 1 + src/character.cpp | 2 +- src/game.cpp | 69 ----------------- src/game.h | 8 -- src/monattack.cpp | 8 +- src/monexamine.cpp | 77 ++++++++++++++----- src/monexamine.h | 1 + src/monmove.cpp | 16 ++-- src/monster.cpp | 2 +- src/monstergenerator.cpp | 1 + src/mtype.h | 1 + 12 files changed, 78 insertions(+), 110 deletions(-) diff --git a/data/json/monsters/drones.json b/data/json/monsters/drones.json index 12ae9730e277..24e03c870bce 100644 --- a/data/json/monsters/drones.json +++ b/data/json/monsters/drones.json @@ -15,7 +15,7 @@ "luminance": 5, "death_drops": { "subtype": "collection", "groups": [ [ "robots", 80 ] ], "//": "80% chance of an item from group robots" }, "death_function": [ "BROKEN_AMMO" ], - "flags": [ "SEES", "FLIES", "NOHEAD", "ELECTRONIC", "NO_BREATHE", "INTERIOR_AMMO", "BIOPROOF" ] + "flags": [ "SEES", "FLIES", "NOHEAD", "ELECTRONIC", "NO_BREATHE", "INTERIOR_AMMO", "BIOPROOF", "CAN_BE_ORDERED" ] }, { "id": "mon_EMP_hack", diff --git a/doc/src/content/docs/en/mod/json/reference/json_flags.md b/doc/src/content/docs/en/mod/json/reference/json_flags.md index 0e3eb5145866..d2a8cf6bf3aa 100644 --- a/doc/src/content/docs/en/mod/json/reference/json_flags.md +++ b/doc/src/content/docs/en/mod/json/reference/json_flags.md @@ -987,6 +987,7 @@ Multiple death functions can be used. Not all combinations make sense. - `BLEED` Causes the player to bleed. - `BONES` May produce bones and sinews when butchered. - `BORES` Tunnels through just about anything (15x bash multiplier: dark wyrms' bash skill 12->180) +- `CAN_BE_ORDERED` This creature can be directed to not attack enemies, if friendly. - `CAN_DIG` Can dig _and_ walk. - `CAN_OPEN_DOORS` Can open doors on its path. - `CANPLAY` This creature can be played with if it's a pet. diff --git a/src/character.cpp b/src/character.cpp index dd76d44b40f5..a4d91f6c6996 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -10961,12 +10961,12 @@ Attitude Character::attitude_to( const Creature &other ) const switch( m->attitude( const_cast( this ) ) ) { // player probably does not want to harm them, but doesn't care much at all. case MATT_FOLLOW: - case MATT_FPASSIVE: case MATT_IGNORE: case MATT_FLEE: return Attitude::A_NEUTRAL; // player does not want to harm those. case MATT_FRIEND: + case MATT_FPASSIVE: case MATT_ZLAVE: // Don't want to harm your zlave! return Attitude::A_FRIENDLY; diff --git a/src/game.cpp b/src/game.cpp index 9d3a174a84c3..496ff64a4a96 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -203,8 +203,6 @@ static constexpr int DANGEROUS_PROXIMITY = 5; static const activity_id ACT_OPERATION( "ACT_OPERATION" ); static const activity_id ACT_AUTODRIVE( "ACT_AUTODRIVE" ); -static const mtype_id mon_manhack( "mon_manhack" ); - static const skill_id skill_melee( "melee" ); static const skill_id skill_dodge( "dodge" ); static const skill_id skill_firstaid( "firstaid" ); @@ -223,7 +221,6 @@ static const efftype_id effect_assisted( "assisted" ); static const efftype_id effect_blind( "blind" ); static const efftype_id effect_bouldering( "bouldering" ); static const efftype_id effect_contacts( "contacts" ); -static const efftype_id effect_docile( "docile" ); static const efftype_id effect_downed( "downed" ); static const efftype_id effect_drunk( "drunk" ); static const efftype_id effect_evil( "evil" ); @@ -235,7 +232,6 @@ static const efftype_id effect_no_sight( "no_sight" ); static const efftype_id effect_npc_suspend( "npc_suspend" ); static const efftype_id effect_onfire( "onfire" ); static const efftype_id effect_pacified( "pacified" ); -static const efftype_id effect_paid( "paid" ); static const efftype_id effect_pet( "pet" ); static const efftype_id effect_ridden( "ridden" ); static const efftype_id effect_riding( "riding" ); @@ -5492,9 +5488,6 @@ static std::string get_fire_fuel_string( const tripoint &examp ) void game::examine( const tripoint &examp ) { - if( disable_robot( examp ) ) { - return; - } Creature *c = critter_at( examp ); if( c != nullptr ) { @@ -8678,68 +8671,6 @@ void game::set_safe_mode( safe_mode_type mode ) safe_mode_warning_logged = false; } -bool game::disable_robot( const tripoint &p ) -{ - monster *const mon_ptr = critter_at( p ); - if( !mon_ptr ) { - return false; - } - monster &critter = *mon_ptr; - if( critter.friendly == 0 || critter.has_effect( effect_pet ) || - critter.has_flag( MF_RIDEABLE_MECH ) || - ( critter.has_flag( MF_PAY_BOT ) && critter.has_effect( effect_paid ) ) ) { - // Can only disable / reprogram friendly monsters - return false; - } - const auto mid = critter.type->id; - const auto mon_item_id = critter.type->revert_to_itype; - if( !mon_item_id.is_empty() && - query_yn( _( "Deactivate the %s?" ), critter.name() ) ) { - - u.moves -= 100; - m.add_item_or_charges( p, critter.to_item() ); - if( !critter.has_flag( MF_INTERIOR_AMMO ) ) { - for( auto &ammodef : critter.ammo ) { - if( ammodef.second > 0 ) { - m.spawn_item( p.xy(), ammodef.first, 1, ammodef.second, calendar::turn ); - } - } - } - remove_zombie( critter ); - return true; - } - // Manhacks are special, they have their own menu here. - if( mid == mon_manhack ) { - int choice = UILIST_CANCEL; - if( critter.has_effect( effect_docile ) ) { - choice = uilist( _( "Reprogram the manhack?" ), { _( "Engage targets." ) } ); - } else { - choice = uilist( _( "Reprogram the manhack?" ), { _( "Follow me." ) } ); - } - switch( choice ) { - case 0: - if( critter.has_effect( effect_docile ) ) { - critter.remove_effect( effect_docile ); - if( one_in( 3 ) ) { - add_msg( _( "The %s hovers momentarily as it surveys the area." ), - critter.name() ); - } - } else { - critter.add_effect( effect_docile, 1_turns, num_bp ); - if( one_in( 3 ) ) { - add_msg( _( "The %s lets out a whirring noise and starts to follow you." ), - critter.name() ); - } - } - u.moves -= 100; - return true; - default: - break; - } - } - return false; -} - bool game::is_dangerous_tile( const tripoint &dest_loc ) const { return !( get_dangerous_tile( dest_loc ).empty() ); diff --git a/src/game.h b/src/game.h index 94b647d17029..e45873f8f4ff 100644 --- a/src/game.h +++ b/src/game.h @@ -881,14 +881,6 @@ class game void win_screen(); // Display our stats, "CONGRATULATIONS!" void draw_minimap(); // Draw the 5x5 minimap public: - /** - * If there is a robot (that can be disabled), query the player - * and try to disable it. - * @return true if the robot has been disabled or a similar action has - * been done. false if the player did not choose any action and the function - * has effectively done nothing. - */ - bool disable_robot( const tripoint &p ); // Draws the pixel minimap based on the player's current location void draw_pixel_minimap( const catacurses::window &w ); private: diff --git a/src/monattack.cpp b/src/monattack.cpp index f967e13943d9..2a676391c30e 100644 --- a/src/monattack.cpp +++ b/src/monattack.cpp @@ -3119,9 +3119,9 @@ bool mattack::nurse_operate( monster *z ) } bool mattack::check_money_left( monster *z ) { - if( !z->has_effect( effect_pet ) ) { + if( !z->has_effect( effect_paid ) ) { if( z->friendly == -1 && - z->has_effect( effect_paid ) ) { // if the pet effect runs out we're no longer friends + z->has_effect( effect_pet ) ) { // if the pet effect runs out we're no longer friends z->friendly = 0; if( !z->get_items().empty() ) { @@ -3135,11 +3135,11 @@ bool mattack::check_money_left( monster *z ) const SpeechBubble &speech_no_time = get_speech( "mon_grocerybot_friendship_done" ); sounds::sound( z->pos(), speech_no_time.volume, sounds::sound_t::electronic_speech, speech_no_time.text ); - z->remove_effect( effect_paid ); + z->remove_effect( effect_pet ); return true; } } else { - const time_duration time_left = z->get_effect_dur( effect_pet ); + const time_duration time_left = z->get_effect_dur( effect_paid ); if( time_left < 1_minutes ) { if( calendar::once_every( 20_seconds ) ) { const SpeechBubble &speech_time_low = get_speech( "mon_grocerybot_running_out_of_friendship" ); diff --git a/src/monexamine.cpp b/src/monexamine.cpp index 35be76ebf5b3..326ed2a5f86e 100644 --- a/src/monexamine.cpp +++ b/src/monexamine.cpp @@ -39,6 +39,7 @@ static const quality_id qual_shear( "SHEAR" ); +static const quality_id qual_butcher( "BUTCHER" ); static const efftype_id effect_sheared( "sheared" ); @@ -46,6 +47,7 @@ static const activity_id ACT_MILK( "ACT_MILK" ); static const activity_id ACT_PLAY_WITH_PET( "ACT_PLAY_WITH_PET" ); static const efftype_id effect_ai_waiting( "ai_waiting" ); +static const efftype_id effect_docile( "docile" ); static const efftype_id effect_harnessed( "harnessed" ); static const efftype_id effect_has_bag( "has_bag" ); static const efftype_id effect_monster_armor( "monster_armor" ); @@ -86,7 +88,7 @@ bool monexamine::pet_menu( monster &z ) leash, unleash, play_with_pet, - pheromone, + slaughter, milk, shear, pay, @@ -98,6 +100,7 @@ bool monexamine::pet_menu( monster &z ) remove_bat, insert_bat, check_bat, + change_orders, disable_pet, attack }; @@ -105,6 +108,7 @@ bool monexamine::pet_menu( monster &z ) uilist amenu; std::string pet_name = z.get_name(); bool is_zombie = z.type->in_species( ZOMBIE ); + bool can_slaughter = z.type->in_category( "WILDLIFE" ); const auto mon_item_id = z.type->revert_to_itype; avatar &you = get_avatar(); if( is_zombie ) { @@ -122,7 +126,6 @@ bool monexamine::pet_menu( monster &z ) } } amenu.addentry( rename, true, 'e', _( "Rename" ) ); - amenu.addentry( attack, true, 'A', _( "Attack" ) ); if( z.has_effect( effect_has_bag ) ) { amenu.addentry( give_items, true, 'g', _( "Place items into bag" ) ); amenu.addentry( remove_bag, true, 'b', _( "Remove bag from %s" ), pet_name ); @@ -165,10 +168,6 @@ bool monexamine::pet_menu( monster &z ) pet_name ); } } - if( is_zombie ) { - amenu.addentry( pheromone, true, 'z', _( "Tear out pheromone ball" ) ); - } - if( z.has_flag( MF_MILKABLE ) ) { amenu.addentry( milk, true, 'm', _( "Milk %s" ), pet_name ); } @@ -243,7 +242,14 @@ bool monexamine::pet_menu( monster &z ) amenu.addentry( insert_bat, false, 'x', _( "You need a %s to power this mech" ), type.nname( 1 ) ); } } - if( !mon_item_id.is_empty() && !z.has_flag( MF_RIDEABLE_MECH ) ) { + if( z.has_flag( MF_CAN_BE_ORDERED ) ) { + if( z.has_effect( effect_docile ) ) { + amenu.addentry( change_orders, true, 'O', _( "Order to engage targets" ), pet_name ); + } else { + amenu.addentry( change_orders, true, 'O', _( "Order to ignore enemies and follow" ), pet_name ); + } + } + if( !mon_item_id.is_empty() && !z.has_flag( MF_RIDEABLE_MECH ) && !z.has_flag( MF_PAY_BOT ) ) { if( z.has_effect( effect_has_bag ) || z.has_effect( effect_monster_armor ) || z.has_effect( effect_leashed ) || z.has_effect( effect_saddled ) ) { amenu.addentry( disable_pet, true, 'D', _( "Remove items and deactivate the %s" ), pet_name ); @@ -251,6 +257,11 @@ bool monexamine::pet_menu( monster &z ) amenu.addentry( disable_pet, true, 'D', _( "Deactivate the %s" ), pet_name ); } } + if( ( is_zombie || can_slaughter ) && you.has_quality( qual_butcher, 1 ) ) { + amenu.addentry( slaughter, true, 'A', _( "Slaughter %s" ), pet_name ); + } else { + amenu.addentry( attack, true, 'A', _( "Attack" ) ); + } amenu.query(); int choice = amenu.ret; @@ -294,8 +305,8 @@ bool monexamine::pet_menu( monster &z ) play_with( z ); } break; - case pheromone: - if( query_yn( _( "Really kill the zombie slave?" ) ) ) { + case slaughter: + if( query_yn( _( "Really kill the %s?" ), pet_name ) ) { kill_zslave( z ); } break; @@ -335,6 +346,9 @@ bool monexamine::pet_menu( monster &z ) break; case check_bat: break; + case change_orders: + toggle_ignore_targets( z ); + break; case disable_pet: if( query_yn( _( "Really deactivate your %s?" ), pet_name ) ) { deactivate_pet( z ); @@ -509,16 +523,29 @@ bool monexamine::mfriend_menu( monster &z ) enum choices { push_monster = 0, rename, + change_orders, + disable_pet, attack }; uilist amenu; const std::string pet_name = z.get_name(); + const auto mon_item_id = z.type->revert_to_itype; amenu.text = string_format( _( "What to do with your %s?" ), pet_name ); amenu.addentry( push_monster, true, 'p', _( "Push %s" ), pet_name ); amenu.addentry( rename, true, 'e', _( "Rename" ) ); + if( z.has_flag( MF_CAN_BE_ORDERED ) ) { + if( z.has_effect( effect_docile ) ) { + amenu.addentry( change_orders, true, 'O', _( "Order to engage targets" ), pet_name ); + } else { + amenu.addentry( change_orders, true, 'O', _( "Order to ignore enemies and follow" ), pet_name ); + } + } + if( !mon_item_id.is_empty() && !z.has_flag( MF_RIDEABLE_MECH ) && !z.has_flag( MF_PAY_BOT ) ) { + amenu.addentry( disable_pet, true, 'D', _( "Deactivate the %s" ), pet_name ); + } amenu.addentry( attack, true, 'a', _( "Attack" ) ); amenu.query(); @@ -531,6 +558,14 @@ bool monexamine::mfriend_menu( monster &z ) case rename: rename_pet( z ); break; + case change_orders: + toggle_ignore_targets( z ); + break; + case disable_pet: + if( query_yn( _( "Really deactivate your %s?" ), pet_name ) ) { + deactivate_pet( z ); + } + break; case attack: if( query_yn( _( "You may be attacked! Proceed?" ) ) ) { get_player_character().melee_attack( z, true ); @@ -791,16 +826,10 @@ void monexamine::play_with( monster &z ) void monexamine::kill_zslave( monster &z ) { avatar &you = get_avatar(); - z.apply_damage( &you, bodypart_id( "torso" ), 100 ); // damage the monster (and its corpse) - z.die( &you ); // and make sure it's really dead + you.add_msg_if_player( _( "With a clean cut you put your %s down." ), z.get_name() ); + z.die( &you ); // execute it cleanly without damaging the corpse you.moves -= 150; - - if( !one_in( 3 ) ) { - you.add_msg_if_player( _( "You tear out the pheromone ball from the zombie slave." ) ); - item *ball = item::spawn_temporary( "pheromone", calendar::start_of_cataclysm ); - iuse::pheromone( &you, ball, true, you.pos() ); - } } void monexamine::add_leash( monster &z ) @@ -891,6 +920,19 @@ void monexamine::start_leading( monster &z ) add_msg( _( "You take hold of the %s's leash to make it follow you." ), z.get_name() ); } +void monexamine::toggle_ignore_targets( monster &z ) +{ + if( z.has_effect( effect_docile ) ) { + z.remove_effect( effect_docile ); + add_msg( _( "You order the %s to engage targets." ), z.get_name() ); + return; + } else { + z.add_effect( effect_docile, 1_turns ); + add_msg( _( "You order the %s to focus on following you." ), z.get_name() ); + return; + } +} + void monexamine::stop_leading( monster &z ) { if( !z.has_effect( effect_led_by_leash ) ) { @@ -901,7 +943,6 @@ void monexamine::stop_leading( monster &z ) add_msg( _( "You release the %s's leash." ), z.get_name() ); } - void monexamine::deactivate_pet( monster &z ) { if( z.has_effect( effect_has_bag ) ) { diff --git a/src/monexamine.h b/src/monexamine.h index 1b923b2a7e5b..a449422d2a0c 100644 --- a/src/monexamine.h +++ b/src/monexamine.h @@ -28,6 +28,7 @@ void add_leash( monster &z ); void remove_leash( monster &z ); void start_leading( monster &z ); void stop_leading( monster &z ); +void toggle_ignore_targets( monster &z ); void tie_pet( monster &z ); void untie_pet( monster &z ); void shear_animal( monster &z ); diff --git a/src/monmove.cpp b/src/monmove.cpp index 5fe26ed3fd1d..e0d64eded8b4 100644 --- a/src/monmove.cpp +++ b/src/monmove.cpp @@ -397,14 +397,6 @@ void monster::plan() return; } - if( docile ) { - if( friendly != 0 && target != nullptr ) { - set_dest( target->pos() ); - } - - return; - } - int valid_targets = ( target == nullptr ) ? 1 : 0; for( npc &who : g->all_npcs() ) { auto faction_att = faction.obj().attitude( who.get_monster_faction() ); @@ -524,6 +516,14 @@ void monster::plan() } } + // Docile monsters should ignore targets, so place it after all possible ways target could be selected + if( docile ) { + if( target != nullptr ) { + // Stop fighting and focus on following the player + target = nullptr; + } + } + // Operating monster keep you safe while they operate, how nice.... if( type->has_special_attack( "OPERATE" ) ) { int prev_friendlyness = friendly; diff --git a/src/monster.cpp b/src/monster.cpp index 2874925d892f..71a5fa16076c 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -1258,8 +1258,8 @@ Attitude monster::attitude_to( const Creature &other ) const switch( attitude( const_cast( p ) ) ) { case MATT_FRIEND: case MATT_ZLAVE: - return Attitude::A_FRIENDLY; case MATT_FPASSIVE: + return Attitude::A_FRIENDLY; case MATT_FLEE: case MATT_IGNORE: case MATT_FOLLOW: diff --git a/src/monstergenerator.cpp b/src/monstergenerator.cpp index e9a0807a610c..181c1e1359a8 100644 --- a/src/monstergenerator.cpp +++ b/src/monstergenerator.cpp @@ -180,6 +180,7 @@ std::string enum_to_string( m_flag data ) case MF_STUN_IMMUNE: return "STUN_IMMUNE"; case MF_LOUDMOVES: return "LOUDMOVES"; case MF_DROPS_AMMO: return "DROPS_AMMO"; + case MF_CAN_BE_ORDERED: return "CAN_BE_ORDERED"; // *INDENT-ON* case m_flag::MF_MAX: break; diff --git a/src/mtype.h b/src/mtype.h index 79a4575e8e6d..2896a451d1f1 100644 --- a/src/mtype.h +++ b/src/mtype.h @@ -178,6 +178,7 @@ enum m_flag : int { MF_CAN_OPEN_DOORS, // This monster can open doors. MF_STUN_IMMUNE, // This monster is immune to the stun effect MF_DROPS_AMMO, // This monster drops ammo. Check to make sure starting_ammo paramter is present for this monster type! + MF_CAN_BE_ORDERED, // If friendly, allow setting this monster to ignore hostiles and prioritize following the player. MF_MAX // Sets the length of the flags - obviously must be LAST };