Skip to content

Commit

Permalink
Add ability to leash and lead pets
Browse files Browse the repository at this point in the history
- introduce leashed and led_by_leash effects
- add corresponding actions to the monexamine pet menu
- consume rope on leash, return it on unleash
- rework tie action to require leashing first
  • Loading branch information
eltank committed Aug 19, 2021
1 parent 0055339 commit 61c083e
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 23 deletions.
12 changes: 12 additions & 0 deletions data/json/effects.json
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,18 @@
"name": [ "Has Saddle" ],
"desc": [ "AI tag used for critters wearing a saddle. This is a bug if you have it." ]
},
{
"type": "effect_type",
"id": "leashed",
"name": [ "Has Leash" ],
"desc": [ "AI tag used for critters wearing a leash. This is a bug if you have it." ]
},
{
"type": "effect_type",
"id": "led_by_leash",
"name": [ "Being Led By Leash" ],
"desc": [ "AI tag used for critters forced to follow using a leash. This is a bug if you have it." ]
},
{
"type": "effect_type",
"id": "tied",
Expand Down
120 changes: 97 additions & 23 deletions src/monexamine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ 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_monster_saddled( "monster_saddled" );
static const efftype_id effect_leashed( "leashed" );
static const efftype_id effect_led_by_leash( "led_by_leash" );
static const efftype_id effect_tied( "tied" );

static const itype_id itype_cash_card( "cash_card" );
Expand Down Expand Up @@ -340,9 +342,9 @@ void play_with( monster &z )
player_character.assign_activity( player_activity( play_with_pet_activity_actor( pet_name ) ) );
}

void tie_pet( monster &z )
void add_leash( monster &z )
{
if( z.has_effect( effect_tied ) ) {
if( z.has_effect( effect_leashed ) ) {
return;
}
Character &player_character = get_player_character();
Expand All @@ -354,7 +356,7 @@ void tie_pet( monster &z )
}
int i = 0;
uilist selection_menu;
selection_menu.text = string_format( _( "Select an item to tie your %s with." ), z.get_name() );
selection_menu.text = string_format( _( "Select an item to leash your %s with." ), z.get_name() );
selection_menu.addentry( i++, true, MENU_AUTOASSIGN, _( "Cancel" ) );
for( const item *iter : rope_inv ) {
selection_menu.addentry( i++, true, MENU_AUTOASSIGN, _( "Use %s" ), iter->tname() );
Expand All @@ -369,6 +371,28 @@ void tie_pet( monster &z )
item *rope_item = rope_inv[index - 1];
z.tied_item = cata::make_value<item>( *rope_item );
player_character.i_rem( rope_item );
z.add_effect( effect_leashed, 1_turns, true );
add_msg( _( "You add a leash to your %s." ), z.get_name() );
}

void remove_leash( monster &z )
{
if( !z.has_effect( effect_leashed ) ) {
return;
}
z.remove_effect( effect_leashed );
if( z.tied_item ) {
get_player_character().i_add( *z.tied_item );
z.tied_item.reset();
}
add_msg( _( "You remove the leash from your %s." ), z.get_name() );
}

void tie_pet( monster &z )
{
if( z.has_effect( effect_tied ) ) {
return;
}
z.add_effect( effect_tied, 1_turns, true );
add_msg( _( "You tie your %s." ), z.get_name() );
}
Expand All @@ -379,11 +403,33 @@ void untie_pet( monster &z )
return;
}
z.remove_effect( effect_tied );
if( !z.has_effect( effect_leashed ) ) {
// migration code dealing with animals tied before leashing was introduced
z.add_effect( effect_leashed, 1_turns, true );
}
add_msg( _( "You untie your %s." ), z.get_name() );
if( z.tied_item ) {
player_character.i_add( *z.tied_item );
z.tied_item.reset();
}

