diff --git a/data/json/bionics.json b/data/json/bionics.json index 6103a74050fbe..2b30534fe74f1 100644 --- a/data/json/bionics.json +++ b/data/json/bionics.json @@ -137,7 +137,7 @@ "type": "bionic", "name": "Cable Charger System", "capacity": 10, - "description": "You have a complex port surgically mounted above your hip. While active, it will recharge bionic power when connected to a battery via jumper cable.", + "description": "You have a complex port surgically mounted above your hip. While active, it will recharge bionic power when connected to a power source via jumper cable.", "occupied_bodyparts": [ [ "TORSO", 10 ] ], "flags": [ "BIONIC_POWER_SOURCE", "BIONIC_SHOCKPROOF", "BIONIC_TOGGLED" ] }, diff --git a/data/json/items/tools.json b/data/json/items/tools.json index d46d20778dd53..df8ac2e474944 100644 --- a/data/json/items/tools.json +++ b/data/json/items/tools.json @@ -108,7 +108,7 @@ "type": "TOOL", "name": "UPS", "name_plural": "UPS's", - "description": "This is a unified power supply, or UPS. It is a device developed jointly by military and scientific interests for use in combat and the field. The UPS is designed to power armor and some guns, but drains batteries quickly.", + "description": "This is a unified power supply, or UPS. It is a device developed jointly by military and scientific interests for use in combat and the field. The UPS is designed to power bionics, armor and some guns, but drains batteries quickly.", "weight": "680 g", "volume": "2500 ml", "price": 280000, @@ -121,7 +121,8 @@ "magazines": [ [ "battery", [ "heavy_plus_battery_cell", "heavy_battery_cell", "heavy_atomic_battery_cell", "heavy_disposable_cell" ] ] ], - "magazine_well": 4 + "magazine_well": 4, + "flags": [ "IS_UPS" ] }, { "id": "acidbomb", @@ -155,7 +156,8 @@ "symbol": ";", "color": "light_green", "ammo": "plutonium", - "max_charges": 2500 + "max_charges": 2500, + "flags": [ "IS_UPS" ] }, { "id": "advanced_ecig", diff --git a/data/json/items/vehicle/cables.json b/data/json/items/vehicle/cables.json index 37e45fbea3fa2..dfc1f6a836a33 100644 --- a/data/json/items/vehicle/cables.json +++ b/data/json/items/vehicle/cables.json @@ -3,7 +3,7 @@ "type": "TOOL", "id": "jumper_cable", "name": "jumper cable", - "description": "A jumper cable, like you've seen many times before: it's a short multi-stranded copper cable with power leads on either end, whose purpose is to share power between vehicles.", + "description": "A jumper cable, like you've seen many times before: it's a short multi-stranded copper cable with power leads on either end, whose main purpose is to share power between vehicles, but can also link other electrical systems.", "to_hit": 1, "color": "light_blue", "symbol": "&", @@ -22,7 +22,7 @@ "type": "TOOL", "id": "jumper_cable_heavy", "name": "heavy-duty cable", - "description": "A long, thick, heavy-duty cable with power leads on either end. It looks like you could use it to hook up two vehicles to each other, though you expect the power loss would be noticeable.", + "description": "A long, thick, heavy-duty cable with power leads on either end. It looks like you could use it to hook up two vehicles to each other, though you expect the power loss would be noticeable. Can also link other electrical systems.", "volume": "1500 ml", "weight": "750 g", "max_charges": 20, diff --git a/doc/JSON_FLAGS.md b/doc/JSON_FLAGS.md index 7a45899bc7146..90d20d1ee9728 100644 --- a/doc/JSON_FLAGS.md +++ b/doc/JSON_FLAGS.md @@ -1191,6 +1191,7 @@ Melee flags are fully compatible with tool flags, and vice versa. - ```FISH_GOOD``` When used for fishing, it's a good tool (requires that the matching use_action has been set). - ```FISH_POOR``` When used for fishing, it's a poor tool (requires that the matching use_action has been set). - ```HAS_RECIPE``` Used by the E-Ink tablet to indicates it's currently showing a recipe. +- ```IS_UPS``` Item is Unified Power Supply. Used in active item processing - ```LIGHT_[X]``` Illuminates the area with light intensity `[X]` where `[X]` is an intensity value. (e.x. `LIGHT_4` or `LIGHT_100`). - ```MC_MOBILE```, ```MC_RANDOM_STUFF```, ```MC_SCIENCE_STUFF```, ```MC_USED```, ```MC_HAS_DATA``` Memory card related flags, see `iuse.cpp` - ```NO_DROP``` Item should never exist on map tile as a discrete item (must be contained by another item) diff --git a/src/bionics.cpp b/src/bionics.cpp index 96b539eb7514f..68e76cdeaa0bd 100644 --- a/src/bionics.cpp +++ b/src/bionics.cpp @@ -681,28 +681,49 @@ bool player::activate_bionic( int b, bool eff_only ) reactor_plut = 0; } } else if( bio.id == "bio_cable" ) { - bool has_cable = has_item_with( []( const item & it ) { - return it.active && it.has_flag( "CABLE_SPOOL" ); - } ); - bool has_connected_cable = has_item_with( []( const item & it ) { - return it.active && it.has_flag( "CABLE_SPOOL" ) && it.get_var( "state" ) == "solar_pack_link"; + std::vector cables = items_with( []( const item & it ) { + return it.has_flag( "CABLE_SPOOL" ); } ); - + bool has_cable = !cables.empty(); + bool free_cable = false; if( !has_cable ) { add_msg_if_player( m_info, - _( "You need a jumper cable connected to a vehicle to drain power from it." ) ); - } - if( is_wearing( "solarpack_on" ) || is_wearing( "q_solarpack_on" ) ) { - if( has_connected_cable ) { - add_msg_if_player( m_info, _( "Your plugged-in solar pack is now able to charge" - " your system." ) ); - } else { - add_msg_if_player( m_info, _( "You need to connect the cable to yourself and the solar pack" - " before your solar pack can charge your system." ) ); + _( "You need a jumper cable connected to a power source to drain power from it." ) ); + } else { + for( item *cable : cables ) { + const std::string state = cable->get_var( "state" ); + if( state == "cable_charger" ) { + add_msg_if_player( m_info, + _( "Cable is plugged-in to the CBM but it has to be also connected to the power source." ) ); + } + if( state == "cable_charger_link" ) { + add_msg_if_player( m_info, + _( "You are plugged to the vehicle. It will charge you if it has some juice in it." ) ); + } + if( state == "solar_pack_link" ) { + add_msg_if_player( m_info, + _( "You are plugged to a solar pack. It will charge you if it's unfolded and in sunlight." ) ); + } + if( state == "UPS_link" ) { + add_msg_if_player( m_info, + _( "You are plugged to a UPS. It will charge you if it has some juice in it." ) ); + } + if( state == "solar_pack" || state == "UPS" ) { + add_msg_if_player( m_info, + _( "You have a cable plugged to a portable power source, but you need to plug it in to the CBM." ) ); + } + if( state == "pay_oyt_cable" ) { + add_msg_if_player( m_info, + _( "You have a cable plugged to a vehicle, but you need to plug it in to the CBM." ) ); + } + if( state == "attach_first" ) { + free_cable = true; + } } - } else if( is_wearing( "solarpack" ) || is_wearing( "q_solarpack" ) ) { - add_msg_if_player( m_info, _( "You might plug in your solar pack to the cable charging" - " system, if you unfold it." ) ); + } + if( free_cable ) { + add_msg_if_player( m_info, + _( "You have at least one free cable in your inventory that you could use to plug yourself in." ) ); } } @@ -984,6 +1005,30 @@ void player::process_bionic( int b ) for( const item *cable : cables ) { const cata::optional target = cable->get_cable_target( this, pos() ); if( !target ) { + if( g->m.is_outside( pos() ) && !is_night( calendar::turn ) && + cable->get_var( "state" ) == "solar_pack_link" ) { + double modifier = g->natural_light_level( pos().z ) / default_daylight_level(); + // basic solar panel produces 50W = 1 charge/20_seconds = 180 charges/hour(3600) + if( is_wearing( "solarpack_on" ) && x_in_y( 180 * modifier, 3600 ) ) { + charge_power( 1 ); + } + // quantum solar backpack = solar panel x6 + if( is_wearing( "q_solarpack_on" ) && x_in_y( 6 * 180 * modifier, 3600 ) ) { + charge_power( 1 ); + } + } + if( cable->get_var( "state" ) == "UPS_link" ) { + static const item_filter used_ups = [&]( const item & itm ) { + return itm.get_var( "cable" ) == "plugged_in"; + }; + if( has_charges( "UPS_off", 1, used_ups ) ) { + use_charges( "UPS_off", 1, used_ups ); + charge_power( 1 ); + } else if( has_charges( "adv_UPS_off", 1, used_ups ) ) { + use_charges( "adv_UPS_off", roll_remainder( 0.6 ), used_ups ); + charge_power( 1 ); + } + } continue; } const optional_vpart_position vp = g->m.veh_at( *target ); diff --git a/src/item.cpp b/src/item.cpp index fed4d25cffdc5..ffd6425b98e66 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -3522,6 +3522,8 @@ std::string item::tname( unsigned int quantity, bool with_prefix, unsigned int t } if( active && ( has_flag( "WATER_EXTINGUISH" ) || has_flag( "LITCIG" ) ) ) { ret << _( " (lit)" ); + } else if( has_flag( "IS_UPS" ) && get_var( "cable" ) == "plugged_in" ) { + ret << _( " (plugged in)" ); } else if( active && !is_food() && !is_corpse() && ( typeId().length() < 3 || typeId().compare( typeId().length() - 3, 3, "_on" ) != 0 ) ) { // Usually the items whose ids end in "_on" have the "active" or "on" string already contained @@ -8200,18 +8202,46 @@ cata::optional item::get_cable_target( player *p, const tripoint &pos return g->m.getlocal( source ); } -bool item::process_cable( player *p, const tripoint &pos ) +bool item::process_cable( player *carrier, const tripoint &pos ) { - const cata::optional source = get_cable_target( p, pos ); + if( carrier == nullptr ) { + reset_cable( carrier ); + return false; + } + std::string state = get_var( "state" ); + if( state == "solar_pack_link" || state == "solar_pack" ) { + if( !carrier->has_item( *this ) || ( !carrier->is_wearing( "solarpack_on" ) || + !carrier->is_wearing( "q_solarpack_on" ) ) ) { + carrier->add_msg_if_player( m_bad, _( "You notice the cable has come loose!" ) ); + reset_cable( carrier ); + return false; + } + } + + static const item_filter used_ups = [&]( const item & itm ) { + return itm.get_var( "cable" ) == "plugged_in"; + }; + + if( state == "UPS" ) { + if( !carrier->has_item( *this ) || !carrier->has_item_with( used_ups ) ) { + carrier->add_msg_if_player( m_bad, _( "You notice the cable has come loose!" ) ); + for( item *used : carrier->items_with( used_ups ) ) { + used->erase_var( "cable" ); + } + reset_cable( carrier ); + return false; + } + } + const cata::optional source = get_cable_target( carrier, pos ); if( !source ) { return false; } if( !g->m.veh_at( *source ) || ( source->z != g->get_levz() && !g->m.has_zlevels() ) ) { - if( p != nullptr && p->has_item( *this ) ) { - p->add_msg_if_player( m_bad, _( "You notice the cable has come loose!" ) ); + if( carrier != nullptr && carrier->has_item( *this ) ) { + carrier->add_msg_if_player( m_bad, _( "You notice the cable has come loose!" ) ); } - reset_cable( p ); + reset_cable( carrier ); return false; } @@ -8220,10 +8250,10 @@ bool item::process_cable( player *p, const tripoint &pos ) charges = max_charges - distance; if( charges < 1 ) { - if( p != nullptr && p->has_item( *this ) ) { - p->add_msg_if_player( m_bad, _( "The over-extended cable breaks loose!" ) ); + if( carrier != nullptr && carrier->has_item( *this ) ) { + carrier->add_msg_if_player( m_bad, _( "The over-extended cable breaks loose!" ) ); } - reset_cable( p ); + reset_cable( carrier ); } return false; @@ -8246,6 +8276,24 @@ void item::reset_cable( player *p ) } } +bool item::process_UPS( player *carrier, const tripoint & /*pos*/ ) +{ + if( carrier == nullptr ) { + erase_var( "cable" ); + active = false; + return false; + } + bool has_connected_cable = carrier->has_item_with( []( const item & it ) { + return it.active && it.has_flag( "CABLE_SPOOL" ) && ( it.get_var( "state" ) == "UPS_link" || + it.get_var( "state" ) == "UPS" ); + } ); + if( !has_connected_cable ) { + erase_var( "cable" ); + active = false; + } + return false; +} + bool item::process_wet( player * /*carrier*/, const tripoint & /*pos*/ ) { if( item_counter == 0 ) { @@ -8414,6 +8462,10 @@ bool item::process( player *carrier, const tripoint &pos, bool activate, // DO NOT process this as a tool! It really isn't! return process_cable( carrier, pos ); } + if( has_flag( "IS_UPS" ) ) { + // DO NOT process this as a tool! It really isn't! + return process_UPS( carrier, pos ); + } if( is_tool() ) { return process_tool( carrier, pos ); } diff --git a/src/item.h b/src/item.h index 0d16d13b5b7e5..a0dd38c2d28e0 100644 --- a/src/item.h +++ b/src/item.h @@ -2021,7 +2021,8 @@ class item : public visitable // Place conditions that should remove fake smoke item in this sub-function bool process_fake_smoke( player *carrier, const tripoint &pos ); bool process_fake_mill( player *carrier, const tripoint &pos ); - bool process_cable( player *p, const tripoint &pos ); + bool process_cable( player *carrier, const tripoint &pos ); + bool process_UPS( player *carrier, const tripoint &pos ); bool process_blackpowder_fouling( player *carrier ); bool process_tool( player *carrier, const tripoint &pos ); diff --git a/src/iuse.cpp b/src/iuse.cpp index 87ad66223925c..3cb419b5833f2 100644 --- a/src/iuse.cpp +++ b/src/iuse.cpp @@ -8565,6 +8565,7 @@ int iuse::cable_attach( player *p, item *it, bool, const tripoint & ) const bool has_solar_pack = p->is_wearing( "solarpack" ) || p->is_wearing( "q_solarpack" ); const bool has_solar_pack_on = p->is_wearing( "solarpack_on" ) || p->is_wearing( "q_solarpack_on" ); const bool wearing_solar_pack = has_solar_pack || has_solar_pack_on; + const bool has_ups = p->has_charges( "UPS_off", 1 ) || p->has_charges( "adv_UPS_off", 1 ); const auto set_cable_active = []( player * p, item * it, const std::string & state ) { it->set_var( "state", state ); @@ -8581,6 +8582,9 @@ int iuse::cable_attach( player *p, item *it, bool, const tripoint & ) if( wearing_solar_pack ) { kmenu.addentry( 2, has_solar_pack_on, -1, _( "Attach cable to solar pack" ) ); } + if( has_ups ) { + kmenu.addentry( 3, true, -1, _( "Attach cable to UPS" ) ); + } kmenu.query(); int choice = kmenu.ret; @@ -8588,9 +8592,25 @@ int iuse::cable_attach( player *p, item *it, bool, const tripoint & ) return 0; // we did nothing. } else if( choice == 1 ) { set_cable_active( p, it, "cable_charger" ); + p->add_msg_if_player( m_info, _( "You attach the cable to your Cable Charger System." ) ); return 0; } else if( choice == 2 ) { set_cable_active( p, it, "solar_pack" ); + p->add_msg_if_player( m_info, _( "You attach the cable to the solar pack." ) ); + return 0; + } else if( choice == 3 ) { + int pos = g->inv_for_filter( _( "Choose UPS:" ), [&]( const item & itm ) { + return itm.has_flag( "IS_UPS" ); + }, _( "You don't have any UPS." ) ); + if( pos == INT_MIN ) { + add_msg( _( "Never mind" ) ); + return 0; + } + item &chosen = p->i_at( pos ); + chosen.set_var( "cable", "plugged_in" ); + chosen.activate(); + set_cable_active( p, it, "UPS" ); + p->add_msg_if_player( m_info, _( "You attach the cable to the UPS." ) ); return 0; } // fall through for attaching to a vehicle @@ -8632,16 +8652,20 @@ int iuse::cable_attach( player *p, item *it, bool, const tripoint & ) const bool paying_out = initial_state == "pay_out_cable"; const bool cable_cbm = initial_state == "cable_charger"; const bool solar_pack = initial_state == "solar_pack"; - bool loose_ends = paying_out || cable_cbm || solar_pack; + const bool UPS = initial_state == "UPS"; + bool loose_ends = paying_out || cable_cbm || solar_pack || UPS; uilist kmenu; kmenu.text = _( "Using cable:" ); - kmenu.addentry( 0, paying_out || cable_cbm, -1, _( "Attach loose end of the cable" ) ); - kmenu.addentry( 1, true, -1, _( "Detach and re-spool the cable" ) ); + kmenu.addentry( 0, true, -1, _( "Detach and re-spool the cable" ) ); if( has_bio_cable && loose_ends ) { - kmenu.addentry( 2, !cable_cbm, -1, _( "Attach cable to self" ) ); - // can't attach solar backpacks to cars - if( wearing_solar_pack && cable_cbm ) { - kmenu.addentry( 3, has_solar_pack_on, -1, _( "Attach cable to solar pack" ) ); + kmenu.addentry( 1, ( paying_out || cable_cbm ) && !solar_pack && + !UPS, -1, _( "Attach loose end to vehicle" ) ); + kmenu.addentry( 2, !cable_cbm, -1, _( "Attach loose end to self" ) ); + if( wearing_solar_pack ) { + kmenu.addentry( 3, !solar_pack && !paying_out && !UPS, -1, _( "Attach loose end to solar pack" ) ); + } + if( has_ups ) { + kmenu.addentry( 4, !UPS && !solar_pack && !paying_out, -1, _( "Attach loose end to UPS" ) ); } } kmenu.query(); @@ -8649,26 +8673,52 @@ int iuse::cable_attach( player *p, item *it, bool, const tripoint & ) if( choice < 0 ) { return 0; // we did nothing. - } else if( choice == 1 ) { + } else if( choice == 0 ) { // unconnect & respool it->reset_cable( p ); return 0; - } else if( choice == 2 ) { - // connecting self to backpack or car + } else if( choice == 2 ) { // connect self while other end already connected + p->add_msg_if_player( m_info, _( "You attach the cable to the Cable Charger System." ) ); + // connecting self, solar backpack connected if( solar_pack ) { set_cable_active( p, it, "solar_pack_link" ); + p->add_msg_if_player( m_good, _( "You are now plugged to the solar backpack." ) ); return 0; } + // connecting self, UPS connected + if( UPS ) { + set_cable_active( p, it, "UPS_link" ); + p->add_msg_if_player( m_good, _( "You are now plugged to the UPS." ) ); + return 0; + } + // connecting self, vehicle connected const optional_vpart_position source_vp = confirm_source_vehicle( p, it, true ); if( veh_pointer_or_null( source_vp ) != nullptr ) { set_cable_active( p, it, "cable_charger_link" ); + p->add_msg_if_player( m_good, _( "You are now plugged to the vehicle." ) ); } return 0; } else if( choice == 3 ) { - // connecting self to backpack + // connecting self to solar backpack set_cable_active( p, it, "solar_pack_link" ); + p->add_msg_if_player( m_good, _( "You are now plugged to the solar backpack." ) ); + return 0; + } else if( choice == 4 ) { + // connecting self to UPS + int pos = g->inv_for_filter( _( "Choose UPS:" ), [&]( const item & itm ) { + return itm.has_flag( "IS_UPS" ); + }, _( "You don't have any UPS." ) ); + if( pos == INT_MIN ) { + add_msg( _( "Never mind" ) ); + return 0; + } + item &chosen = p->i_at( pos ); + chosen.set_var( "cable", "plugged_in" ); + chosen.activate(); + set_cable_active( p, it, "UPS_link" ); + p->add_msg_if_player( m_good, _( "You are now plugged to the UPS." ) ); return 0; } - + // connecting self to vehicle const optional_vpart_position source_vp = confirm_source_vehicle( p, it, paying_out ); vehicle *const source_veh = veh_pointer_or_null( source_vp ); if( source_veh == nullptr && paying_out ) { @@ -8691,6 +8741,7 @@ int iuse::cable_attach( player *p, item *it, bool, const tripoint & ) it->set_var( "source_y", abspos.y ); it->set_var( "source_z", g->get_levz() ); set_cable_active( p, it, "cable_charger_link" ); + p->add_msg_if_player( m_good, _( "You are now plugged to the vehicle." ) ); return 0; } else { vehicle *const target_veh = &target_vp->vehicle(); diff --git a/src/player.cpp b/src/player.cpp index 94cfe599a94c6..6369fb77e30ab 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -4176,14 +4176,6 @@ void player::update_needs( int rate_multiplier ) if( has_bionic( bn_bio_solar ) ) { charge_power( rate_multiplier * 25 ); } - if( has_active_bionic( bionic_id( "bio_cable" ) ) ) { - if( is_wearing( "solarpack_on" ) ) { - charge_power( rate_multiplier * 25 ); - } - if( is_wearing( "q_solarpack_on" ) ) { - charge_power( rate_multiplier * 50 ); - } - } } // Huge folks take penalties for cramming themselves in vehicles