Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swappable player outfits #72347

Merged
merged 5 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions data/json/item_actions.json
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,11 @@
"id": "MEASURE_RESONANCE",
"name": { "str": "Measure artifact resonance" }
},
{
"type": "item_action",
"id": "CHANGE_OUTFIT",
"name": { "str": "Swap outfits" }
},
{
"type": "item_action",
"id": "PLAY_GAME",
Expand Down
27 changes: 27 additions & 0 deletions data/json/items/containers/generic.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,33 @@
}
]
},
{
"id": "outfit_storage",
"type": "TOOL",
"category": "container",
"name": { "str": "set of clothes", "str_pl": "sets of clothes" },
"description": "A bundle of presses, coat hangars, and other miscellaneous items needed to store and keep a good change of clothes. You can interact with it to swap to the clothes it's currently containing, or disassemble it to get the clothes back.",
"weight": "1 kg",
"volume": "1 L",
"longest_side": "150 cm",
"price": 0,
"price_postapoc": 0,
"to_hit": -3,
"use_action": [ "CHANGE_OUTFIT" ],
"material": [ "plastic" ],
"pocket_data": [
{
"pocket_type": "CONTAINER",
"max_contains_volume": "100 L",
"max_contains_weight": "100 kg",
"rigid": false,
"moves": 1000
}
],
"symbol": "#",
"color": "black",
"flags": [ "SINGLE_USE", "NO_SALVAGE", "NO_RELOAD" ]
},
{
"id": "box_paper_small",
"type": "GENERIC",
Expand Down
8 changes: 8 additions & 0 deletions data/json/player_activities.json
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,14 @@
"rooted": true,
"based_on": "speed"
},
{
"id": "ACT_OUTFIT_SWAP",
"type": "activity_type",
"activity_level": "LIGHT_EXERCISE",
"verb": "changing clothes",
"rooted": true,
"based_on": "speed"
},
{
"id": "ACT_WASH",
"type": "activity_type",
Expand Down
13 changes: 13 additions & 0 deletions data/json/recipes/other/other.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,19 @@
"tools": [ [ [ "drawing_tool", 5, "LIST" ] ] ],
"components": [ [ [ "cardboard", 10 ] ] ]
},
{
"result": "outfit_storage",
"type": "recipe",
"activity_level": "NO_EXERCISE",
"category": "CC_OTHER",
"subcategory": "CSC_OTHER_TOOLS",
"skill_used": "fabrication",
"time": "1 s",
"autolearn": true,
"reversible": true,
"//": "Tying equipment or other fasteners. Maybe one day we will have coat hangars.",
"components": [ [ [ "cordage", 10, "LIST" ] ] ]
RenechCDDA marked this conversation as resolved.
Show resolved Hide resolved
},
{
"result": "tic_tac",
"type": "recipe",
Expand Down
81 changes: 81 additions & 0 deletions src/activity_actor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5568,6 +5568,87 @@ std::unique_ptr<activity_actor> reel_cable_activity_actor::deserialize( JsonValu
return actor.clone();
}

void outfit_swap_actor::start( player_activity &act, Character &who )
{
item fake_storage( outfit_item->typeId() );
for( const item_location &worn_item : who.get_visible_worn_items() ) {
act.moves_total += who.item_handling_cost( *worn_item ); // Cost of taking it off
act.moves_total += who.item_store_cost( *worn_item, fake_storage ); // And putting it away
}
for( const item *clothing : outfit_item->all_items_top() ) {
auto ret = who.can_wear( *clothing );
// Note this is checking if we can put something new on, but it might conflict with our current clothing, causing a
// spurious failure. Maybe we should strip the player first?
if( ret.success() ) {
act.moves_total += who.item_wear_cost( *clothing );
} else {
act.moves_total += who.item_retrieve_cost( *clothing, *outfit_item );
// Dropping takes no time? So I guess that's all we need
}
}
act.moves_left = act.moves_total;
}

void outfit_swap_actor::finish( player_activity &act, Character &who )
{
map &here = get_map();
// First, make a new outfit and shove all our existing clothes into it.
item new_outfit( outfit_item->typeId() );
item_location ground = here.add_item_ret_loc( who.pos(), new_outfit, true );
if( !ground ) {
debugmsg( "Failed to swap outfits during outfit_swap_actor::finish" );
act.set_to_null();
return;
}
// Taken-off items are put in this temporary list, then naturally deleted from the world when the function returns.
std::list<item> it_list;
for( item_location &worn_item : who.get_visible_worn_items() ) {
item outfit_component( *worn_item );
if( who.takeoff( worn_item, &it_list ) ) {
ground->force_insert_item( outfit_component, pocket_type::CONTAINER );
}
}

// Now we have to take clothes out of the one we activated
for( item *component : outfit_item->all_items_top() ) {
auto ret = who.can_wear( *component );
if( ret.success() ) {
item_location new_clothes( who, component );
who.wear( new_clothes );
} else {
// For some reason we couldn't wear this item. Maybe the player mutated in the meanwhile, but
// drop the item instead of deleting it.
here.add_item( who.pos(), *component );
}
}

who.i_rem( outfit_item.get_item() );

// Now we just did a whole bunch of wearing and taking off at once, but we had already paid that movecost by doing the activity
// So we reset our moves
who.set_moves( 0 );
// TODO: Granularize this and allow resumable swapping if you were interrupted during the activity

act.set_to_null();
}

void outfit_swap_actor::serialize( JsonOut &jsout ) const
{
jsout.start_object();
jsout.member( "outfit_item", outfit_item );
jsout.end_object();
}

std::unique_ptr<activity_actor> outfit_swap_actor::deserialize( JsonValue &jsin )
{
outfit_swap_actor actor( {} );

JsonObject data = jsin.get_object();

data.read( "outfit_item", actor.outfit_item );
return actor.clone();
}

void meditate_activity_actor::start( player_activity &act, Character & )
{
act.moves_total = to_moves<int>( 20_minutes );
Expand Down
26 changes: 26 additions & 0 deletions src/activity_actor_definitions.h
Original file line number Diff line number Diff line change
Expand Up @@ -1413,6 +1413,32 @@ class oxytorch_activity_actor : public activity_actor
}
};

