Skip to content

Commit

Permalink
Merge pull request #72347 from RenechCDDA/fashion_on_the_go
Browse files Browse the repository at this point in the history
Swappable player outfits
  • Loading branch information
kevingranade authored May 13, 2024
2 parents 41ee830 + 6eed95d commit 81ede3c
Show file tree
Hide file tree
Showing 15 changed files with 194 additions and 18 deletions.
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" ] ] ]
},
{
"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 @@ -1809,6 +1809,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

0 comments on commit 81ede3c

Please sign in to comment.