void start_leading( monster &z )
{
if( z.has_effect( effect_led_by_leash ) ) {
return;
}
if( z.has_effect( effect_tied ) ) {
untie_pet( z );
}
z.add_effect( effect_led_by_leash, 1_turns, true );
add_msg( _( "You take hold of the %s's leash to make it follow you." ), z.get_name() );
}

void stop_leading( monster &z )
{
if( !z.has_effect( effect_led_by_leash ) ) {
return;
}
z.remove_effect( effect_led_by_leash );
// The pet may or may not stop following so don't print that here
add_msg( _( "You release the %s's leash." ), z.get_name() );
}

/*
Expand Down Expand Up @@ -498,15 +544,19 @@ bool monexamine::pet_menu( monster &z )
{
enum choices {
swap_pos = 0,
push_monster,
lead,
stop_lead,
rename,
attach_bag,
remove_bag,
drop_all,
push_monster,
give_items,
mon_armor_add,
mon_harness_remove,
mon_armor_remove,
leash,
unleash,
play_with_pet,
milk,
shear,
Expand All @@ -533,6 +583,13 @@ bool monexamine::pet_menu( monster &z )

amenu.addentry( swap_pos, true, 's', _( "Swap positions" ) );
amenu.addentry( push_monster, true, 'p', _( "Push %s" ), pet_name );
if( z.has_effect( effect_leashed ) ) {
if( z.has_effect( effect_led_by_leash ) ) {
amenu.addentry( stop_lead, true, 'p', _( "Stop leading %s" ), pet_name );
} else {
amenu.addentry( lead, true, 'p', _( "Lead %s by the leash" ), pet_name );
}
}
amenu.addentry( rename, true, 'e', _( "Rename" ) );
amenu.addentry( attack, true, 'A', _( "Attack" ) );
Character &player_character = get_player_character();
Expand All @@ -553,24 +610,29 @@ bool monexamine::pet_menu( monster &z )
} else if( !z.has_flag( MF_RIDEABLE_MECH ) ) {
amenu.addentry( mon_armor_add, true, 'a', _( "Equip %s with armor" ), pet_name );
}
if( z.has_flag( MF_BIRDFOOD ) || z.has_flag( MF_CATFOOD ) || z.has_flag( MF_DOGFOOD ) ||
z.has_flag( MF_CANPLAY ) ) {
amenu.addentry( play_with_pet, true, 'y', _( "Play with %s" ), pet_name );
}
if( z.has_effect( effect_tied ) ) {
amenu.addentry( rope, true, 't', _( "Untie" ) );
} else if( !z.has_flag( MF_RIDEABLE_MECH ) ) {
amenu.addentry( untie, true, 't', _( "Untie" ) );
}
if( z.has_effect( effect_leashed ) && !z.has_effect( effect_tied ) ) {
amenu.addentry( tie, true, 't', _( "Tie" ) );
amenu.addentry( unleash, true, 't', _( "Remove leash from %s" ), pet_name );
}
if( !z.has_effect( effect_leashed ) && !z.has_flag( MF_RIDEABLE_MECH ) ) {
std::vector<item *> rope_inv = player_character.items_with( []( const item & itm ) {
return itm.has_flag( json_flag_TIE_UP );
} );
if( !rope_inv.empty() ) {
amenu.addentry( rope, true, 't', _( "Tie" ) );
amenu.addentry( leash, true, 't', _( "Attach leash to %s" ), pet_name );
} else {
amenu.addentry( rope, false, 't', _( "You need any type of rope to tie %s in place" ),
amenu.addentry( leash, false, 't', _( "You need any type of rope to leash %s" ),
pet_name );
}
}

if( z.has_flag( MF_BIRDFOOD ) || z.has_flag( MF_CATFOOD ) || z.has_flag( MF_DOGFOOD ) ||
z.has_flag( MF_CANPLAY ) ) {
amenu.addentry( play_with_pet, true, 'y', _( "Play with %s" ), pet_name );
}
if( z.has_flag( MF_MILKABLE ) ) {
amenu.addentry( milk, true, 'm', _( "Milk %s" ), pet_name );
}
Expand All @@ -593,15 +655,15 @@ bool monexamine::pet_menu( monster &z )
}
}
if( z.has_flag( MF_PET_MOUNTABLE ) && !z.has_effect( effect_monster_saddled ) &&
player_character.has_item_with_flag( json_flag_TACK ) &&
player_character.get_skill_level( skill_survival ) >= 1 ) {
amenu.addentry( attach_saddle, true, 'h', _( "Tack up %s" ), pet_name );
} else if( z.has_flag( MF_PET_MOUNTABLE ) && z.has_effect( effect_monster_saddled ) ) {
player_character.has_item_with_flag( json_flag_TACK ) ) {
if( player_character.get_skill_level( skill_survival ) >= 1 ) {
amenu.addentry( attach_saddle, true, 'h', _( "Tack up %s" ), pet_name );
} else {
amenu.addentry( attach_saddle, false, 'h', _( "You don't know how to saddle %s" ), pet_name );
}
}
if( z.has_flag( MF_PET_MOUNTABLE ) && z.has_effect( effect_monster_saddled ) ) {
amenu.addentry( remove_saddle, true, 'h', _( "Remove tack from %s" ), pet_name );
} else if( z.has_flag( MF_PET_MOUNTABLE ) && !z.has_effect( effect_monster_saddled ) &&
player_character.has_item_with_flag( json_flag_TACK ) &&
player_character.get_skill_level( skill_survival ) < 1 ) {
amenu.addentry( remove_saddle, false, 'h', _( "You don't know how to saddle %s" ), pet_name );
}
if( z.has_flag( MF_PAY_BOT ) ) {
amenu.addentry( pay, true, 'f', _( "Manage your friendship with %s" ), pet_name );
Expand Down Expand Up @@ -657,6 +719,12 @@ bool monexamine::pet_menu( monster &z )
case push_monster:
push( z );
break;
case lead:
start_leading( z );
break;
case stop_lead:
stop_leading( z );
break;
case rename:
rename_pet( z );
break;
Expand Down Expand Up @@ -684,6 +752,12 @@ bool monexamine::pet_menu( monster &z )
play_with( z );
}
break;
case leash:
add_leash( z );
break;
case unleash:
remove_leash( z );
break;
case tie:
tie_pet( z );
break;
Expand Down
15 changes: 15 additions & 0 deletions src/monmove.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ static const efftype_id effect_operating( "operating" );
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 itype_id itype_pressurized_tank( "pressurized_tank" );

Expand Down Expand Up @@ -634,6 +635,13 @@ void monster::plan()
} else if( friendly > 0 && one_in( 3 ) ) {
// Grow restless with no targets
friendly--;
} else if( friendly != 0 && has_effect( effect_led_by_leash ) ) {
// visibility doesn't matter, we're getting pulled by a leash
if( rl_dist( pos(), player_character.pos() ) > 1 ) {
set_dest( player_character.pos() );
} else {
unset_dest();
}
} else if( friendly < 0 && sees( player_character ) && !has_flag( MF_PET_WONT_FOLLOW ) ) {
if( rl_dist( pos(), player_character.pos() ) > 2 ) {
set_dest( player_character.pos() );
Expand Down Expand Up @@ -1103,6 +1111,13 @@ void monster::move()
stumble();
path.clear();
}
if( has_effect( effect_led_by_leash ) ) {
if( rl_dist( pos(), player_character.pos() ) > 2 ) {
// Either failed to keep up with the player or moved away
remove_effect( effect_led_by_leash );
add_msg( m_info, _( "You lose hold of a leash." ) );
}
}
}

Character *monster::find_dragged_foe()
Expand Down

0 comments on commit 61c083e

Please sign in to comment.