class outfit_swap_actor : public activity_actor
{
public:
explicit outfit_swap_actor( const item_location &outfit_item ) : outfit_item( outfit_item ) {};
activity_id get_type() const override {
return activity_id( "ACT_OUTFIT_SWAP" );
}

void start( player_activity &act, Character &who ) override;
void do_turn( player_activity &, Character & ) override {}
void finish( player_activity &act, Character &who ) override;

std::unique_ptr<activity_actor> clone() const override {
return std::make_unique<outfit_swap_actor>( *this );
}

void serialize( JsonOut & ) const override;
static std::unique_ptr<activity_actor> deserialize( JsonValue & );
private:
item_location outfit_item;

bool can_resume_with_internal( const activity_actor &, const Character & ) const override {
return false;
}
};

class meditate_activity_actor : public activity_actor
{
public:
Expand Down
12 changes: 6 additions & 6 deletions src/character.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6260,10 +6260,10 @@ std::string Character::extended_description() const
ss += "\n";
ss += _( "Wearing:" ) + std::string( " " );

const std::list<item> visible_worn_items = get_visible_worn_items();
const std::list<item_location> visible_worn_items = get_visible_worn_items();
std::string worn_string = enumerate_as_string( visible_worn_items.begin(), visible_worn_items.end(),
[]( const item & it ) {
return it.tname();
[]( const item_location & it ) {
return it.get_item()->tname();
} );
ss += !worn_string.empty() ? worn_string : _( "Nothing" );

Expand Down Expand Up @@ -10751,11 +10751,11 @@ std::vector<std::string> Character::short_description_parts() const
if( is_armed() ) {
result.push_back( _( "Wielding: " ) + weapon.tname() );
}
const std::list<item> visible_worn_items = get_visible_worn_items();
const std::list<item_location> visible_worn_items = get_visible_worn_items();
const std::string worn_str = enumerate_as_string( visible_worn_items.begin(),
visible_worn_items.end(),
[]( const item & it ) {
return it.tname();
[]( const item_location & it ) {
return it.get_item()->tname();
} );
if( !worn_str.empty() ) {
result.push_back( _( "Wearing: " ) + worn_str );
Expand Down
2 changes: 1 addition & 1 deletion src/character.h
Original file line number Diff line number Diff line change
Expand Up @@ -3418,7 +3418,7 @@ class Character : public Creature, public visitable
bool is_worn_item_visible( std::list<item>::const_iterator ) const;

/** Returns all worn items visible to an outside observer */
std::list<item> get_visible_worn_items() const;
std::list<item_location> get_visible_worn_items() const;

/** Swap side on which item is worn; returns false on fail. If interactive is false, don't alert player or drain moves */
bool change_side( item &it, bool interactive = true );
Expand Down
13 changes: 7 additions & 6 deletions src/character_attire.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -686,9 +686,9 @@ bool Character::is_worn_item_visible( std::list<item>::const_iterator worn_item
return worn.is_worn_item_visible( worn_item, worn_item_body_parts );
}

std::list<item> Character::get_visible_worn_items() const
std::list<item_location> Character::get_visible_worn_items() const
{
return worn.get_visible_worn_items( *this );
return const_cast<outfit &>( worn ).get_visible_worn_items( *this );
}

double Character::armwear_factor() const
Expand Down Expand Up @@ -1072,12 +1072,13 @@ item *outfit::item_worn_with_id( const itype_id &i )
return it_with_id;
}

std::list<item> outfit::get_visible_worn_items( const Character &guy ) const
std::list<item_location> outfit::get_visible_worn_items( const Character &guy )
{
std::list<item> result;
for( auto i = worn.cbegin(), end = worn.cend(); i != end; ++i ) {
std::list<item_location> result;
for( auto i = worn.begin(), end = worn.end(); i != end; ++i ) {
if( guy.is_worn_item_visible( i ) ) {
result.push_back( *i );
item_location loc_here( const_cast<Character &>( guy ), &*i );
result.emplace_back( loc_here );
}
}
return result;
Expand Down
2 changes: 1 addition & 1 deletion src/character_attire.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ class outfit
*/
void item_encumb( std::map<bodypart_id, encumbrance_data> &vals, const item &new_item,
const Character &guy ) const;
std::list<item> get_visible_worn_items( const Character &guy ) const;
std::list<item_location> get_visible_worn_items( const Character &guy );
int swim_modifier( int swim_skill ) const;
bool natural_attack_restricted_on( const bodypart_id &bp ) const;
bool natural_attack_restricted_on( const sub_bodypart_id &bp ) const;
Expand Down
2 changes: 1 addition & 1 deletion src/item.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1805,7 +1805,7 @@ ret_val<void> item::put_in( const item &payload, pocket_type pk_type,
void item::force_insert_item( const item &it, pocket_type pk_type )
{
contents.force_insert_item( it, pk_type );
update_inherited_flags();
on_contents_changed();
}

void item::set_var( const std::string &name, const int value )
Expand Down
1 change: 1 addition & 0 deletions src/item_factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1805,6 +1805,7 @@ void Item_factory::init()
add_iuse( "MACE", &iuse::mace );
add_iuse( "MAGIC_8_BALL", &iuse::magic_8_ball );
add_iuse( "MEASURE_RESONANCE", &iuse::measure_resonance );
add_iuse( "CHANGE_OUTFIT", &iuse::change_outfit );
add_iuse( "PLAY_GAME", &iuse::play_game );
add_iuse( "MAKEMOUND", &iuse::makemound );
add_iuse( "DIG_CHANNEL", &iuse::dig_channel );
Expand Down
13 changes: 13 additions & 0 deletions src/iuse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8786,6 +8786,19 @@ std::optional<int> iuse::measure_resonance( Character *p, item *it, const tripoi
return 0;
}

std::optional<int> iuse::change_outfit( Character *p, item *it, const tripoint & )
{
if( !p->is_avatar() ) {
debugmsg( "NPC %s tried to swap outfit", p->get_name() );
return std::nullopt;
}

p->assign_activity( outfit_swap_actor( item_location{*p, it} ) );

// Deleting the item we activated is handled in outfit_swap_actor::finish
return std::nullopt;
}

std::optional<int> iuse::electricstorage( Character *p, item *it, const tripoint & )
{
// From item processing
Expand Down
1 change: 1 addition & 0 deletions src/iuse.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ std::optional<int> lumber( Character *, item *, const tripoint & );
std::optional<int> ma_manual( Character *, item *, const tripoint & );
std::optional<int> magic_8_ball( Character *, item *, const tripoint & );
std::optional<int> measure_resonance( Character *, item *, const tripoint & );
std::optional<int> change_outfit( Character *, item *, const tripoint & );
std::optional<int> electricstorage( Character *, item *, const tripoint & );
std::optional<int> ebooksave( Character *, item *, const tripoint & );
std::optional<int> ebookread( Character *, item *, const tripoint & );
Expand Down
6 changes: 3 additions & 3 deletions src/npc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2612,10 +2612,10 @@ int npc::print_info( const catacurses::window &w, int line, int vLines, int colu
}

// Worn gear list on following lines.
const std::list<item> visible_worn_items = get_visible_worn_items();
const std::list<item_location> visible_worn_items = get_visible_worn_items();
const std::string worn_str = enumerate_as_string( visible_worn_items.begin(),
visible_worn_items.end(), []( const item & it ) {
return it.tname();
visible_worn_items.end(), []( const item_location & it ) {
return it.get_item()->tname();
} );
if( !worn_str.empty() ) {
std::vector<std::string> worn_lines = foldstring( _( "Wearing: " ) + worn_str, iWidth );
Expand Down
Loading