Skip to content

Commit

Permalink
MVP of grid furniture examine action (#1613)
Browse files Browse the repository at this point in the history
Implementation parts:

* Added new examine action `use_furn_fake_item` which allows players to activate crafting pseudo items from furniture.
* Added `use_furn_fake_item` for grid welder and grid soldering iron.
* Copied usages from soldering iron and welder to their fake counterparts.
* Rewritten and generalized vehicle welding usage hack to use furniture too.
* Little changes in tests.
* Added TODOs for further improvements.

Testings:
* One time usages tested by placing soldering iron, spawning nurse, getting bleeding and cauterizing.
* Repair activity changes tested using:
    - Repair items using spawned welding cart
    - Try to repair items using spawned welding cart without charges
    - Repair items using grid soldering iron and grid welder
    - Try to repair items using grid soldering iron without power in grid
    - Repair items using soldering iron and tailor kit in inventory
    - Repair items using soldering iron and tailor kit standing in nearby tile
* Watched variable changes during all manual testing using debugger.

Additional context:
See #1612
  • Loading branch information
AngelicosPhosphoros authored Jun 21, 2022
1 parent 8a9069d commit 3f8acfd
Show file tree
Hide file tree
Showing 8 changed files with 366 additions and 60 deletions.
3 changes: 3 additions & 0 deletions data/json/furniture_and_terrain/furniture-appliances.json
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,7 @@
"coverage": 10,
"required_str": -1,
"crafting_pseudo_item": "fake_gridwelder",
"examine_action": "use_furn_fake_item",
"flags": [ "TRANSPARENT", "MOUNTABLE", "EASY_DECONSTRUCT" ],
"deconstruct": {
"items": [ { "item": "welder", "count": 1 }, { "item": "cable", "charges": 5 }, { "item": "power_supply", "count": 1 } ]
Expand Down Expand Up @@ -832,6 +833,7 @@
"coverage": 10,
"required_str": -1,
"crafting_pseudo_item": "fake_gridwelder",
"examine_action": "use_furn_fake_item",
"flags": [ "TRANSPARENT", "MOUNTABLE", "EASY_DECONSTRUCT" ],
"deconstruct": {
"items": [ { "item": "weldrig", "count": 1 }, { "item": "cable", "charges": 5 }, { "item": "power_supply", "count": 1 } ]
Expand Down Expand Up @@ -1038,6 +1040,7 @@
"coverage": 10,
"required_str": -1,
"crafting_pseudo_item": "fake_gridsolderingiron",
"examine_action": "use_furn_fake_item",
"flags": [ "TRANSPARENT", "MOUNTABLE", "EASY_DECONSTRUCT" ],
"deconstruct": {
"items": [ { "item": "soldering_iron", "count": 1 }, { "item": "cable", "charges": 5 }, { "item": "power_supply", "count": 1 } ]
Expand Down
26 changes: 25 additions & 1 deletion data/json/items/fake.json
Original file line number Diff line number Diff line change
Expand Up @@ -194,15 +194,39 @@
"name": { "str": "grid welder" },
"sub": "welder",
"max_charges": 1000,
"charges_per_use": 5,
"use_action": [
{
"type": "repair_item",
"item_action_type": "repair_metal",
"materials": [ "iron", "steel", "hardsteel", "aluminum", "copper", "bronze", "silver", "gold", "platinum", "superalloy", "glass" ],
"skill": "fabrication",
"tool_quality": 10,
"cost_scaling": 0.1,
"move_cost": 500
}
],
"flags": [ "USES_GRID_POWER" ]
},
{
"id": "fake_gridsolderingiron",
"copy-from": "fake_item",
"type": "TOOL",
"name": { "str": "soldering iron" },
"name": { "str": "grid soldering iron" },
"sub": "soldering_iron",
"max_charges": 1000,
"charges_per_use": 1,
"use_action": [
{
"type": "repair_item",
"item_action_type": "repair_metal",
"materials": [ "plastic", "lead", "tin", "zinc" ],
"skill": "fabrication",
"cost_scaling": 0.1,
"move_cost": 1500
},
{ "flame": false, "type": "cauterize" }
],
"flags": [ "USES_GRID_POWER" ]
},
{
Expand Down
259 changes: 213 additions & 46 deletions src/activity_handlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
#include "creature.h"
#include "damage.h"
#include "debug.h"
// TODO (https://github.com/cataclysmbnteam/Cataclysm-BN/issues/1612):
// Remove that include after implementing repair_activity_actor.
#include "distribution_grid.h"
#include "enums.h"
#include "event.h"
#include "event_bus.h"
Expand Down Expand Up @@ -2564,6 +2567,10 @@ void activity_handlers::lockpicking_finish( player_activity *act, player *p )
act->set_to_null();
}


// TODO (https://github.com/cataclysmbnteam/Cataclysm-BN/issues/1612):
// Remove that repair code after repair_activity_actor.

enum repeat_type : int {
// REPEAT_INIT should be zero. In some scenarios (veh welder), activity value default to zero.
REPEAT_INIT = 0, // Haven't found repeat value yet.
Expand Down Expand Up @@ -2595,77 +2602,228 @@ static repeat_type repeat_menu( const std::string &title, repeat_type last_selec
return REPEAT_CANCEL;
}

// HACK: This is a part of a hack to provide pseudo items for long repair activity
// Note: similar hack could be used to implement all sorts of vehicle pseudo-items
// and possibly CBM pseudo-items too.
struct weldrig_hack {
vehicle *veh;
int part;
item pseudo;
namespace activity_handlers
{
namespace repair_activity_hack
{

weldrig_hack()
: veh( nullptr )
, part( -1 )
, pseudo( itype_welder, calendar::turn )
{ }
// Total idea is that:
// 1. Modify activity to make sure that repair action wouldn't search item in inventory.
// 2. Put coords of interesting vehicle part or furniture.
// 3. Before applying each stage of repair, search for possible fake item.
//
// This relies on fact that repairing with real tools
// never use `player_activity::coords`
// and use `player::activity::values` with only one item.

bool init( const player_activity &act ) {
if( act.coords.empty() || act.values.size() < 2 ) {
return false;
}
namespace
{
enum class hack_type_t : int {
vehicle_weldrig = 0,
furniture = 1
};

cata::optional<hack_type_t> get_hack_type( const player_activity &activity )
{
// Uses real tool
if( activity.values.size() < 2 ) {
return cata::nullopt;
}
assert( !activity.coords.empty() );
// Old save data, probably
if( activity.values.size() == 2 ) {
return hack_type_t::vehicle_weldrig;
}
return static_cast<hack_type_t>( activity.values[2] );
}

tripoint get_position( const player_activity &activity )
{
return activity.coords.at( 0 );
}

item get_fake_tool( hack_type_t hack_type, const player_activity &activity )
{
const tripoint position = get_position( activity );
const map &m = get_map();

item fake_item;

part = act.values[1];
veh = veh_pointer_or_null( get_map().veh_at( act.coords[0] ) );
if( veh == nullptr || veh->part_count() <= part ) {
part = -1;
return false;
switch( hack_type ) {
case hack_type_t::vehicle_weldrig: {
const optional_vpart_position pos = m.veh_at( position );
if( !pos ) {
debugmsg( "Failed to find vehicle while using it for repair at %s", position.to_string() );
return fake_item;
}
const vehicle &veh = pos->vehicle();

fake_item = item( itype_welder, calendar::turn, 0 );
fake_item.charges = veh.fuel_left( itype_battery );

break;
}
case hack_type_t::furniture: {
if( !m.has_furn( position ) ) {
debugmsg( "Failed to find furniture while using it for repair at %s", position.to_string() );
// Return nullitem in that case
return fake_item;
}
const furn_t &furniture = m.furn( position ).obj();
const itype *item_type = furniture.crafting_pseudo_item_type();
if( item_type == nullptr ) {
return fake_item;
}
if( !item_type->has_flag( "USES_GRID_POWER" ) ) {
debugmsg( "Non grid powered furniture for long repairs is not supported yet." );
return fake_item;
}

part = veh->part_with_feature( part, "WELDRIG", true );
return part >= 0;
}
const tripoint_abs_ms abspos( m.getabs( position ) );
const distribution_grid &grid = get_distribution_grid_tracker().grid_at( abspos );

item &get_item() {
if( veh != nullptr && part >= 0 ) {
pseudo.charges = veh->drain( itype_battery, 1000 - pseudo.charges );
return pseudo;
fake_item = item( item_type, calendar::turn, 0 );
fake_item.charges = grid.get_resource( true );
break;
}
}

fake_item.set_flag( "PSEUDO" );
return fake_item;
}

void discharge_real_power_source(
hack_type_t hack_type,
const tripoint &position,
item &tool,
const int original_charges
)
{
const int used_charges = original_charges - tool.charges;

// null item should be handled just fine
return null_item_reference();
if( used_charges <= 0 ) {
return;
}

void clean_up() {
// Return unused charges
if( veh == nullptr || part < 0 ) {
return;
const map &m = get_map();

int unfulfilled_demand = 0;
switch( hack_type ) {
case hack_type_t::vehicle_weldrig: {
optional_vpart_position pos = m.veh_at( position );
if( !pos ) {
return;
}
vehicle &veh = pos->vehicle();
unfulfilled_demand = veh.discharge_battery( used_charges );
break;
}
case hack_type_t::furniture: {
const tripoint_abs_ms abspos( m.getabs( position ) );
distribution_grid &grid = get_distribution_grid_tracker().grid_at( abspos );
unfulfilled_demand = grid.mod_resource( -used_charges );
break;
}
}
if( unfulfilled_demand != 0 ) {
debugmsg(
"Fake tool discharged grid/veh more than grid/veh had! Unfulfilled demand %d kJ",
unfulfilled_demand
);
}
}

} // namespace

veh->charge_battery( pseudo.charges );
pseudo.charges = 0;
void patch_activity_for_vehicle_welder(
player_activity &activity,
const tripoint &veh_part_position,
const vehicle &veh,
int interact_part_idx )
{
// Player may start another activity on welder/soldering iron
// Check it here instead of vehicle interaction code
// because we want to encapsulate hack here.
if( activity.id() != ACT_REPAIR_ITEM ) {
return;
}

~weldrig_hack() {
clean_up();
const int welding_rig_index = veh.part_with_feature( interact_part_idx, "WELDRIG", true );

// This tells activity, that real item doesn't exists in inventory.
activity.index = INT_MIN;
// Data for lookup vehicle part
activity.coords = { veh_part_position };
activity.values = {
// Because we called only on start of repair
static_cast<int>( repeat_type::REPEAT_INIT ),
welding_rig_index,
static_cast<int>( hack_type_t::vehicle_weldrig )
};
}

void patch_activity_for_furniture( player_activity &activity,
const tripoint &furniture_position )
{
// Player may start another activity on welder/soldering iron
// Check it here instead of furniture interaction code
// because we want to encapsulate hack here.
if( activity.id() != ACT_REPAIR_ITEM ) {
return;
}
};

// This tells activity, that real item doesn't exists in inventory.
activity.index = INT_MIN;
// Data for lookup furniture
activity.coords = { furniture_position };
activity.values = {
// Because we called only on start of repair
static_cast<int>( repeat_type::REPEAT_INIT ),
0, // Useless for us, set only to be compatible with vehicle
static_cast<int>( hack_type_t::furniture )
};
}

} // namespace repair_activity_hack
} // namespace activity_handlers

void activity_handlers::repair_item_finish( player_activity *act, player *p )
{
namespace hack = activity_handlers::repair_activity_hack;

const std::string iuse_name_string = act->get_str_value( 0, "repair_item" );
repeat_type repeat = static_cast<repeat_type>( act->get_value( 0, REPEAT_INIT ) );
weldrig_hack w_hack;
item_location *ploc = nullptr;

if( !act->targets.empty() ) {
ploc = &act->targets[0];
}

item &main_tool = !w_hack.init( *act ) ?
ploc ?
**ploc : p->i_at( act->index ) : w_hack.get_item();
// nullopt if used real tool
cata::optional<hack::hack_type_t> hack_type = hack::get_hack_type( *act );
cata::optional<item> fake_tool = cata::nullopt;
if( hack_type ) {
fake_tool = hack::get_fake_tool( hack_type.value(), *act );
}
const tripoint hack_position = hack_type ? hack::get_position( *act ) : tripoint{};
const int hack_original_charges = fake_tool ? fake_tool->charges : 0;

item *used_tool = main_tool.get_usable_item( iuse_name_string );
item *main_tool = nullptr;
if( hack_type.has_value() ) {
main_tool = &fake_tool.value();
}
if( main_tool == nullptr && ploc ) {
main_tool = & **ploc;
}
if( main_tool == nullptr ) {
main_tool = &p->i_at( act->index );
}
if( main_tool == nullptr ) {
debugmsg( "Failed to get main_tool for long repair" );
act->set_to_null();
return;
}

item *used_tool = main_tool->get_usable_item( iuse_name_string );
if( used_tool == nullptr ) {
debugmsg( "Lost tool used for long repair" );
act->set_to_null();
Expand Down Expand Up @@ -2695,6 +2853,14 @@ void activity_handlers::repair_item_finish( player_activity *act, player *p )
} else {
p->consume_charges( *used_tool, used_tool->ammo_required() );
}
if( hack_type.has_value() ) {
hack::discharge_real_power_source(
hack_type.value(),
hack_position,
*used_tool,
hack_original_charges
);
}
}

// TODO: Allow setting this in the actor
Expand Down Expand Up @@ -2728,6 +2894,7 @@ void activity_handlers::repair_item_finish( player_activity *act, player *p )
repeat = REPEAT_INIT;
}
}

// Check tool is valid before we query target and Repeat choice.
if( !actor->can_use_tool( *p, *used_tool, true ) ) {
act->set_to_null();
Expand All @@ -2736,7 +2903,7 @@ void activity_handlers::repair_item_finish( player_activity *act, player *p )

// target selection and validation.
while( act->targets.size() < 2 ) {
item_location item_loc = game_menus::inv::repair( *p, actor, &main_tool );
item_location item_loc = game_menus::inv::repair( *p, actor, main_tool );

if( item_loc == item_location::nowhere ) {
p->add_msg_if_player( m_info, _( "Never mind." ) );
Expand Down
Loading

0 comments on commit 3f8acfd

Please sign in to comment.