From 921d26c273bb2199c4a8649e2d9f408f940376eb Mon Sep 17 00:00:00 2001 From: Coolthulhu Date: Sun, 17 Jan 2021 15:19:13 +0100 Subject: [PATCH 01/12] WIP version of vehicle<->grid connector --- .../furniture-appliances.json | 22 +++ src/active_tile_data.cpp | 137 ++++++++++++++++-- src/active_tile_data.h | 22 +++ src/item.cpp | 2 +- src/map.cpp | 2 +- 5 files changed, 174 insertions(+), 11 deletions(-) diff --git a/data/json/furniture_and_terrain/furniture-appliances.json b/data/json/furniture_and_terrain/furniture-appliances.json index 4e8568aaed08..29cc014e1a5f 100644 --- a/data/json/furniture_and_terrain/furniture-appliances.json +++ b/data/json/furniture_and_terrain/furniture-appliances.json @@ -679,5 +679,27 @@ { "item": "small_storage_battery", "count": [ 5, 10 ] } ] } + }, + { + "type": "furniture", + "id": "f_cable_connector", + "looks_like": "f_utility_shelf", + "name": "jumper cable connector", + "description": "Power outlet connecting a building's electrical grid to a vehicle. To use, connect a jumper cable to a vehicle, then place the other end on the connector.", + "symbol": "*", + "color": "blue_white", + "move_cost_mod": -1, + "coverage": 10, + "required_str": -1, + "max_volume": "5000 ml", + "active": [ "vehicle_connector", { } ], + "deconstruct": { "items": [ { "item": "power_supply" } ] }, + "bash": { + "str_min": 16, + "str_max": 40, + "sound": "metal screeching!", + "sound_fail": "clang!", + "items": [ { "item": "power_supply", "prob": 50 } ] + } } ] diff --git a/src/active_tile_data.cpp b/src/active_tile_data.cpp index 1b91c6940f26..4864fbd608e1 100644 --- a/src/active_tile_data.cpp +++ b/src/active_tile_data.cpp @@ -7,11 +7,14 @@ #include "json.h" #include "map.h" #include "mapbuffer.h" +#include "vehicle.h" +#include "vpart_range.h" #include "weather.h" // TODO: Shouldn't use #include "submap.h" +static const std::string flag_CABLE_SPOOL( "CABLE_SPOOL" ); static const std::string flag_RECHARGE( "RECHARGE" ); static const std::string flag_USE_UPS( "USE_UPS" ); @@ -215,20 +218,136 @@ void charger_tile::load( JsonObject &jo ) jo.read( "power", power ); } +void vehicle_connector_tile::update_internal( time_point, const tripoint &pos, distribution_grid & ) +{ + this->pos = pos; +} + +active_tile_data *vehicle_connector_tile::clone() const +{ + return new vehicle_connector_tile( *this ); +} + +const std::string &vehicle_connector_tile::get_type() const +{ + static const std::string type( "vehicle_connector" ); + return type; +} + +void vehicle_connector_tile::store( JsonOut &jsout ) const +{ + ( void )jsout; +} + +void vehicle_connector_tile::load( JsonObject &jo ) +{ + ( void )jo; +} + +static vehicle *get_vehicle( const item &it ) +{ + if( !it.has_flag( flag_CABLE_SPOOL ) ) { + return nullptr; + } + // TODO: Common functions instead of copypaste of stringly typed code + const std::string &state = it.get_var( "state" ); + if( state != "pay_out_cable" && state != "cable_charger_link" ) { + return nullptr; + } + // In absolute coords + int source_x = it.get_var( "source_x", 0 ); + int source_y = it.get_var( "source_y", 0 ); + int source_z = it.get_var( "source_z", 0 ); + tripoint abs_ms( source_x, source_y, source_z ); + tripoint abs_ms_minus_local( source_x - ( source_x % SEEX ), + source_y - ( source_y % SEEY ), + source_z ); + // TODO: Cache, finding this is expensive + tripoint sm_coord = ms_to_sm_copy( abs_ms ); + submap *sm = MAPBUFFER.lookup_submap( sm_coord ); + for( const std::unique_ptr &veh : sm->vehicles ) { + for( auto iter : veh->get_all_parts() ) { + point mount_point = iter.part().precalc[0]; + point part_pos_local = veh->pos + mount_point; + tripoint global_pos = abs_ms_minus_local + part_pos_local; + if( global_pos == abs_ms ) { + return veh.get(); + } + } + } + + return nullptr; +} + +int vehicle_connector_tile::get_resource() const +{ + int resource_sum = 0; + // TODO: Avoid using submaps here + tripoint sm_coord = ms_to_sm_copy( this->pos ); + submap *sm = MAPBUFFER.lookup_submap( sm_coord ); + // This duplicates what @ref map does, that's not good + point local_offset = point( this->pos.x % SEEX, this->pos.y % SEEY ); + + for( const item &it : sm->get_items( local_offset ) ) { + vehicle *veh = get_vehicle( it ); + if( veh != nullptr ) { + // TODO: Handle cabled up vehicles without including any of them more than once + resource_sum += veh->fuel_left( "battery", false ); + } + } + + return resource_sum; +} +// TODO: Extract common function with the above +int vehicle_connector_tile::mod_resource( int amt ) +{ + // TODO: Avoid using submaps here + tripoint sm_coord = ms_to_sm_copy( this->pos ); + submap *sm = MAPBUFFER.lookup_submap( sm_coord ); + // This duplicates what @ref map does, that's not good + point local_offset = point( this->pos.x % SEEX, this->pos.y % SEEY ); + + for( const item &it : sm->get_items( local_offset ) ) { + vehicle *veh = get_vehicle( it ); + if( veh != nullptr ) { + // TODO: Handle cabled up vehicles without including any of them more than once + if( amt > 0 ) { + amt = veh->charge_battery( amt, false ); + } else { + amt = -veh->discharge_battery( -amt, false ); + } + if( amt == 0 ) { + return 0; + } + } + } + + return amt; +} + +static std::map> build_type_map() +{ + std::map> type_map; + const auto add_type = [&type_map]( active_tile_data * arg ) { + type_map[arg->get_type()].reset( arg ); + }; + add_type( new solar_tile() ); + add_type( new battery_tile() ); + add_type( new charger_tile() ); + add_type( new vehicle_connector_tile() ); + return type_map; +} + active_tile_data *active_tile_data::create( const std::string &id ) { - active_tile_data *new_tile; - if( id == "solar" ) { - new_tile = new solar_tile(); - } else if( id == "battery" ) { - new_tile = new battery_tile(); - } else if( id == "charger" ) { - new_tile = new charger_tile(); - } else { + static const auto type_map = build_type_map(); + const auto iter = type_map.find( id ); + if( iter == type_map.end() ) { debugmsg( "Invalid active_tile_data id %s", id.c_str() ); - new_tile = new null_tile_data(); + return new null_tile_data(); } + active_tile_data *new_tile = iter->second->clone(); new_tile->last_updated = calendar::start_of_cataclysm; return new_tile; } diff --git a/src/active_tile_data.h b/src/active_tile_data.h index a4d3fb7e21e9..6de53f1c3df4 100644 --- a/src/active_tile_data.h +++ b/src/active_tile_data.h @@ -4,6 +4,7 @@ #include #include "calendar.h" +#include "point.h" class JsonObject; class JsonOut; @@ -20,6 +21,11 @@ class active_tile_data time_point last_updated; protected: + /** + * @param to the time to update to + * @param p absolute map coordinates (@ref map::getabs) of the tile being updated + * @param grid distribution grid being updated, containing the tile being updated + */ virtual void update_internal( time_point to, const tripoint &p, distribution_grid &grid ) = 0; public: @@ -94,4 +100,20 @@ class charger_tile : public active_tile_data void load( JsonObject &jo ) override; }; +class vehicle_connector_tile : public active_tile_data +{ + private: + // VERY ugly hack, don't merge!!!!!!!!111 + tripoint pos; + public: + void update_internal( time_point to, const tripoint &p, distribution_grid &grid ) override; + active_tile_data *clone() const override; + const std::string &get_type() const override; + void store( JsonOut &jsout ) const override; + void load( JsonObject &jo ) override; + + int get_resource() const override; + int mod_resource( int amt ) override; +}; + #endif // CATA_SRC_ACTIVE_TILE_DATA_H diff --git a/src/item.cpp b/src/item.cpp index 0f13e9b55337..07956ab90cda 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -8816,7 +8816,7 @@ cata::optional item::get_cable_target( Character *p, const tripoint &p bool item::process_cable( player *carrier, const tripoint &pos ) { if( carrier == nullptr ) { - reset_cable( carrier ); + //reset_cable( carrier ); return false; } std::string state = get_var( "state" ); diff --git a/src/map.cpp b/src/map.cpp index 136cb4504709..0e9f5a858bca 100755 --- a/src/map.cpp +++ b/src/map.cpp @@ -4774,7 +4774,7 @@ static void use_charges_from_furn( const furn_t &f, const itype_id &type, int &q if( itt != nullptr && itt->item_tags.count( flag_USES_GRID_POWER ) > 0 ) { item furn_item( itt, -1, m->distribution_grid_at( p ).get_resource() ); if( filter( furn_item ) ) { - quantity = m->distribution_grid_at( p ).mod_resource( -quantity ); + quantity = -m->distribution_grid_at( p ).mod_resource( -quantity ); } } else if( itt != nullptr && itt->tool && !itt->tool->ammo_id.empty() ) { const itype_id ammo = ammotype( *itt->tool->ammo_id.begin() )->default_ammotype(); From a8ba429451436d83711bbfbf7eba7c88213575eb Mon Sep 17 00:00:00 2001 From: Coolthulhu Date: Tue, 19 Jan 2021 19:15:49 +0100 Subject: [PATCH 02/12] Simpler vehicle finding --- src/active_tile_data.cpp | 19 +------------------ src/vehicle.h | 2 ++ 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/src/active_tile_data.cpp b/src/active_tile_data.cpp index 4864fbd608e1..246685d5f9d3 100644 --- a/src/active_tile_data.cpp +++ b/src/active_tile_data.cpp @@ -259,24 +259,7 @@ static vehicle *get_vehicle( const item &it ) int source_y = it.get_var( "source_y", 0 ); int source_z = it.get_var( "source_z", 0 ); tripoint abs_ms( source_x, source_y, source_z ); - tripoint abs_ms_minus_local( source_x - ( source_x % SEEX ), - source_y - ( source_y % SEEY ), - source_z ); - // TODO: Cache, finding this is expensive - tripoint sm_coord = ms_to_sm_copy( abs_ms ); - submap *sm = MAPBUFFER.lookup_submap( sm_coord ); - for( const std::unique_ptr &veh : sm->vehicles ) { - for( auto iter : veh->get_all_parts() ) { - point mount_point = iter.part().precalc[0]; - point part_pos_local = veh->pos + mount_point; - tripoint global_pos = abs_ms_minus_local + part_pos_local; - if( global_pos == abs_ms ) { - return veh.get(); - } - } - } - - return nullptr; + return vehicle::find_vehicle( abs_ms ); } int vehicle_connector_tile::get_resource() const diff --git a/src/vehicle.h b/src/vehicle.h index 25756f7235ef..0ae18371f3a9 100644 --- a/src/vehicle.h +++ b/src/vehicle.h @@ -716,6 +716,7 @@ class vehicle /** empty the contents of a tank, battery or turret spilling liquids randomly on the ground */ void leak_fuel( vehicle_part &pt ); + public: /** * Find a possibly off-map vehicle. If necessary, loads up its submap through * the global MAPBUFFER and pulls it from there. For this reason, you should only @@ -724,6 +725,7 @@ class vehicle */ static vehicle *find_vehicle( const tripoint &where ); + private: /** * Traverses the graph of connected vehicles, starting from start_veh, and continuing * along all vehicles connected by some kind of POWER_TRANSFER part. From 72c85493657788f769edbf1edd20e098c4c82f90 Mon Sep 17 00:00:00 2001 From: Coolthulhu Date: Wed, 20 Jan 2021 17:47:12 +0100 Subject: [PATCH 03/12] Remove power loss from jumper cables --- data/json/items/vehicle/cables.json | 4 +-- data/json/vehicleparts/vehicle_parts.json | 6 ---- src/active_tile_data.cpp | 6 ++-- src/vehicle.cpp | 37 +++++++++-------------- src/vehicle.h | 2 +- 5 files changed, 21 insertions(+), 34 deletions(-) diff --git a/data/json/items/vehicle/cables.json b/data/json/items/vehicle/cables.json index 064a534cf442..a885b1ddf498 100644 --- a/data/json/items/vehicle/cables.json +++ b/data/json/items/vehicle/cables.json @@ -43,7 +43,7 @@ "type": "TOOL", "id": "jumper_cable_heavy", "name": { "str": "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. Can also link other electrical systems.", + "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. Can also link other electrical systems.", "volume": "1500 ml", "weight": "750 g", "max_charges": 20, @@ -54,7 +54,7 @@ "type": "TOOL", "id": "jumper_cable_debug", "name": { "str": "shiny cable" }, - "description": "This is the cable of the gods: 50 meters long, no power loss, light as a feather and fits in a matchbook. You're sure this wasn't supposed to exist, and the way it shimmers makes you uneasy.", + "description": "This is the cable of the gods: 50 meters long, light as a feather and fits in a matchbook. You're sure this wasn't supposed to exist, and the way it shimmers makes you uneasy.", "weight": "1 g", "volume": "0 ml", "max_charges": 50, diff --git a/data/json/vehicleparts/vehicle_parts.json b/data/json/vehicleparts/vehicle_parts.json index c52010402a62..a32f2bb4bc67 100644 --- a/data/json/vehicleparts/vehicle_parts.json +++ b/data/json/vehicleparts/vehicle_parts.json @@ -2915,8 +2915,6 @@ "broken_symbol": "*", "broken_color": "dark_gray", "damage_modifier": 10, - "epower": 1, - "//": "Epower for POWER_TRANSFER stuff is how much percentage-wise loss there is in transmission", "durability": 120, "description": "Thick copper cable with leads on either end. Attach one end to one vehicle and the other to another, and you can transfer electrical power between the two.", "item": "jumper_cable", @@ -2935,8 +2933,6 @@ "damage_modifier": 10, "durability": 120, "description": "Very thick copper cable with leads on either end. Attach one end to one vehicle and the other to another, and you can transfer electrical power between the two.", - "epower": 5, - "//": "Epower for POWER_TRANSFER stuff is how much percentage-wise loss there is in transmission", "item": "jumper_cable_heavy", "requirements": { "removal": { "time": "5 s" } }, "flags": [ "NOINSTALL", "UNMOUNT_ON_DAMAGE", "UNMOUNT_ON_MOVE", "POWER_TRANSFER" ], @@ -2966,8 +2962,6 @@ "color": "yellow", "broken_symbol": "*", "broken_color": "dark_gray", - "epower": 0, - "//": "Epower for POWER_TRANSFER stuff is how much percentage-wise loss there is in transmission", "damage_modifier": 10, "durability": 120, "item": "jumper_cable_debug", diff --git a/src/active_tile_data.cpp b/src/active_tile_data.cpp index 246685d5f9d3..c210c766efb9 100644 --- a/src/active_tile_data.cpp +++ b/src/active_tile_data.cpp @@ -244,7 +244,7 @@ void vehicle_connector_tile::load( JsonObject &jo ) ( void )jo; } -static vehicle *get_vehicle( const item &it ) +static vehicle *get_cable_vehicle( const item &it ) { if( !it.has_flag( flag_CABLE_SPOOL ) ) { return nullptr; @@ -272,7 +272,7 @@ int vehicle_connector_tile::get_resource() const point local_offset = point( this->pos.x % SEEX, this->pos.y % SEEY ); for( const item &it : sm->get_items( local_offset ) ) { - vehicle *veh = get_vehicle( it ); + vehicle *veh = get_cable_vehicle( it ); if( veh != nullptr ) { // TODO: Handle cabled up vehicles without including any of them more than once resource_sum += veh->fuel_left( "battery", false ); @@ -291,7 +291,7 @@ int vehicle_connector_tile::mod_resource( int amt ) point local_offset = point( this->pos.x % SEEX, this->pos.y % SEEY ); for( const item &it : sm->get_items( local_offset ) ) { - vehicle *veh = get_vehicle( it ); + vehicle *veh = get_cable_vehicle( it ); if( veh != nullptr ) { // TODO: Handle cabled up vehicles without including any of them more than once if( amt > 0 ) { diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 5c75d455549b..f487b80924d5 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -3219,7 +3219,7 @@ int vehicle::fuel_left( const itype_id &ftype, bool recurse ) const } ); if( recurse && ftype == fuel_type_battery ) { - auto fuel_counting_visitor = [&]( vehicle const * veh, int amount, int ) { + auto fuel_counting_visitor = [&]( vehicle const * veh, int amount ) { return amount + veh->fuel_left( ftype, false ); }; @@ -4792,9 +4792,9 @@ vehicle *vehicle::find_vehicle( const tripoint &where ) } void vehicle::enumerate_vehicles( std::map &connected_vehicles, - std::set &vehicle_list ) + const std::set &vehicle_list ) { - auto enumerate_visitor = [&connected_vehicles]( vehicle * veh, int amount, int ) { + auto enumerate_visitor = [&connected_vehicles]( vehicle * veh, int amount ) { // Only emplaces if element is not present already. connected_vehicles.emplace( veh, false ); return amount; @@ -4810,14 +4810,12 @@ template int vehicle::traverse_vehicle_graph( Vehicle *start_veh, int amount, Func action ) { // Breadth-first search! Initialize the queue with a pointer to ourselves and go! - std::queue< std::pair > connected_vehs; + std::queue connected_vehs; std::set visited_vehs; - connected_vehs.push( std::make_pair( start_veh, 0 ) ); + connected_vehs.push( start_veh ); while( amount > 0 && !connected_vehs.empty() ) { - auto current_node = connected_vehs.front(); - Vehicle *current_veh = current_node.first; - int current_loss = current_node.second; + Vehicle *current_veh = connected_vehs.front(); visited_vehs.insert( current_veh ); connected_vehs.pop(); @@ -4839,14 +4837,9 @@ int vehicle::traverse_vehicle_graph( Vehicle *start_veh, int amount, Func action // Add this connected vehicle to the queue of vehicles to search next, // but only if we haven't seen this one before. if( visited_vehs.count( target_veh ) < 1 ) { - int target_loss = current_loss + current_veh->part_info( p ).epower; - connected_vehs.push( std::make_pair( target_veh, target_loss ) ); + connected_vehs.push( target_veh ); - float loss_amount = ( static_cast( amount ) * static_cast( target_loss ) ) / 100; - g->u.add_msg_if_player( m_debug, "Visiting remote %p with %d power (loss %f, which is %d percent)", - static_cast( target_veh ), amount, loss_amount, target_loss ); - - amount = action( target_veh, amount, static_cast( loss_amount ) ); + amount = action( target_veh, amount ); g->u.add_msg_if_player( m_debug, "After remote %p, %d power", static_cast( target_veh ), amount ); @@ -4886,9 +4879,9 @@ int vehicle::charge_battery( int amount, bool include_other_vehicles ) } } - auto charge_visitor = []( vehicle * veh, int amount, int lost ) { - g->u.add_msg_if_player( m_debug, "CH: %d", amount - lost ); - return veh->charge_battery( amount - lost, false ); + auto charge_visitor = []( vehicle * veh, int amount ) { + g->u.add_msg_if_player( m_debug, "CH: %d", amount ); + return veh->charge_battery( amount, false ); }; if( amount > 0 && include_other_vehicles ) { // still a bit of charge we could send out... @@ -4923,9 +4916,9 @@ int vehicle::discharge_battery( int amount, bool recurse ) } } - auto discharge_visitor = []( vehicle * veh, int amount, int lost ) { - g->u.add_msg_if_player( m_debug, "CH: %d", amount + lost ); - return veh->discharge_battery( amount + lost, false ); + auto discharge_visitor = []( vehicle * veh, int amount ) { + g->u.add_msg_if_player( m_debug, "CH: %d", amount ); + return veh->discharge_battery( amount, false ); }; if( amount > 0 && recurse ) { // need more power! amount = traverse_vehicle_graph( this, amount, discharge_visitor ); @@ -5304,7 +5297,7 @@ void vehicle::gain_moves() // Force off-map vehicles to load by visiting them every time we gain moves. // Shouldn't be too expensive if there aren't fifty trillion vehicles in the graph... // ...and if there are, it's the player's fault for putting them there. - auto nil_visitor = []( vehicle *, int amount, int ) { + auto nil_visitor = []( vehicle *, int amount ) { return amount; }; traverse_vehicle_graph( this, 1, nil_visitor ); diff --git a/src/vehicle.h b/src/vehicle.h index 0ae18371f3a9..e109c1721145 100644 --- a/src/vehicle.h +++ b/src/vehicle.h @@ -1373,7 +1373,7 @@ class vehicle * @param vehicle_list is a set of pointers to vehicles present in the reality bubble. */ static void enumerate_vehicles( std::map &connected_vehicles, - std::set &vehicle_list ); + const std::set &vehicle_list ); // idle fuel consumption void idle( bool on_map = true ); // continuous processing for running vehicle alarms From bbca035cd5ead10a859c3de0eecd791f1ba27135 Mon Sep 17 00:00:00 2001 From: Coolthulhu Date: Sun, 24 Jan 2021 17:14:19 +0100 Subject: [PATCH 04/12] Grid->vehicle connection working (reverse not yet) --- src/active_tile_data.cpp | 80 +++++++++++++----------------------- src/active_tile_data.h | 6 +-- src/item_factory.cpp | 4 ++ src/iuse.cpp | 57 +++++++++++++++---------- src/map.cpp | 27 ++++++++++++ src/map.h | 7 ++++ src/vehicle.cpp | 4 -- src/vehicle.h | 4 +- tests/crafting_test.cpp | 1 - tests/electric_grid_test.cpp | 56 +++++++++++++++++++++++++ 10 files changed, 163 insertions(+), 83 deletions(-) create mode 100644 tests/electric_grid_test.cpp diff --git a/src/active_tile_data.cpp b/src/active_tile_data.cpp index c210c766efb9..6ec427dea8a2 100644 --- a/src/active_tile_data.cpp +++ b/src/active_tile_data.cpp @@ -218,9 +218,8 @@ void charger_tile::load( JsonObject &jo ) jo.read( "power", power ); } -void vehicle_connector_tile::update_internal( time_point, const tripoint &pos, distribution_grid & ) +void vehicle_connector_tile::update_internal( time_point, const tripoint &, distribution_grid & ) { - this->pos = pos; } active_tile_data *vehicle_connector_tile::clone() const @@ -236,72 +235,49 @@ const std::string &vehicle_connector_tile::get_type() const void vehicle_connector_tile::store( JsonOut &jsout ) const { - ( void )jsout; + jsout.member( "connected_vehicles", connected_vehicles ); } void vehicle_connector_tile::load( JsonObject &jo ) { - ( void )jo; -} - -static vehicle *get_cable_vehicle( const item &it ) -{ - if( !it.has_flag( flag_CABLE_SPOOL ) ) { - return nullptr; - } - // TODO: Common functions instead of copypaste of stringly typed code - const std::string &state = it.get_var( "state" ); - if( state != "pay_out_cable" && state != "cable_charger_link" ) { - return nullptr; - } - // In absolute coords - int source_x = it.get_var( "source_x", 0 ); - int source_y = it.get_var( "source_y", 0 ); - int source_z = it.get_var( "source_z", 0 ); - tripoint abs_ms( source_x, source_y, source_z ); - return vehicle::find_vehicle( abs_ms ); + jo.read( "connected_vehicles", connected_vehicles ); } int vehicle_connector_tile::get_resource() const { int resource_sum = 0; - // TODO: Avoid using submaps here - tripoint sm_coord = ms_to_sm_copy( this->pos ); - submap *sm = MAPBUFFER.lookup_submap( sm_coord ); - // This duplicates what @ref map does, that's not good - point local_offset = point( this->pos.x % SEEX, this->pos.y % SEEY ); - - for( const item &it : sm->get_items( local_offset ) ) { - vehicle *veh = get_cable_vehicle( it ); - if( veh != nullptr ) { - // TODO: Handle cabled up vehicles without including any of them more than once - resource_sum += veh->fuel_left( "battery", false ); + for( const tripoint &veh_abs : connected_vehicles ) { + vehicle *veh = vehicle::find_vehicle( veh_abs ); + if( veh == nullptr ) { + // TODO + debugmsg( "lost vehicle at %d,%d,%d", veh_abs.x, veh_abs.y, veh_abs.z ); + continue; } + + resource_sum += veh->fuel_left( "battery", false ); } return resource_sum; } -// TODO: Extract common function with the above + int vehicle_connector_tile::mod_resource( int amt ) { - // TODO: Avoid using submaps here - tripoint sm_coord = ms_to_sm_copy( this->pos ); - submap *sm = MAPBUFFER.lookup_submap( sm_coord ); - // This duplicates what @ref map does, that's not good - point local_offset = point( this->pos.x % SEEX, this->pos.y % SEEY ); - - for( const item &it : sm->get_items( local_offset ) ) { - vehicle *veh = get_cable_vehicle( it ); - if( veh != nullptr ) { - // TODO: Handle cabled up vehicles without including any of them more than once - if( amt > 0 ) { - amt = veh->charge_battery( amt, false ); - } else { - amt = -veh->discharge_battery( -amt, false ); - } - if( amt == 0 ) { - return 0; - } + for( const tripoint &veh_abs : connected_vehicles ) { + vehicle *veh = vehicle::find_vehicle( veh_abs ); + if( veh == nullptr ) { + // TODO + debugmsg( "lost vehicle at %d,%d,%d", veh_abs.x, veh_abs.y, veh_abs.z ); + continue; + } + + // TODO: Handle cabled up vehicles without including any of them more than once + if( amt > 0 ) { + amt = veh->charge_battery( amt, false ); + } else { + amt = -veh->discharge_battery( -amt, false ); + } + if( amt == 0 ) { + return 0; } } diff --git a/src/active_tile_data.h b/src/active_tile_data.h index 6de53f1c3df4..83730ef66e54 100644 --- a/src/active_tile_data.h +++ b/src/active_tile_data.h @@ -102,10 +102,10 @@ class charger_tile : public active_tile_data class vehicle_connector_tile : public active_tile_data { - private: - // VERY ugly hack, don't merge!!!!!!!!111 - tripoint pos; public: + /* In absolute map square coordinates */ + std::vector connected_vehicles; + void update_internal( time_point to, const tripoint &p, distribution_grid &grid ) override; active_tile_data *clone() const override; const std::string &get_type() const override; diff --git a/src/item_factory.cpp b/src/item_factory.cpp index bf6ea7215211..d8c589156012 100644 --- a/src/item_factory.cpp +++ b/src/item_factory.cpp @@ -1342,6 +1342,10 @@ void Item_factory::check_definitions() const if( !actor->is_valid() ) { msg += string_format( "item action \"%s\" was not described.\n", actor->type.c_str() ); } + + if( actor->type == "CABLE_ATTACH" && !vpart_id( type->id ).is_valid() ) { + msg += string_format( "no valid vehicle part for CABLE_ATTACH action\n" ); + } } if( type->fuel && !type->count_by_charges() ) { diff --git a/src/iuse.cpp b/src/iuse.cpp index d8d715d98d10..c298f2a9a056 100644 --- a/src/iuse.cpp +++ b/src/iuse.cpp @@ -8894,8 +8894,7 @@ int iuse::cable_attach( player *p, item *it, bool, const tripoint & ) } const tripoint posp = *posp_; const optional_vpart_position vp = g->m.veh_at( posp ); - auto ter = g->m.ter( posp ); - if( !vp && ter != t_chainfence ) { + if( !vp ) { p->add_msg_if_player( _( "There's no vehicle there." ) ); return 0; } else { @@ -9005,8 +9004,10 @@ int iuse::cable_attach( player *p, item *it, bool, const tripoint & ) const tripoint vpos = *vpos_; const optional_vpart_position target_vp = g->m.veh_at( vpos ); - if( !target_vp ) { - p->add_msg_if_player( _( "There's no vehicle there." ) ); + // TODO: MEGA UGLY! Don't merge! + vehicle_connector_tile *grid_connection = g->m.active_furniture_at( vpos ); + if( !target_vp && !grid_connection ) { + p->add_msg_if_player( _( "There's no vehicle or grid connection there." ) ); return 0; } else if( cable_cbm ) { const auto abspos = g->m.getabs( vpos ); @@ -9026,29 +9027,41 @@ int iuse::cable_attach( player *p, item *it, bool, const tripoint & ) return 0; } + tripoint source_global( it->get_var( "source_x", 0 ), + it->get_var( "source_y", 0 ), + it->get_var( "source_z", 0 ) ); tripoint target_global = g->m.getabs( vpos ); - // TODO: make sure there is always a matching vpart id here. Maybe transform this into - // a iuse_actor class, or add a check in item_factory. const vpart_id vpid( it->typeId() ); point vcoords = source_vp->mount(); vehicle_part source_part( vpid, vcoords, item( *it ) ); - source_part.target.first = target_global; - source_part.target.second = g->m.getabs( target_veh->global_pos3() ); - source_veh->install_part( vcoords, source_part ); - - vcoords = target_vp->mount(); - vehicle_part target_part( vpid, vcoords, item( *it ) ); - tripoint source_global( it->get_var( "source_x", 0 ), - it->get_var( "source_y", 0 ), - it->get_var( "source_z", 0 ) ); - target_part.target.first = source_global; - target_part.target.second = g->m.getabs( source_veh->global_pos3() ); - target_veh->install_part( vcoords, target_part ); - - if( p != nullptr && p->has_item( *it ) ) { - p->add_msg_if_player( m_good, _( "You link up the electric systems of the %1$s and the %2$s." ), - source_veh->name, target_veh->name ); + if( grid_connection != nullptr ) { + source_part.target.first = target_global; + source_part.target.second = target_global; + source_part.set_flag( vehicle_part::targets_grid ); + if( p != nullptr && p->has_item( *it ) ) { + p->add_msg_if_player( m_good, _( "You connect the %s to the electric grid." ), + source_veh->name ); + grid_connection->connected_vehicles.emplace_back( source_global ); + source_veh->install_part( vcoords, source_part ); + } + } else { + source_part.target.first = target_global; + source_part.target.second = g->m.getabs( target_veh->global_pos3() ); + source_veh->install_part( vcoords, source_part ); + + if( target_vp ) { + vcoords = target_vp->mount(); + vehicle_part target_part( vpid, vcoords, item( *it ) ); + target_part.target.first = source_global; + target_part.target.second = g->m.getabs( source_veh->global_pos3() ); + target_veh->install_part( vcoords, target_part ); + + if( p != nullptr && p->has_item( *it ) ) { + p->add_msg_if_player( m_good, _( "You link up the electric systems of the %1$s and the %2$s." ), + source_veh->name, target_veh->name ); + } + } } return 1; // Let the cable be destroyed. diff --git a/src/map.cpp b/src/map.cpp index 0e9f5a858bca..0cd510d3d73f 100755 --- a/src/map.cpp +++ b/src/map.cpp @@ -8558,6 +8558,33 @@ void map::on_saved() } } +template +const T *map::active_furniture_at( const tripoint &p ) const +{ + return const_cast( this )->active_furniture_at( p ); +} + +template +T *map::active_furniture_at( const tripoint &p ) +{ + point offset; + submap *sm = get_submap_at( p, offset ); + auto iter = sm->active_furniture.find( offset ); + if( iter == sm->active_furniture.end() ) { + return nullptr; + } + + return dynamic_cast( &*iter->second ); +} + +template const active_tile_data *map::active_furniture_at +( const tripoint & ) const; +template const vehicle_connector_tile *map::active_furniture_at +( const tripoint & ) const; +template active_tile_data *map::active_furniture_at( const tripoint & ); +template vehicle_connector_tile *map::active_furniture_at +( const tripoint & ); + void map::process_distribution_grids() { if( !get_option( "ELECTRIC_GRID" ) ) { diff --git a/src/map.h b/src/map.h index a7ac6c18b45b..8b44ec8d71fd 100644 --- a/src/map.h +++ b/src/map.h @@ -43,6 +43,7 @@ namespace catacurses { class window; } // namespace catacurses +class active_tile_data; class Character; class Creature; class basecamp; @@ -1874,6 +1875,12 @@ class map void make_distribution_grid_at( const tripoint &omt_pos ); public: + template + T * active_furniture_at( const tripoint &pos ); + + template + const T * active_furniture_at( const tripoint &pos ) const; + const level_cache &get_cache_ref( int zlev ) const { return *caches[zlev + OVERMAP_DEPTH]; } diff --git a/src/vehicle.cpp b/src/vehicle.cpp index f487b80924d5..d0c061847a70 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -3211,10 +3211,6 @@ int vehicle::fuel_left( const itype_id &ftype, bool recurse ) const { int fl = std::accumulate( parts.begin(), parts.end(), 0, [&ftype]( const int &lhs, const vehicle_part & rhs ) { - // don't count frozen liquid - if( rhs.is_tank() && rhs.base.contents_made_of( SOLID ) ) { - return lhs; - } return lhs + ( rhs.ammo_current() == ftype ? rhs.ammo_remaining() : 0 ); } ); diff --git a/src/vehicle.h b/src/vehicle.h index e109c1721145..53e03ca2d17e 100644 --- a/src/vehicle.h +++ b/src/vehicle.h @@ -193,7 +193,8 @@ struct vehicle_part { animal_flag = 2, carried_flag = 4, carrying_flag = 8, - tracked_flag = 16 //carried vehicle part with tracking enabled + tracked_flag = 16, //carried vehicle part with tracking enabled + targets_grid = 32, // Jumper cable is to grid, not vehicle }; vehicle_part(); /** DefaultConstructible */ @@ -203,6 +204,7 @@ struct vehicle_part { /** Check this instance is non-null (not default constructed) */ explicit operator bool() const; + // TODO: Make all of those use the above enum bool has_flag( const int flag ) const noexcept { return flag & flags; } diff --git a/tests/crafting_test.cpp b/tests/crafting_test.cpp index be6c4447ca8b..3c8e77ddd278 100644 --- a/tests/crafting_test.cpp +++ b/tests/crafting_test.cpp @@ -553,7 +553,6 @@ TEST_CASE( "total crafting time with or without interruption", "[crafting][time] } } -#include "output.h" TEST_CASE( "oven electric grid test", "[crafting][overmap][grids][slow]" ) { constexpr tripoint start_pos = tripoint( 60, 60, 0 ); diff --git a/tests/electric_grid_test.cpp b/tests/electric_grid_test.cpp new file mode 100644 index 000000000000..8c5463b255b4 --- /dev/null +++ b/tests/electric_grid_test.cpp @@ -0,0 +1,56 @@ +#include + +#include "active_tile_data.h" +#include "catch/catch.hpp" +#include "coordinate_conversions.h" +#include "distribution_grid.h" +#include "game.h" +#include "map.h" +#include "map_helpers.h" +#include "overmap.h" +#include "overmapbuffer.h" +#include "vehicle.h" + +TEST_CASE( "connect vehicle", "[grids]" ) +{ + clear_map_and_put_player_underground(); + const tripoint vehicle_local_pos = tripoint( 10, 10, 0 ); + const tripoint vehicle_abs_pos = g->m.getabs( vehicle_local_pos ); + const tripoint furniture_local_pos = tripoint( 13, 10, 0 ); + const tripoint furniture_abs_pos = g->m.getabs( furniture_local_pos ); + + + // TODO: Wrap in clear_grids()? + auto om = overmap_buffer.get_om_global( sm_to_omt_copy( g->m.getabs( furniture_local_pos ) ) ); + // TODO: That's a lot of setup, implying barely testable design + om.om->set_electric_grid_connections( om.local, {} ); + // Mega ugly + g->load_map( g->m.get_abs_sub() ); + + g->m.furn_set( furniture_local_pos, furn_str_id( "f_cable_connector" ) ); + vehicle *veh = g->m.add_vehicle( vproto_id( "car" ), vehicle_local_pos, 0, 100, 0, false ); + vehicle_connector_tile *grid_connector = g->m.active_furniture_at + ( furniture_abs_pos ); + + REQUIRE( veh ); + REQUIRE( grid_connector ); + + const point cable_part_pos = point( 0, 0 ); + vehicle_part source_part( vpart_id( "jumper_cable" ), cable_part_pos, item( "jumper_cable" ) ); + source_part.target.first = furniture_abs_pos; + source_part.target.second = furniture_abs_pos; + source_part.set_flag( vehicle_part::targets_grid ); + grid_connector->connected_vehicles.emplace_back( vehicle_abs_pos ); + int part_index = veh->install_part( cable_part_pos, source_part ); + + REQUIRE( part_index >= 0 ); + + distribution_grid &grid = g->m.distribution_grid_at( furniture_local_pos ); + REQUIRE( !grid.empty() ); + CHECK( grid.get_resource() == veh->fuel_left( itype_id( "battery" ), false ) ); + + int vehicle_battery_before = veh->fuel_left( itype_id( "battery" ), false ); + CHECK( grid.mod_resource( -10 ) == 0 ); + CHECK( grid.get_resource() == vehicle_battery_before - 10 ); + CHECK( veh->fuel_left( itype_id( "battery" ), false ) == vehicle_battery_before - 10 ); +} From 07cc8a6bfccc448d8bb30ef4da7b14a95985c549 Mon Sep 17 00:00:00 2001 From: Coolthulhu Date: Sat, 30 Jan 2021 20:35:18 +0100 Subject: [PATCH 05/12] Halfway towards getting veh/grid traversal --- src/active_tile_data.cpp | 25 ++++ src/active_tile_data.h | 9 ++ src/distribution_grid.cpp | 250 ++++++++++++++++++++++++++++++++++- src/distribution_grid.h | 78 ++++++++++- src/game.cpp | 13 +- src/game.h | 4 + src/iexamine.cpp | 3 +- src/inventory.cpp | 4 +- src/iuse.cpp | 3 +- src/map.cpp | 111 +--------------- src/map.h | 30 ----- src/mapbuffer.cpp | 3 +- src/vehicle.cpp | 12 +- tests/crafting_test.cpp | 23 ++-- tests/electric_grid_test.cpp | 89 +++++++++---- tests/test_main.cpp | 2 + 16 files changed, 475 insertions(+), 184 deletions(-) diff --git a/src/active_tile_data.cpp b/src/active_tile_data.cpp index 6ec427dea8a2..b7a7dc37a373 100644 --- a/src/active_tile_data.cpp +++ b/src/active_tile_data.cpp @@ -18,6 +18,31 @@ static const std::string flag_CABLE_SPOOL( "CABLE_SPOOL" ); static const std::string flag_RECHARGE( "RECHARGE" ); static const std::string flag_USE_UPS( "USE_UPS" ); +namespace active_tiles +{ + +template +T *furn_at( const tripoint &p ) +{ + point offset( p.x % SEEX, p.y % SEEY ); + submap *sm = MAPBUFFER.lookup_submap( ms_to_sm_copy( p ) ); + if( sm == nullptr ) { + return nullptr; + } + auto iter = sm->active_furniture.find( offset ); + if( iter == sm->active_furniture.end() ) { + return nullptr; + } + + return dynamic_cast( &*iter->second ); +} + +template active_tile_data *furn_at( const tripoint & ); +template vehicle_connector_tile *furn_at( const tripoint & ); +template battery_tile *furn_at( const tripoint & ); + +} // namespace active_tiles + active_tile_data::~active_tile_data() {} void active_tile_data::serialize( JsonOut &jsout ) const diff --git a/src/active_tile_data.h b/src/active_tile_data.h index 83730ef66e54..9f4688fe8c1e 100644 --- a/src/active_tile_data.h +++ b/src/active_tile_data.h @@ -116,4 +116,13 @@ class vehicle_connector_tile : public active_tile_data int mod_resource( int amt ) override; }; +// TODO: Better place for this +namespace active_tiles +{ + +template +T * furn_at( const tripoint &pos ); + +} // namespace active_tiles + #endif // CATA_SRC_ACTIVE_TILE_DATA_H diff --git a/src/distribution_grid.cpp b/src/distribution_grid.cpp index 2825302f9254..542f389103fe 100644 --- a/src/distribution_grid.cpp +++ b/src/distribution_grid.cpp @@ -1,20 +1,28 @@ +#include + #include "distribution_grid.h" #include "coordinate_conversions.h" #include "active_tile_data.h" +#include "map.h" #include "mapbuffer.h" +#include "map_iterator.h" #include "submap.h" +#include "overmapbuffer.h" -distribution_grid::distribution_grid( const std::vector &global_submap_coords ) +distribution_grid::distribution_grid( const std::vector &global_submap_coords, + mapbuffer &buffer ) : + submap_coords( global_submap_coords ), + mb( buffer ) { - for( const tripoint &sm_coord : global_submap_coords ) { - submap *sm = MAPBUFFER.lookup_submap( sm_coord ); + for( const tripoint &sm_coord : submap_coords ) { + submap *sm = mb.lookup_submap( sm_coord ); if( sm == nullptr ) { // Debugmsg already printed in mapbuffer.cpp return; } - const tripoint ms_pos = sm_to_ms_copy( sm_coord ); for( auto &active : sm->active_furniture ) { + const tripoint ms_pos = sm_to_ms_copy( sm_coord ); contents[sm_coord].emplace_back( active.first, ms_pos + active.first ); } } @@ -25,10 +33,15 @@ bool distribution_grid::empty() const return contents.empty(); } +distribution_grid::operator bool() const +{ + return !empty(); +} + void distribution_grid::update( time_point to ) { for( const std::pair> &c : contents ) { - submap *sm = MAPBUFFER.lookup_submap( c.first ); + submap *sm = mb.lookup_submap( c.first ); if( sm == nullptr ) { return; } @@ -52,7 +65,7 @@ int distribution_grid::mod_resource( int amt ) if( amt == 0 ) { return 0; } - submap *sm = MAPBUFFER.lookup_submap( c.first ); + submap *sm = mb.lookup_submap( c.first ); if( sm == nullptr ) { continue; } @@ -69,7 +82,7 @@ int distribution_grid::get_resource() const { int res = 0; for( const std::pair> &c : contents ) { - submap *sm = MAPBUFFER.lookup_submap( c.first ); + submap *sm = mb.lookup_submap( c.first ); if( sm == nullptr ) { continue; } @@ -81,3 +94,226 @@ int distribution_grid::get_resource() const return res; } + +distribution_grid_tracker::distribution_grid_tracker() + : distribution_grid_tracker( MAPBUFFER ) +{} + +distribution_grid_tracker::distribution_grid_tracker( mapbuffer &buffer ) + : mb( buffer ) +{ +} + +void distribution_grid_tracker::make_distribution_grid_at( const tripoint &sm_pos ) +{ + const std::set overmap_positions = overmap_buffer.electric_grid_at( sm_to_omt_copy( + sm_pos ) ); + std::vector submap_positions; + for( const tripoint &omp : overmap_positions ) { + submap_positions.emplace_back( omt_to_sm_copy( omp ) + point( 0, 0 ) ); + submap_positions.emplace_back( omt_to_sm_copy( omp ) + point( 1, 0 ) ); + submap_positions.emplace_back( omt_to_sm_copy( omp ) + point( 0, 1 ) ); + submap_positions.emplace_back( omt_to_sm_copy( omp ) + point( 1, 1 ) ); + } + shared_ptr_fast dist_grid = make_shared_fast + ( submap_positions, mb ); + if( dist_grid->empty() ) { + for( const tripoint &smp : submap_positions ) { + parent_distribution_grids.erase( smp ); + } + return; + } + for( const tripoint &smp : submap_positions ) { + parent_distribution_grids[smp] = dist_grid; + } +} + +void distribution_grid_tracker::on_saved() +{ + parent_distribution_grids.clear(); + tripoint min_bounds = tripoint( bounds.p_min, -OVERMAP_DEPTH ); + tripoint max_bounds = tripoint( bounds.p_max, OVERMAP_HEIGHT ); + // TODO: Only those which existed before the save + for( const tripoint &sm_pos : tripoint_range( min_bounds, max_bounds ) ) { + if( parent_distribution_grids.find( sm_pos ) == parent_distribution_grids.end() ) { + make_distribution_grid_at( sm_pos ); + } + } +} + +void distribution_grid_tracker::on_changed( const tripoint &p ) +{ + tripoint sm_pos = ms_to_sm_copy( p ); + if( bounds.contains_half_open( sm_pos.xy() ) ) { + // TODO: Don't rebuild, update + make_distribution_grid_at( sm_pos ); + } + +} + +distribution_grid &distribution_grid_tracker::grid_at( const tripoint &p ) +{ + // TODO: empty mapbuffer for this case + static distribution_grid empty_grid( {}, MAPBUFFER ); + tripoint sm_pos = ms_to_sm_copy( p ); + auto iter = parent_distribution_grids.find( sm_pos ); + if( iter != parent_distribution_grids.end() ) { + return *iter->second; + } + + return empty_grid; +} + +const distribution_grid &distribution_grid_tracker::grid_at( const tripoint &p ) const +{ + return const_cast( + const_cast( this )->grid_at( p ) ); +} + +void distribution_grid_tracker::update( time_point to ) +{ + // TODO: Don't recalc this every update + std::unordered_set updated; + for( auto &pr : parent_distribution_grids ) { + if( updated.count( pr.second.get() ) == 0 ) { + updated.emplace( pr.second.get() ); + pr.second->update( to ); + } + } +} + +void distribution_grid_tracker::load( rectangle area ) +{ + bounds = area; + // TODO: Don't reload everything when not needed + on_saved(); +} + +void distribution_grid_tracker::load( const map &m ) +{ + load( rectangle( m.get_abs_sub().xy(), + m.get_abs_sub().xy() + point( m.getmapsize(), m.getmapsize() ) ) ); +} + +#include "game.h" +#include "vehicle.h" +#include "avatar.h" + +template +int traverse_graph( StartPoint *start, int amount, + VehFunc veh_action, GridFunc grid_action ) +{ + + struct vehicle_or_grid { + enum class type_t { + vehicle, + grid + } type; + + vehicle *const veh; + distribution_grid *const grid; + + vehicle_or_grid( vehicle *veh ) + : type( type_t::vehicle ) + , veh( veh ) + {} + + vehicle_or_grid( distribution_grid *grid ) + : type( type_t::grid ) + , grid( grid ) + {} + + bool operator==( const vehicle *veh ) const { + return this->veh = veh; + } + + bool operator==( const distribution_grid *grid ) const { + return this->grid = grid; + } + }; + + std::queue connected_elements; + std::set visited_elements; + connected_elements.emplace( start ); + + auto process_vehicle = [&]( vehicle * veh ) { + // Add this connected vehicle to the queue of vehicles to search next, + // but only if we haven't seen this one before. + if( visited_elements.count( veh ) < 1 ) { + connected_elements.emplace( veh ); + + amount = veh_action( veh, amount ); + g->u.add_msg_if_player( m_debug, "After remote veh %p, %d power", static_cast( veh ), + amount ); + + return amount < 1; + } + }; + + auto process_grid = [&]( distribution_grid * grid ) { + if( visited_elements.count( grid ) < 1 ) { + connected_elements.emplace( grid ); + + amount = grid_action( grid, amount ); + g->u.add_msg_if_player( m_debug, "After remote grid %p, %d power", static_cast( grid ), + amount ); + + return amount < 1; + } + }; + + auto grid_tracker = get_distribution_grid_tracker(); + while( amount > 0 && !connected_elements.empty() ) { + vehicle_or_grid current = connected_elements.front(); + + visited_elements.insert( current ); + connected_elements.pop(); + + if( current.type == vehicle_or_grid::type_t::vehicle ) { + auto *current_veh = current.veh; + for( auto &p : current_veh->loose_parts ) { + if( !current_veh->part_info( p ).has_flag( "POWER_TRANSFER" ) ) { + continue; // ignore loose parts that aren't power transfer cables + } + + const tripoint target_pos = current_veh->parts[p].target.second; + if( current_veh->parts[p].has_flag( vehicle_part::targets_grid ) ) { + auto target_grid = grid_tracker.grid_at( target_pos ); + if( target_grid && process_grid( &target_grid ) ) { + break; + } + } else { + + auto target_veh = vehicle::find_vehicle( target_pos ); + // Skip visited or unloadable vehicles + if( target_veh != nullptr && visited_elements.count( target_veh ) == 0 && + process_vehicle( target_veh ) ) { + // No more charge to donate away. + break; + } + } + } + } else { + // Grids can only be connected to vehicles at the moment + auto *current_grid = current.grid; + for( auto &pr : current_grid->contents ) { + const vehicle_connector_tile *connector = active_tiles::furn_at + ( pr.second.absolute ); + if( connector == nullptr ) { + continue; + } + + for( const tripoint &target_pos : connector->connected_vehicles ) { + auto target_veh = vehicle::find_vehicle( target_pos ); + // Skip visited or unloadable vehicles + if( target_veh != nullptr && visited_elements.count( target_veh ) == 0 && + process_vehicle( target_veh ) ) { + // No more charge to donate away. + break; + } + } + } + } + } + return amount; +} diff --git a/src/distribution_grid.h b/src/distribution_grid.h index d731ddf3e4a0..84cb2f93b867 100644 --- a/src/distribution_grid.h +++ b/src/distribution_grid.h @@ -9,6 +9,9 @@ #include "memory_fast.h" #include "point.h" +class map; +class mapbuffer; + struct tile_location { point on_submap; tripoint absolute; @@ -26,19 +29,90 @@ struct tile_location { class distribution_grid { private: + friend class distribution_grid_tracker; /** * Map of submap coords to points on this submap * that contain an active tile. */ std::map> contents; + std::vector submap_coords; + + mapbuffer &mb; public: - distribution_grid() = default; - distribution_grid( const std::vector &global_submap_coords ); + distribution_grid( const std::vector &global_submap_coords, mapbuffer &buffer ); bool empty() const; + explicit operator bool() const; void update( time_point to ); int mod_resource( int amt ); int get_resource() const; }; +/** + * Contains and manages all the active distribution grids. + */ +class distribution_grid_tracker +{ + private: + /** + * Mapping of sm position to grid it belongs to. + */ + std::map> parent_distribution_grids; + + /** + * @param omt_pos Absolute submap position of one of the tiles of the grid. + */ + void make_distribution_grid_at( const tripoint &sm_pos ); + + /** + * In submap coords, to mirror @ref map + */ + rectangle bounds; + + mapbuffer &mb; + + public: + distribution_grid_tracker(); + distribution_grid_tracker( mapbuffer &buffer ); + distribution_grid_tracker( distribution_grid_tracker && ) = default; + /** + * Gets grid at given global map square coordinate. @ref map::getabs + */ + /**@{*/ + distribution_grid &grid_at( const tripoint &p ); + const distribution_grid &grid_at( const tripoint &p ) const; + /*@}*/ + + void update( time_point to ); + /** + * Loads grids in an area given by submap coords. + */ + void load( rectangle area ); + /** + * Loads grids in the same area as a given map. + */ + void load( const map &m ); + + /** + * Updates grid at given global map square coordinate. + */ + void on_changed( const tripoint &p ); + void on_saved(); + + /** + * Traverses the graph of connected vehicles and grids. + */ + template + int traverse_graph( StartPoint *start, int amount, + VehFunc veh_action, GridFunc grid_action ); +}; + + + +/** + * Returns distribution grid tracker that is a part of the global game *g. @ref game + * TODO: This wouldn't be required in an ideal world + */ +distribution_grid_tracker &get_distribution_grid_tracker(); + #endif // CATA_SRC_DISTRIBUTION_GRID_H diff --git a/src/game.cpp b/src/game.cpp index 94f6843d25fa..de740cab824d 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -58,6 +58,7 @@ #include "damage.h" #include "debug.h" #include "dependency_tree.h" +#include "distribution_grid.h" #include "editmap.h" #include "enums.h" #include "event.h" @@ -264,6 +265,7 @@ game::game() : liveview( *liveview_ptr ), scent_ptr( *this ), achievements_tracker_ptr( *stats_tracker_ptr, achievement_attained ), + grid_tracker_ptr( MAPBUFFER ), m( *map_ptr ), u( *u_ptr ), scent( *scent_ptr ), @@ -604,6 +606,7 @@ special_game_id game::gametype() const void game::load_map( const tripoint &pos_sm ) { m.load( pos_sm, true ); + grid_tracker_ptr->load( m ); } // Set up all default values for a new game @@ -1507,8 +1510,8 @@ bool game::do_turn() m.vehmove(); m.process_fields(); m.process_items(); - m.process_distribution_grids(); m.creature_in_field( u ); + grid_tracker_ptr->update( calendar::turn ); // Apply sounds from previous turn to monster and NPC AI. sounds::process_sounds(); @@ -10943,6 +10946,9 @@ point game::update_map( int &x, int &y ) remaining_shift -= this_shift; } + // TODO: Shift, don't reload + grid_tracker_ptr->load( m ); + // Shift monsters shift_monsters( tripoint( shift, 0 ) ); const point shift_ms = sm_to_ms_copy( shift ); @@ -12300,3 +12306,8 @@ void avatar_moves( const avatar &u, const map &m, const tripoint &p ) u.get_movement_mode(), u.is_underwater(), p.z ); } } // namespace cata_event_dispatch + +distribution_grid_tracker &get_distribution_grid_tracker() +{ + return *g->grid_tracker_ptr; +} diff --git a/src/game.h b/src/game.h index fe86c1adafc7..21f4568c285a 100644 --- a/src/game.h +++ b/src/game.h @@ -116,6 +116,8 @@ class timed_event_manager; class ui_adaptor; struct visibility_variables; +class distribution_grid_tracker; + using item_filter = std::function; enum peek_act : int { @@ -146,6 +148,7 @@ class game friend class advanced_inventory; friend class main_menu; friend class target_handler; + friend distribution_grid_tracker &get_distribution_grid_tracker(); public: game(); ~game(); @@ -976,6 +979,7 @@ class game pimpl kill_tracker_ptr; pimpl memorial_logger_ptr; pimpl spell_events_ptr; + pimpl grid_tracker_ptr; public: /** Make map a reference here, to avoid map.h in game.h */ diff --git a/src/iexamine.cpp b/src/iexamine.cpp index 2751a2e4d127..c83ba095e083 100644 --- a/src/iexamine.cpp +++ b/src/iexamine.cpp @@ -5642,7 +5642,8 @@ void iexamine::dimensional_portal( player &p, const tripoint &pos ) void iexamine::check_power( player &, const tripoint &pos ) { - int amt = g->m.distribution_grid_at( pos ).get_resource(); + tripoint abspos = g->m.getabs( pos ); + int amt = get_distribution_grid_tracker().grid_at( abspos ).get_resource(); add_msg( m_info, _( "This electric grid stores %d kJ of electric power." ), amt ); } diff --git a/src/inventory.cpp b/src/inventory.cpp index 8b759a8083d0..bdeccd6ca071 100644 --- a/src/inventory.cpp +++ b/src/inventory.cpp @@ -448,7 +448,9 @@ void inventory::form_from_map( map &m, std::vector pts, const Characte item furn_item( type, calendar::turn, 0 ); furn_item.item_tags.insert( "PSEUDO" ); if( furn_item.has_flag( "USES_GRID_POWER" ) ) { - furn_item.charges = m.distribution_grid_at( p ).get_resource(); + // TODO: The grid tracker should correspond to map! + auto &grid = get_distribution_grid_tracker().grid_at( m.getabs( p ) ); + furn_item.charges = grid.get_resource(); } else { furn_item.charges = ammo ? count_charges_in_list( ammo, m.i_at( p ) ) : 0; } diff --git a/src/iuse.cpp b/src/iuse.cpp index c298f2a9a056..7fe3fcdb9b64 100644 --- a/src/iuse.cpp +++ b/src/iuse.cpp @@ -9004,8 +9004,7 @@ int iuse::cable_attach( player *p, item *it, bool, const tripoint & ) const tripoint vpos = *vpos_; const optional_vpart_position target_vp = g->m.veh_at( vpos ); - // TODO: MEGA UGLY! Don't merge! - vehicle_connector_tile *grid_connection = g->m.active_furniture_at( vpos ); + vehicle_connector_tile *grid_connection = active_tiles::furn_at( vpos ); if( !target_vp && !grid_connection ) { p->add_msg_if_player( _( "There's no vehicle or grid connection there." ) ); return 0; diff --git a/src/map.cpp b/src/map.cpp index 0cd510d3d73f..cab3d796b789 100755 --- a/src/map.cpp +++ b/src/map.cpp @@ -1341,11 +1341,12 @@ void map::furn_set( const tripoint &p, const furn_id &new_furniture ) if( old_t.active ) { current_submap->active_furniture.erase( l ); - make_distribution_grid_at( ms_to_omt_copy( getabs( p ) ) ); + // TODO: Only for g->m? Observer pattern? + get_distribution_grid_tracker().on_changed( getabs( p ) ); } if( new_t.active ) { current_submap->active_furniture[l].reset( new_t.active->clone() ); - make_distribution_grid_at( ms_to_omt_copy( getabs( p ) ) ); + get_distribution_grid_tracker().on_changed( getabs( p ) ); } } @@ -4772,9 +4773,11 @@ static void use_charges_from_furn( const furn_t &f, const itype_id &type, int &q const itype *itt = f.crafting_pseudo_item_type(); if( itt != nullptr && itt->item_tags.count( flag_USES_GRID_POWER ) > 0 ) { - item furn_item( itt, -1, m->distribution_grid_at( p ).get_resource() ); + const tripoint abspos = m->getabs( p ); + auto &grid = get_distribution_grid_tracker().grid_at( abspos ); + item furn_item( itt, -1, grid.get_resource() ); if( filter( furn_item ) ) { - quantity = -m->distribution_grid_at( p ).mod_resource( -quantity ); + quantity = -grid.mod_resource( -quantity ); } } else if( itt != nullptr && itt->tool && !itt->tool->ammo_id.empty() ) { const itype_id ammo = ammotype( *itt->tool->ammo_id.begin() )->default_ammotype(); @@ -6396,7 +6399,6 @@ void map::load( const tripoint &w, const bool update_vehicle ) } field_furn_locs.clear(); submaps_with_active_items.clear(); - parent_distribution_grids.clear(); set_abs_sub( w ); for( int gridx = 0; gridx < my_MAPSIZE; gridx++ ) { for( int gridy = 0; gridy < my_MAPSIZE; gridy++ ) { @@ -6782,10 +6784,6 @@ void map::loadn( const tripoint &grid, const bool update_vehicles ) } } - if( get_option( "ELECTRIC_GRID" ) && !tmpsub->active_furniture.empty() ) { - make_distribution_grid_at( sm_to_omt_copy( grid_abs_sub ) ); - } - actualize( grid ); abs_sub.z = old_abs_z; @@ -8507,101 +8505,6 @@ void map::calc_max_populated_zlev() } } -const distribution_grid &map::distribution_grid_at( const tripoint &p ) const -{ - static distribution_grid null_grid; - const tripoint coord = ms_to_omt_copy( getabs( p ) ); - auto iter = parent_distribution_grids.find( coord ); - if( iter != parent_distribution_grids.end() ) { - return *iter->second; - } - - return null_grid; -} - -distribution_grid &map::distribution_grid_at( const tripoint &p ) -{ - return const_cast( const_cast( this )->distribution_grid_at( - p ) ); -} - -void map::make_distribution_grid_at( const tripoint &grid ) -{ - const std::set overmap_positions = overmap_buffer.electric_grid_at( grid ); - std::vector submap_positions; - for( const tripoint &omp : overmap_positions ) { - submap_positions.emplace_back( omt_to_sm_copy( omp ) + point( 0, 0 ) ); - submap_positions.emplace_back( omt_to_sm_copy( omp ) + point( 1, 0 ) ); - submap_positions.emplace_back( omt_to_sm_copy( omp ) + point( 0, 1 ) ); - submap_positions.emplace_back( omt_to_sm_copy( omp ) + point( 1, 1 ) ); - } - shared_ptr_fast dist_grid = make_shared_fast - ( submap_positions ); - if( dist_grid->empty() ) { - for( const tripoint &omp : overmap_positions ) { - parent_distribution_grids.erase( omp ); - } - return; - } - for( const tripoint &omp : overmap_positions ) { - parent_distribution_grids[omp] = dist_grid; - } -} - -void map::on_saved() -{ - parent_distribution_grids.clear(); - const tripoint low = sm_to_omt_copy( { get_abs_sub().xy(), -OVERMAP_DEPTH } ); - const tripoint high = sm_to_omt_copy( { get_abs_sub().xy() + point( my_MAPSIZE, my_MAPSIZE ), OVERMAP_HEIGHT } ); - for( const tripoint &om_pos : tripoint_range( low, high ) ) { - make_distribution_grid_at( om_pos ); - } -} - -template -const T *map::active_furniture_at( const tripoint &p ) const -{ - return const_cast( this )->active_furniture_at( p ); -} - -template -T *map::active_furniture_at( const tripoint &p ) -{ - point offset; - submap *sm = get_submap_at( p, offset ); - auto iter = sm->active_furniture.find( offset ); - if( iter == sm->active_furniture.end() ) { - return nullptr; - } - - return dynamic_cast( &*iter->second ); -} - -template const active_tile_data *map::active_furniture_at -( const tripoint & ) const; -template const vehicle_connector_tile *map::active_furniture_at -( const tripoint & ) const; -template active_tile_data *map::active_furniture_at( const tripoint & ); -template vehicle_connector_tile *map::active_furniture_at -( const tripoint & ); - -void map::process_distribution_grids() -{ - if( !get_option( "ELECTRIC_GRID" ) ) { - return; - } - - std::unordered_set checked; - for( const auto &pr : parent_distribution_grids ) { - if( checked.count( &*pr.second ) != 0 ) { - continue; - } - - checked.insert( &*pr.second ); - pr.second->update( calendar::turn ); - } -} - tripoint drawsq_params::center() const { if( view_center == tripoint_min ) { diff --git a/src/map.h b/src/map.h index 8b44ec8d71fd..5d235165c57a 100644 --- a/src/map.h +++ b/src/map.h @@ -89,7 +89,6 @@ struct pathfinding_cache; struct pathfinding_settings; template struct weighted_int_list; -class distribution_grid; class map_stack : public item_stack { @@ -1423,8 +1422,6 @@ class map */ void process_falling(); - void process_distribution_grids(); - bool is_cornerfloor( const tripoint &p ) const; // mapgen.cpp functions @@ -1864,23 +1861,7 @@ class map visibility_variables visibility_variables_cache; - /** - * Mapping of omt position to grid it belongs to. - */ - std::map> parent_distribution_grids; - - /** - * @param omt_pos Absolute overmap tile position of one of the tiles of the grid. - */ - void make_distribution_grid_at( const tripoint &omt_pos ); - public: - template - T * active_furniture_at( const tripoint &pos ); - - template - const T * active_furniture_at( const tripoint &pos ) const; - const level_cache &get_cache_ref( int zlev ) const { return *caches[zlev + OVERMAP_DEPTH]; } @@ -1925,17 +1906,6 @@ class map level_cache &access_cache( int zlev ); const level_cache &access_cache( int zlev ) const; bool need_draw_lower_floor( const tripoint &p ); - - /** - * Returns the grid at given local coord. - * The grid can be empty. - */ - /*@{*/ - const distribution_grid &distribution_grid_at( const tripoint &p ) const; - distribution_grid &distribution_grid_at( const tripoint &p ); - /*@}*/ - - void on_saved(); }; template diff --git a/src/mapbuffer.cpp b/src/mapbuffer.cpp index 5f46649da810..5685f501e614 100644 --- a/src/mapbuffer.cpp +++ b/src/mapbuffer.cpp @@ -11,6 +11,7 @@ #include "cata_utility.h" #include "coordinate_conversions.h" #include "debug.h" +#include "distribution_grid.h" #include "filesystem.h" #include "game.h" #include "game_constants.h" @@ -160,7 +161,7 @@ void mapbuffer::save( bool delete_after_save ) remove_submap( elem ); } - g->m.on_saved(); + get_distribution_grid_tracker().on_saved(); } void mapbuffer::save_quad( const std::string &dirname, const std::string &filename, diff --git a/src/vehicle.cpp b/src/vehicle.cpp index d0c061847a70..2d32c90b7d31 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -26,6 +26,7 @@ #include "colony.h" #include "coordinate_conversions.h" #include "debug.h" +#include "distribution_grid.h" #include "enums.h" #include "event.h" #include "event_bus.h" @@ -4912,12 +4913,17 @@ int vehicle::discharge_battery( int amount, bool recurse ) } } - auto discharge_visitor = []( vehicle * veh, int amount ) { - g->u.add_msg_if_player( m_debug, "CH: %d", amount ); + auto discharge_vehicle = []( vehicle * veh, int amount ) { + g->u.add_msg_if_player( m_debug, "CHv: %d", amount ); return veh->discharge_battery( amount, false ); }; + auto discharge_grid = []( distribution_grid * grid, int amount ) { + g->u.add_msg_if_player( m_debug, "CHg: %d", amount ); + return -grid->mod_resource( -amount ); + }; if( amount > 0 && recurse ) { // need more power! - amount = traverse_vehicle_graph( this, amount, discharge_visitor ); + auto &tracker = get_distribution_grid_tracker(); + amount = tracker.traverse_graph( this, amount, discharge_vehicle, discharge_grid ); } return amount; // non-zero if we weren't able to fulfill demand. diff --git a/tests/crafting_test.cpp b/tests/crafting_test.cpp index 3c8e77ddd278..05cc73e6380a 100644 --- a/tests/crafting_test.cpp +++ b/tests/crafting_test.cpp @@ -555,24 +555,31 @@ TEST_CASE( "total crafting time with or without interruption", "[crafting][time] TEST_CASE( "oven electric grid test", "[crafting][overmap][grids][slow]" ) { + map &m = g->m; constexpr tripoint start_pos = tripoint( 60, 60, 0 ); + const tripoint start_pos_abs = m.getabs( start_pos ); g->u.setpos( start_pos ); clear_avatar(); clear_map(); GIVEN( "player is near an oven on an electric grid with a battery on it" ) { - auto om = overmap_buffer.get_om_global( sm_to_omt_copy( g->m.getabs( g->u.pos() ) ) ); + auto om = overmap_buffer.get_om_global( sm_to_omt_copy( m.getabs( g->u.pos() ) ) ); // TODO: That's a lot of setup, implying barely testable design om.om->set_electric_grid_connections( om.local, {} ); - // Mega ugly - g->load_map( g->m.get_abs_sub() ); - g->m.furn_set( start_pos + point( 10, 0 ), furn_str_id( "f_battery" ) ); - g->m.furn_set( start_pos + point( 1, 0 ), furn_str_id( "f_oven" ) ); - distribution_grid &grid = g->m.distribution_grid_at( start_pos + point( 10, 0 ) ); - REQUIRE( &grid == &g->m.distribution_grid_at( start_pos + point( 1, 0 ) ) ); + m.furn_set( start_pos + point( 10, 0 ), furn_str_id( "f_battery" ) ); + m.furn_set( start_pos + point( 1, 0 ), furn_str_id( "f_oven" ) ); + + distribution_grid_tracker grid_tracker; + grid_tracker.load( rectangle( m.get_abs_sub().xy(), + m.get_abs_sub().xy() + point( m.getmapsize(), m.getmapsize() ) ) ); + distribution_grid &grid = grid_tracker.grid_at( start_pos_abs + point( 10, 0 ) ); + REQUIRE( !grid.empty() ); + // We need the grid to be the same for both the oven and the battery + REQUIRE( &grid == &grid_tracker.grid_at( start_pos_abs + point( 1, 0 ) ) ); WHEN( "the grid is charged with 10 units of power" ) { REQUIRE( grid.get_resource() == 0 ); - grid.mod_resource( 10 ); + int excess = grid.mod_resource( 10 ); + REQUIRE( excess == 0 ); REQUIRE( grid.get_resource() == 10 ); AND_WHEN( "crafting inventory is built" ) { g->u.invalidate_crafting_inventory(); diff --git a/tests/electric_grid_test.cpp b/tests/electric_grid_test.cpp index 8c5463b255b4..e9d3df195a1f 100644 --- a/tests/electric_grid_test.cpp +++ b/tests/electric_grid_test.cpp @@ -11,41 +11,42 @@ #include "overmapbuffer.h" #include "vehicle.h" -TEST_CASE( "connect vehicle", "[grids]" ) +static void connect_grid_vehicle( vehicle &veh, vehicle_connector_tile &connector, + const tripoint &connector_abs_pos ) +{ + const point cable_part_pos = point( 0, 0 ); + vehicle_part source_part( vpart_id( "jumper_cable" ), cable_part_pos, item( "jumper_cable" ) ); + source_part.target.first = connector_abs_pos; + source_part.target.second = connector_abs_pos; + source_part.set_flag( vehicle_part::targets_grid ); + connector.connected_vehicles.emplace_back( veh.global_pos3() ); + int part_index = veh.install_part( cable_part_pos, source_part ); + + REQUIRE( part_index >= 0 ); +} + +TEST_CASE( "drain vehicle with grid", "[grids][vehicle]" ) { clear_map_and_put_player_underground(); const tripoint vehicle_local_pos = tripoint( 10, 10, 0 ); - const tripoint vehicle_abs_pos = g->m.getabs( vehicle_local_pos ); - const tripoint furniture_local_pos = tripoint( 13, 10, 0 ); - const tripoint furniture_abs_pos = g->m.getabs( furniture_local_pos ); + const tripoint connector_local_pos = tripoint( 13, 10, 0 ); + map &m = g->m; + const tripoint connector_abs_pos = m.getabs( connector_local_pos ); - // TODO: Wrap in clear_grids()? - auto om = overmap_buffer.get_om_global( sm_to_omt_copy( g->m.getabs( furniture_local_pos ) ) ); - // TODO: That's a lot of setup, implying barely testable design - om.om->set_electric_grid_connections( om.local, {} ); - // Mega ugly - g->load_map( g->m.get_abs_sub() ); + m.furn_set( connector_local_pos, furn_str_id( "f_cable_connector" ) ); + vehicle *veh = m.add_vehicle( vproto_id( "car" ), vehicle_local_pos, 0, 100, 0, false ); + vehicle_connector_tile *grid_connector = active_tiles::furn_at + ( connector_abs_pos ); - g->m.furn_set( furniture_local_pos, furn_str_id( "f_cable_connector" ) ); - vehicle *veh = g->m.add_vehicle( vproto_id( "car" ), vehicle_local_pos, 0, 100, 0, false ); - vehicle_connector_tile *grid_connector = g->m.active_furniture_at - ( furniture_abs_pos ); + m.save(); REQUIRE( veh ); REQUIRE( grid_connector ); - const point cable_part_pos = point( 0, 0 ); - vehicle_part source_part( vpart_id( "jumper_cable" ), cable_part_pos, item( "jumper_cable" ) ); - source_part.target.first = furniture_abs_pos; - source_part.target.second = furniture_abs_pos; - source_part.set_flag( vehicle_part::targets_grid ); - grid_connector->connected_vehicles.emplace_back( vehicle_abs_pos ); - int part_index = veh->install_part( cable_part_pos, source_part ); + connect_grid_vehicle( *veh, *grid_connector, connector_abs_pos ); - REQUIRE( part_index >= 0 ); - - distribution_grid &grid = g->m.distribution_grid_at( furniture_local_pos ); + distribution_grid &grid = get_distribution_grid_tracker().grid_at( connector_abs_pos ); REQUIRE( !grid.empty() ); CHECK( grid.get_resource() == veh->fuel_left( itype_id( "battery" ), false ) ); @@ -54,3 +55,43 @@ TEST_CASE( "connect vehicle", "[grids]" ) CHECK( grid.get_resource() == vehicle_battery_before - 10 ); CHECK( veh->fuel_left( itype_id( "battery" ), false ) == vehicle_battery_before - 10 ); } + +TEST_CASE( "drain grid with vehicle", "[grids][vehicle]" ) +{ + clear_map_and_put_player_underground(); + const tripoint vehicle_local_pos = tripoint( 10, 10, 0 ); + const tripoint connector_local_pos = tripoint( 13, 10, 0 ); + const tripoint battery_local_pos = tripoint( 14, 10, 0 ); + + map &m = g->m; + const tripoint connector_abs_pos = m.getabs( connector_local_pos ); + const tripoint battery_abs_pos = m.getabs( battery_local_pos ); + + m.furn_set( connector_local_pos, furn_str_id( "f_cable_connector" ) ); + m.furn_set( battery_local_pos, furn_str_id( "f_battery" ) ); + vehicle *veh = m.add_vehicle( vproto_id( "car" ), vehicle_local_pos, 0, 100, 0, false ); + vehicle_connector_tile *grid_connector = + active_tiles::furn_at( connector_abs_pos ); + battery_tile *battery = active_tiles::furn_at( battery_abs_pos ); + + m.save(); + + REQUIRE( veh ); + REQUIRE( grid_connector ); + REQUIRE( battery ); + + connect_grid_vehicle( *veh, *grid_connector, connector_abs_pos ); + + distribution_grid &grid = get_distribution_grid_tracker().grid_at( connector_abs_pos ); + REQUIRE( !grid.empty() ); + REQUIRE( &grid == &get_distribution_grid_tracker().grid_at( battery_abs_pos ) ); + CHECK( grid.get_resource() == veh->fuel_left( itype_id( "battery" ), false ) ); + int excess = grid.mod_resource( 10 ); + CHECK( excess == 0 ); + CHECK( grid.get_resource() == veh->fuel_left( itype_id( "battery" ), false ) + 10 ); + + int missing = veh->discharge_battery( grid.get_resource() - 5 ); + CHECK( missing == 0 ); + CHECK( grid.get_resource() == 5 ); + CHECK( veh->fuel_left( itype_id( "battery" ), false ) == 0 ); +} diff --git a/tests/test_main.cpp b/tests/test_main.cpp index b1b2dc92ba48..313c022437a5 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -33,6 +33,7 @@ #include "catch/catch.hpp" #include "color.h" #include "debug.h" +#include "distribution_grid.h" #include "filesystem.h" #include "game.h" #include "loading_ui.h" @@ -151,6 +152,7 @@ static void init_global_game_state( const std::vector &mods, overmap_buffer.create_custom_overmap( point_zero, empty_specials ); g->m.load( tripoint( g->get_levx(), g->get_levy(), g->get_levz() ), false ); + get_distribution_grid_tracker().load( g->m ); g->weather.update_weather(); } From 783d0d991dffa87ba8aaef391063cdb735eb72ce Mon Sep 17 00:00:00 2001 From: Coolthulhu Date: Sun, 31 Jan 2021 12:18:46 +0100 Subject: [PATCH 06/12] Passing tests! --- src/distribution_grid.cpp | 124 +----------------------------- src/distribution_grid.h | 20 +++-- src/vehicle.cpp | 154 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 162 insertions(+), 136 deletions(-) diff --git a/src/distribution_grid.cpp b/src/distribution_grid.cpp index 542f389103fe..1052cce80254 100644 --- a/src/distribution_grid.cpp +++ b/src/distribution_grid.cpp @@ -156,6 +156,7 @@ distribution_grid &distribution_grid_tracker::grid_at( const tripoint &p ) // TODO: empty mapbuffer for this case static distribution_grid empty_grid( {}, MAPBUFFER ); tripoint sm_pos = ms_to_sm_copy( p ); + // TODO: This should load a grid, not just expect to find loaded ones! auto iter = parent_distribution_grids.find( sm_pos ); if( iter != parent_distribution_grids.end() ) { return *iter->second; @@ -194,126 +195,3 @@ void distribution_grid_tracker::load( const map &m ) load( rectangle( m.get_abs_sub().xy(), m.get_abs_sub().xy() + point( m.getmapsize(), m.getmapsize() ) ) ); } - -#include "game.h" -#include "vehicle.h" -#include "avatar.h" - -template -int traverse_graph( StartPoint *start, int amount, - VehFunc veh_action, GridFunc grid_action ) -{ - - struct vehicle_or_grid { - enum class type_t { - vehicle, - grid - } type; - - vehicle *const veh; - distribution_grid *const grid; - - vehicle_or_grid( vehicle *veh ) - : type( type_t::vehicle ) - , veh( veh ) - {} - - vehicle_or_grid( distribution_grid *grid ) - : type( type_t::grid ) - , grid( grid ) - {} - - bool operator==( const vehicle *veh ) const { - return this->veh = veh; - } - - bool operator==( const distribution_grid *grid ) const { - return this->grid = grid; - } - }; - - std::queue connected_elements; - std::set visited_elements; - connected_elements.emplace( start ); - - auto process_vehicle = [&]( vehicle * veh ) { - // Add this connected vehicle to the queue of vehicles to search next, - // but only if we haven't seen this one before. - if( visited_elements.count( veh ) < 1 ) { - connected_elements.emplace( veh ); - - amount = veh_action( veh, amount ); - g->u.add_msg_if_player( m_debug, "After remote veh %p, %d power", static_cast( veh ), - amount ); - - return amount < 1; - } - }; - - auto process_grid = [&]( distribution_grid * grid ) { - if( visited_elements.count( grid ) < 1 ) { - connected_elements.emplace( grid ); - - amount = grid_action( grid, amount ); - g->u.add_msg_if_player( m_debug, "After remote grid %p, %d power", static_cast( grid ), - amount ); - - return amount < 1; - } - }; - - auto grid_tracker = get_distribution_grid_tracker(); - while( amount > 0 && !connected_elements.empty() ) { - vehicle_or_grid current = connected_elements.front(); - - visited_elements.insert( current ); - connected_elements.pop(); - - if( current.type == vehicle_or_grid::type_t::vehicle ) { - auto *current_veh = current.veh; - for( auto &p : current_veh->loose_parts ) { - if( !current_veh->part_info( p ).has_flag( "POWER_TRANSFER" ) ) { - continue; // ignore loose parts that aren't power transfer cables - } - - const tripoint target_pos = current_veh->parts[p].target.second; - if( current_veh->parts[p].has_flag( vehicle_part::targets_grid ) ) { - auto target_grid = grid_tracker.grid_at( target_pos ); - if( target_grid && process_grid( &target_grid ) ) { - break; - } - } else { - - auto target_veh = vehicle::find_vehicle( target_pos ); - // Skip visited or unloadable vehicles - if( target_veh != nullptr && visited_elements.count( target_veh ) == 0 && - process_vehicle( target_veh ) ) { - // No more charge to donate away. - break; - } - } - } - } else { - // Grids can only be connected to vehicles at the moment - auto *current_grid = current.grid; - for( auto &pr : current_grid->contents ) { - const vehicle_connector_tile *connector = active_tiles::furn_at - ( pr.second.absolute ); - if( connector == nullptr ) { - continue; - } - - for( const tripoint &target_pos : connector->connected_vehicles ) { - auto target_veh = vehicle::find_vehicle( target_pos ); - // Skip visited or unloadable vehicles - if( target_veh != nullptr && visited_elements.count( target_veh ) == 0 && - process_vehicle( target_veh ) ) { - // No more charge to donate away. - break; - } - } - } - } - } - return amount; -} diff --git a/src/distribution_grid.h b/src/distribution_grid.h index 84cb2f93b867..cc2829a69b7e 100644 --- a/src/distribution_grid.h +++ b/src/distribution_grid.h @@ -30,11 +30,14 @@ class distribution_grid { private: friend class distribution_grid_tracker; + // TODO: Remove that public + public: /** * Map of submap coords to points on this submap * that contain an active tile. */ std::map> contents; + private: std::vector submap_coords; mapbuffer &mb; @@ -98,16 +101,19 @@ class distribution_grid_tracker */ void on_changed( const tripoint &p ); void on_saved(); - - /** - * Traverses the graph of connected vehicles and grids. - */ - template - int traverse_graph( StartPoint *start, int amount, - VehFunc veh_action, GridFunc grid_action ); }; +namespace distribution_graph +{ + +/** +* Traverses the graph of connected vehicles and grids. +*/ +template +int traverse( StartPoint *start, int amount, + VehFunc veh_action, GridFunc grid_action ); +} /** * Returns distribution grid tracker that is a part of the global game *g. @ref game diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 2d32c90b7d31..4eb3f638c38a 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -4849,6 +4849,143 @@ int vehicle::traverse_vehicle_graph( Vehicle *start_veh, int amount, Func action return amount; } +// TODO: It looks out of place in vehicle.cpp +namespace distribution_graph +{ + +template +int traverse( StartPoint *start, int amount, + VehFunc veh_action, GridFunc grid_action ) +{ + + struct vehicle_or_grid { + enum class type_t : char { + vehicle, + grid + } type; + + vehicle *veh = nullptr; + distribution_grid *grid = nullptr; + + vehicle_or_grid( vehicle *veh ) + : type( type_t::vehicle ) + , veh( veh ) + {} + + vehicle_or_grid( distribution_grid *grid ) + : type( type_t::grid ) + , grid( grid ) + {} + + bool operator==( const vehicle_or_grid &other ) const { + return veh == other.veh && grid == other.grid; + } + + bool operator==( const vehicle *veh ) const { + return this->veh == veh; + } + + bool operator==( const distribution_grid *grid ) const { + return this->grid == grid; + } + }; + + struct hash { + const std::hash char_hash = std::hash(); + const std::hash ptr_hash = std::hash(); + auto operator()( const vehicle_or_grid &elem ) const { + return char_hash( static_cast( elem.type ) ) ^ + ( ptr_hash( reinterpret_cast( elem.veh ) | reinterpret_cast( elem.grid ) ) ); + } + }; + + std::queue connected_elements; + std::unordered_set visited_elements; + connected_elements.emplace( start ); + auto &grid_tracker = get_distribution_grid_tracker(); + + auto process_vehicle = [&]( const tripoint & target_pos ) { + auto *veh = vehicle::find_vehicle( target_pos ); + // Add this connected vehicle to the queue of vehicles to search next, + // but only if we haven't seen this one before. + if( veh != nullptr && visited_elements.count( veh ) < 1 ) { + connected_elements.emplace( veh ); + + amount = veh_action( veh, amount ); + g->u.add_msg_if_player( m_debug, "After remote veh %p, %d power", static_cast( veh ), + amount ); + + return amount < 1; + } + + return false; + }; + + auto process_grid = [&]( const tripoint & target_pos ) { + auto *grid = &grid_tracker.grid_at( target_pos ); + if( *grid && visited_elements.count( grid ) < 1 ) { + connected_elements.emplace( grid ); + + amount = grid_action( grid, amount ); + g->u.add_msg_if_player( m_debug, "After remote grid %p, %d power", static_cast( grid ), + amount ); + + return amount < 1; + } + + return false; + }; + + while( amount > 0 && !connected_elements.empty() ) { + vehicle_or_grid current = connected_elements.front(); + + visited_elements.insert( current ); + connected_elements.pop(); + + if( current.type == vehicle_or_grid::type_t::vehicle ) { + auto ¤t_veh = *current.veh; + for( auto &p : current_veh.loose_parts ) { + if( !current_veh.part_info( p ).has_flag( "POWER_TRANSFER" ) ) { + // Ignore loose parts that aren't power transfer cables + continue; + } + + const tripoint target_pos = current_veh.parts[p].target.second; + if( current_veh.parts[p].has_flag( vehicle_part::targets_grid ) ) { + if( process_grid( target_pos ) ) { + break; + } + } else { + if( process_vehicle( target_pos ) ) { + break; + } + } + } + } else { + // Grids can only be connected to vehicles at the moment + auto ¤t_grid = *current.grid; + for( auto &pr : current_grid.contents ) { + for( const tile_location &loc : pr.second ) { + const vehicle_connector_tile *connector = active_tiles::furn_at + ( loc.absolute ); + if( connector == nullptr ) { + continue; + } + + for( const tripoint &target_pos : connector->connected_vehicles ) { + if( process_vehicle( target_pos ) ) { + break; + } + } + } + } + } + } + return amount; +} + +} // namespace distribution_graph + int vehicle::charge_battery( int amount, bool include_other_vehicles ) { // Key parts by percentage charge level. @@ -4876,15 +5013,21 @@ int vehicle::charge_battery( int amount, bool include_other_vehicles ) } } - auto charge_visitor = []( vehicle * veh, int amount ) { - g->u.add_msg_if_player( m_debug, "CH: %d", amount ); + auto charge_veh = []( vehicle * veh, int amount ) { + g->u.add_msg_if_player( m_debug, "CHv: %d", amount ); return veh->charge_battery( amount, false ); }; + auto charge_grid = []( distribution_grid * grid, int amount ) { + g->u.add_msg_if_player( m_debug, "CHg: %d", amount ); + return grid->mod_resource( amount ); + }; - if( amount > 0 && include_other_vehicles ) { // still a bit of charge we could send out... - amount = traverse_vehicle_graph( this, amount, charge_visitor ); + if( amount > 0 && include_other_vehicles ) { + // still a bit of charge we could send out... + amount = distribution_graph::traverse( this, amount, charge_veh, charge_grid ); } + return amount; } @@ -4922,8 +5065,7 @@ int vehicle::discharge_battery( int amount, bool recurse ) return -grid->mod_resource( -amount ); }; if( amount > 0 && recurse ) { // need more power! - auto &tracker = get_distribution_grid_tracker(); - amount = tracker.traverse_graph( this, amount, discharge_vehicle, discharge_grid ); + amount = distribution_graph::traverse( this, amount, discharge_vehicle, discharge_grid ); } return amount; // non-zero if we weren't able to fulfill demand. From a534af97e8b709006166ce914208bd74d819761a Mon Sep 17 00:00:00 2001 From: Coolthulhu Date: Sun, 31 Jan 2021 14:52:27 +0100 Subject: [PATCH 07/12] Fix infinite loops --- src/active_tile_data.cpp | 41 ------------ src/active_tile_data.h | 15 +---- src/distribution_grid.cpp | 72 ++++++++++++++++----- src/distribution_grid.h | 4 +- src/vehicle.cpp | 120 +++++++++++++---------------------- tests/electric_grid_test.cpp | 9 ++- 6 files changed, 112 insertions(+), 149 deletions(-) diff --git a/src/active_tile_data.cpp b/src/active_tile_data.cpp index b7a7dc37a373..bdee50f7e9f3 100644 --- a/src/active_tile_data.cpp +++ b/src/active_tile_data.cpp @@ -268,47 +268,6 @@ void vehicle_connector_tile::load( JsonObject &jo ) jo.read( "connected_vehicles", connected_vehicles ); } -int vehicle_connector_tile::get_resource() const -{ - int resource_sum = 0; - for( const tripoint &veh_abs : connected_vehicles ) { - vehicle *veh = vehicle::find_vehicle( veh_abs ); - if( veh == nullptr ) { - // TODO - debugmsg( "lost vehicle at %d,%d,%d", veh_abs.x, veh_abs.y, veh_abs.z ); - continue; - } - - resource_sum += veh->fuel_left( "battery", false ); - } - - return resource_sum; -} - -int vehicle_connector_tile::mod_resource( int amt ) -{ - for( const tripoint &veh_abs : connected_vehicles ) { - vehicle *veh = vehicle::find_vehicle( veh_abs ); - if( veh == nullptr ) { - // TODO - debugmsg( "lost vehicle at %d,%d,%d", veh_abs.x, veh_abs.y, veh_abs.z ); - continue; - } - - // TODO: Handle cabled up vehicles without including any of them more than once - if( amt > 0 ) { - amt = veh->charge_battery( amt, false ); - } else { - amt = -veh->discharge_battery( -amt, false ); - } - if( amt == 0 ) { - return 0; - } - } - - return amt; -} - static std::map> build_type_map() { std::map> type_map; diff --git a/src/active_tile_data.h b/src/active_tile_data.h index 9f4688fe8c1e..abb6738e394e 100644 --- a/src/active_tile_data.h +++ b/src/active_tile_data.h @@ -41,14 +41,6 @@ class active_tile_data void serialize( JsonOut &jsout ) const; void deserialize( JsonIn &jsin ); - virtual int get_resource() const { - return 0; - } - - virtual int mod_resource( int amt ) { - return amt; - } - virtual ~active_tile_data(); virtual active_tile_data *clone() const = 0; virtual const std::string &get_type() const = 0; @@ -83,8 +75,8 @@ class battery_tile : public active_tile_data void store( JsonOut &jsout ) const override; void load( JsonObject &jo ) override; - int get_resource() const override; - int mod_resource( int amt ) override; + int get_resource() const; + int mod_resource( int amt ); }; class charger_tile : public active_tile_data @@ -111,9 +103,6 @@ class vehicle_connector_tile : public active_tile_data const std::string &get_type() const override; void store( JsonOut &jsout ) const override; void load( JsonObject &jo ) override; - - int get_resource() const override; - int mod_resource( int amt ) override; }; // TODO: Better place for this diff --git a/src/distribution_grid.cpp b/src/distribution_grid.cpp index 1052cce80254..a71f6a15d750 100644 --- a/src/distribution_grid.cpp +++ b/src/distribution_grid.cpp @@ -35,7 +35,7 @@ bool distribution_grid::empty() const distribution_grid::operator bool() const { - return !empty(); + return !empty() && !submap_coords.empty(); } void distribution_grid::update( time_point to ) @@ -59,19 +59,46 @@ void distribution_grid::update( time_point to ) } } -int distribution_grid::mod_resource( int amt ) +#include "vehicle.h" +int distribution_grid::mod_resource( int amt, bool recurse ) { + std::vector connected_vehicles; for( const std::pair> &c : contents ) { - if( amt == 0 ) { - return 0; - } - submap *sm = mb.lookup_submap( c.first ); - if( sm == nullptr ) { - continue; + for( const tile_location &loc : c.second ) { + battery_tile *battery = active_tiles::furn_at( loc.absolute ); + if( battery != nullptr ) { + amt = battery->mod_resource( amt ); + if( amt == 0 ) { + return 0; + } + continue; + } + + if( !recurse ) { + continue; + } + + vehicle_connector_tile *connector = active_tiles::furn_at( loc.absolute ); + if( connector != nullptr ) { + for( const tripoint &veh_abs : connector->connected_vehicles ) { + vehicle *veh = vehicle::find_vehicle( veh_abs ); + if( veh == nullptr ) { + // TODO: Disconnect + debugmsg( "lost vehicle at %d,%d,%d", veh_abs.x, veh_abs.y, veh_abs.z ); + continue; + } + connected_vehicles.push_back( veh ); + } + } } + } - for( const tile_location &loc : c.second ) { - amt = sm->active_furniture[loc.on_submap]->mod_resource( amt ); + // TODO: Giga ugly. We only charge the first vehicle to get it to use its recursive graph traversal because it's inaccessible from here due to being a template method + if( !connected_vehicles.empty() ) { + if( amt > 0 ) { + amt = connected_vehicles.front()->charge_battery( amt, true ); + } else { + amt = -connected_vehicles.front()->discharge_battery( -amt, true ); } } @@ -82,13 +109,26 @@ int distribution_grid::get_resource() const { int res = 0; for( const std::pair> &c : contents ) { - submap *sm = mb.lookup_submap( c.first ); - if( sm == nullptr ) { - continue; - } - for( const tile_location &loc : c.second ) { - res += sm->active_furniture[loc.on_submap]->get_resource(); + battery_tile *battery = active_tiles::furn_at( loc.absolute ); + if( battery != nullptr ) { + res += battery->stored; + continue; + } + + vehicle_connector_tile *connector = active_tiles::furn_at( loc.absolute ); + if( connector != nullptr ) { + for( const tripoint &veh_abs : connector->connected_vehicles ) { + vehicle *veh = vehicle::find_vehicle( veh_abs ); + if( veh == nullptr ) { + // TODO: Disconnect + debugmsg( "lost vehicle at %d,%d,%d", veh_abs.x, veh_abs.y, veh_abs.z ); + continue; + } + + res += veh->fuel_left( "battery", false ); + } + } } } diff --git a/src/distribution_grid.h b/src/distribution_grid.h index cc2829a69b7e..0d749b821b66 100644 --- a/src/distribution_grid.h +++ b/src/distribution_grid.h @@ -47,7 +47,7 @@ class distribution_grid bool empty() const; explicit operator bool() const; void update( time_point to ); - int mod_resource( int amt ); + int mod_resource( int amt, bool recurse = true ); int get_resource() const; }; @@ -113,6 +113,8 @@ template int traverse( StartPoint *start, int amount, VehFunc veh_action, GridFunc grid_action ); + + } /** diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 4eb3f638c38a..24306704b4e4 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -4806,101 +4806,67 @@ void vehicle::enumerate_vehicles( std::map &connected_vehicles, template int vehicle::traverse_vehicle_graph( Vehicle *start_veh, int amount, Func action ) { - // Breadth-first search! Initialize the queue with a pointer to ourselves and go! - std::queue connected_vehs; - std::set visited_vehs; - connected_vehs.push( start_veh ); - - while( amount > 0 && !connected_vehs.empty() ) { - Vehicle *current_veh = connected_vehs.front(); + const auto do_nothing = []( const distribution_grid *, int amt ) { + return amt; + }; + return distribution_graph::traverse( start_veh, amount, action, do_nothing ); +} - visited_vehs.insert( current_veh ); - connected_vehs.pop(); +// TODO: It looks out of place in vehicle.cpp +namespace distribution_graph +{ - g->u.add_msg_if_player( m_debug, "Traversing graph with %d power", amount ); +template ::type, + typename Grid = typename std::conditional::type> +struct vehicle_or_grid { + enum class type_t : char { + vehicle, + grid + } type; - for( auto &p : current_veh->loose_parts ) { - if( !current_veh->part_info( p ).has_flag( "POWER_TRANSFER" ) ) { - continue; // ignore loose parts that aren't power transfer cables - } + Vehicle *veh = nullptr; + Grid *grid = nullptr; - auto target_veh = vehicle::find_vehicle( current_veh->parts[p].target.second ); - if( target_veh == nullptr || visited_vehs.count( target_veh ) > 0 ) { - // Either no destination here (that vehicle's rolled away or off-map) or - // we've already looked at that vehicle. - continue; - } + vehicle_or_grid( Vehicle *veh ) + : type( type_t::vehicle ) + , veh( veh ) + {} - // Add this connected vehicle to the queue of vehicles to search next, - // but only if we haven't seen this one before. - if( visited_vehs.count( target_veh ) < 1 ) { - connected_vehs.push( target_veh ); + vehicle_or_grid( Grid *grid ) + : type( type_t::grid ) + , grid( grid ) + {} - amount = action( target_veh, amount ); - g->u.add_msg_if_player( m_debug, "After remote %p, %d power", static_cast( target_veh ), - amount ); + bool operator==( const vehicle_or_grid &other ) const { + return veh == other.veh && grid == other.grid; + } - if( amount < 1 ) { - break; // No more charge to donate away. - } - } - } + bool operator==( const vehicle *veh ) const { + return this->veh == veh; } - return amount; -} -// TODO: It looks out of place in vehicle.cpp -namespace distribution_graph -{ + bool operator==( const distribution_grid *grid ) const { + return this->grid == grid; + } +}; template int traverse( StartPoint *start, int amount, VehFunc veh_action, GridFunc grid_action ) { - - struct vehicle_or_grid { - enum class type_t : char { - vehicle, - grid - } type; - - vehicle *veh = nullptr; - distribution_grid *grid = nullptr; - - vehicle_or_grid( vehicle *veh ) - : type( type_t::vehicle ) - , veh( veh ) - {} - - vehicle_or_grid( distribution_grid *grid ) - : type( type_t::grid ) - , grid( grid ) - {} - - bool operator==( const vehicle_or_grid &other ) const { - return veh == other.veh && grid == other.grid; - } - - bool operator==( const vehicle *veh ) const { - return this->veh == veh; - } - - bool operator==( const distribution_grid *grid ) const { - return this->grid == grid; - } - }; - + constexpr bool IsConst = std::is_const::value; struct hash { const std::hash char_hash = std::hash(); const std::hash ptr_hash = std::hash(); - auto operator()( const vehicle_or_grid &elem ) const { + auto operator()( const vehicle_or_grid &elem ) const { return char_hash( static_cast( elem.type ) ) ^ ( ptr_hash( reinterpret_cast( elem.veh ) | reinterpret_cast( elem.grid ) ) ); } }; - std::queue connected_elements; - std::unordered_set visited_elements; + std::queue> connected_elements; + std::unordered_set, hash> visited_elements; connected_elements.emplace( start ); auto &grid_tracker = get_distribution_grid_tracker(); @@ -4937,12 +4903,12 @@ int traverse( StartPoint *start, int amount, }; while( amount > 0 && !connected_elements.empty() ) { - vehicle_or_grid current = connected_elements.front(); + auto current = connected_elements.front(); visited_elements.insert( current ); connected_elements.pop(); - if( current.type == vehicle_or_grid::type_t::vehicle ) { + if( current.type == vehicle_or_grid::type_t::vehicle ) { auto ¤t_veh = *current.veh; for( auto &p : current_veh.loose_parts ) { if( !current_veh.part_info( p ).has_flag( "POWER_TRANSFER" ) ) { @@ -5019,7 +4985,7 @@ int vehicle::charge_battery( int amount, bool include_other_vehicles ) }; auto charge_grid = []( distribution_grid * grid, int amount ) { g->u.add_msg_if_player( m_debug, "CHg: %d", amount ); - return grid->mod_resource( amount ); + return grid->mod_resource( amount, false ); }; if( amount > 0 && include_other_vehicles ) { @@ -5062,7 +5028,7 @@ int vehicle::discharge_battery( int amount, bool recurse ) }; auto discharge_grid = []( distribution_grid * grid, int amount ) { g->u.add_msg_if_player( m_debug, "CHg: %d", amount ); - return -grid->mod_resource( -amount ); + return -grid->mod_resource( -amount, false ); }; if( amount > 0 && recurse ) { // need more power! amount = distribution_graph::traverse( this, amount, discharge_vehicle, discharge_grid ); diff --git a/tests/electric_grid_test.cpp b/tests/electric_grid_test.cpp index e9d3df195a1f..a4805045d803 100644 --- a/tests/electric_grid_test.cpp +++ b/tests/electric_grid_test.cpp @@ -51,9 +51,13 @@ TEST_CASE( "drain vehicle with grid", "[grids][vehicle]" ) CHECK( grid.get_resource() == veh->fuel_left( itype_id( "battery" ), false ) ); int vehicle_battery_before = veh->fuel_left( itype_id( "battery" ), false ); - CHECK( grid.mod_resource( -10 ) == 0 ); + int missing = grid.mod_resource( -10 ); + CHECK( missing == 0 ); CHECK( grid.get_resource() == vehicle_battery_before - 10 ); CHECK( veh->fuel_left( itype_id( "battery" ), false ) == vehicle_battery_before - 10 ); + + missing = grid.mod_resource( -veh->fuel_left( itype_id( "battery" ), false ) - 10 ); + CHECK( missing == -10 ); } TEST_CASE( "drain grid with vehicle", "[grids][vehicle]" ) @@ -94,4 +98,7 @@ TEST_CASE( "drain grid with vehicle", "[grids][vehicle]" ) CHECK( missing == 0 ); CHECK( grid.get_resource() == 5 ); CHECK( veh->fuel_left( itype_id( "battery" ), false ) == 0 ); + + missing = veh->discharge_battery( 10 ); + CHECK( missing == 5 ); } From 3db42c9a5fd54982919f1e72863b070a92406bc5 Mon Sep 17 00:00:00 2001 From: Coolthulhu Date: Sun, 31 Jan 2021 15:25:07 +0100 Subject: [PATCH 08/12] More comprehensive tests --- tests/crafting_test.cpp | 9 +-- tests/electric_grid_test.cpp | 136 ++++++++++++++++++----------------- 2 files changed, 73 insertions(+), 72 deletions(-) diff --git a/tests/crafting_test.cpp b/tests/crafting_test.cpp index 05cc73e6380a..f5a6e085d623 100644 --- a/tests/crafting_test.cpp +++ b/tests/crafting_test.cpp @@ -562,24 +562,21 @@ TEST_CASE( "oven electric grid test", "[crafting][overmap][grids][slow]" ) clear_avatar(); clear_map(); GIVEN( "player is near an oven on an electric grid with a battery on it" ) { + // TODO: clear_grids() auto om = overmap_buffer.get_om_global( sm_to_omt_copy( m.getabs( g->u.pos() ) ) ); - // TODO: That's a lot of setup, implying barely testable design om.om->set_electric_grid_connections( om.local, {} ); m.furn_set( start_pos + point( 10, 0 ), furn_str_id( "f_battery" ) ); m.furn_set( start_pos + point( 1, 0 ), furn_str_id( "f_oven" ) ); distribution_grid_tracker grid_tracker; - grid_tracker.load( rectangle( m.get_abs_sub().xy(), - m.get_abs_sub().xy() + point( m.getmapsize(), m.getmapsize() ) ) ); + grid_tracker.load( m ); distribution_grid &grid = grid_tracker.grid_at( start_pos_abs + point( 10, 0 ) ); REQUIRE( !grid.empty() ); // We need the grid to be the same for both the oven and the battery REQUIRE( &grid == &grid_tracker.grid_at( start_pos_abs + point( 1, 0 ) ) ); WHEN( "the grid is charged with 10 units of power" ) { - REQUIRE( grid.get_resource() == 0 ); - int excess = grid.mod_resource( 10 ); - REQUIRE( excess == 0 ); + grid.mod_resource( 10 ); REQUIRE( grid.get_resource() == 10 ); AND_WHEN( "crafting inventory is built" ) { g->u.invalidate_crafting_inventory(); diff --git a/tests/electric_grid_test.cpp b/tests/electric_grid_test.cpp index a4805045d803..f8b562a38c75 100644 --- a/tests/electric_grid_test.cpp +++ b/tests/electric_grid_test.cpp @@ -11,6 +11,8 @@ #include "overmapbuffer.h" #include "vehicle.h" +static itype_id itype_battery = itype_id( "battery" ); + static void connect_grid_vehicle( vehicle &veh, vehicle_connector_tile &connector, const tripoint &connector_abs_pos ) { @@ -25,42 +27,7 @@ static void connect_grid_vehicle( vehicle &veh, vehicle_connector_tile &connecto REQUIRE( part_index >= 0 ); } -TEST_CASE( "drain vehicle with grid", "[grids][vehicle]" ) -{ - clear_map_and_put_player_underground(); - const tripoint vehicle_local_pos = tripoint( 10, 10, 0 ); - const tripoint connector_local_pos = tripoint( 13, 10, 0 ); - - map &m = g->m; - const tripoint connector_abs_pos = m.getabs( connector_local_pos ); - - m.furn_set( connector_local_pos, furn_str_id( "f_cable_connector" ) ); - vehicle *veh = m.add_vehicle( vproto_id( "car" ), vehicle_local_pos, 0, 100, 0, false ); - vehicle_connector_tile *grid_connector = active_tiles::furn_at - ( connector_abs_pos ); - - m.save(); - - REQUIRE( veh ); - REQUIRE( grid_connector ); - - connect_grid_vehicle( *veh, *grid_connector, connector_abs_pos ); - - distribution_grid &grid = get_distribution_grid_tracker().grid_at( connector_abs_pos ); - REQUIRE( !grid.empty() ); - CHECK( grid.get_resource() == veh->fuel_left( itype_id( "battery" ), false ) ); - - int vehicle_battery_before = veh->fuel_left( itype_id( "battery" ), false ); - int missing = grid.mod_resource( -10 ); - CHECK( missing == 0 ); - CHECK( grid.get_resource() == vehicle_battery_before - 10 ); - CHECK( veh->fuel_left( itype_id( "battery" ), false ) == vehicle_battery_before - 10 ); - - missing = grid.mod_resource( -veh->fuel_left( itype_id( "battery" ), false ) - 10 ); - CHECK( missing == -10 ); -} - -TEST_CASE( "drain grid with vehicle", "[grids][vehicle]" ) +TEST_CASE( "grid_and_vehicle_in_bubble", "[grids][vehicle]" ) { clear_map_and_put_player_underground(); const tripoint vehicle_local_pos = tripoint( 10, 10, 0 ); @@ -71,34 +38,71 @@ TEST_CASE( "drain grid with vehicle", "[grids][vehicle]" ) const tripoint connector_abs_pos = m.getabs( connector_local_pos ); const tripoint battery_abs_pos = m.getabs( battery_local_pos ); - m.furn_set( connector_local_pos, furn_str_id( "f_cable_connector" ) ); - m.furn_set( battery_local_pos, furn_str_id( "f_battery" ) ); - vehicle *veh = m.add_vehicle( vproto_id( "car" ), vehicle_local_pos, 0, 100, 0, false ); - vehicle_connector_tile *grid_connector = - active_tiles::furn_at( connector_abs_pos ); - battery_tile *battery = active_tiles::furn_at( battery_abs_pos ); - - m.save(); - - REQUIRE( veh ); - REQUIRE( grid_connector ); - REQUIRE( battery ); - - connect_grid_vehicle( *veh, *grid_connector, connector_abs_pos ); - - distribution_grid &grid = get_distribution_grid_tracker().grid_at( connector_abs_pos ); - REQUIRE( !grid.empty() ); - REQUIRE( &grid == &get_distribution_grid_tracker().grid_at( battery_abs_pos ) ); - CHECK( grid.get_resource() == veh->fuel_left( itype_id( "battery" ), false ) ); - int excess = grid.mod_resource( 10 ); - CHECK( excess == 0 ); - CHECK( grid.get_resource() == veh->fuel_left( itype_id( "battery" ), false ) + 10 ); - - int missing = veh->discharge_battery( grid.get_resource() - 5 ); - CHECK( missing == 0 ); - CHECK( grid.get_resource() == 5 ); - CHECK( veh->fuel_left( itype_id( "battery" ), false ) == 0 ); - - missing = veh->discharge_battery( 10 ); - CHECK( missing == 5 ); + GIVEN( "vehicle and battery on one grid" ) { + m.furn_set( connector_local_pos, furn_str_id( "f_cable_connector" ) ); + m.furn_set( battery_local_pos, furn_str_id( "f_battery" ) ); + vehicle *veh = m.add_vehicle( vproto_id( "car" ), vehicle_local_pos, 0, 0, 0, false ); + vehicle_connector_tile *grid_connector = + active_tiles::furn_at( connector_abs_pos ); + battery_tile *battery = active_tiles::furn_at( battery_abs_pos ); + + REQUIRE( veh ); + REQUIRE( grid_connector ); + REQUIRE( battery ); + + connect_grid_vehicle( *veh, *grid_connector, connector_abs_pos ); + + distribution_grid &grid = get_distribution_grid_tracker().grid_at( connector_abs_pos ); + REQUIRE( !grid.empty() ); + REQUIRE( &grid == &get_distribution_grid_tracker().grid_at( battery_abs_pos ) ); + WHEN( "the vehicle is fully charged" ) { + veh->charge_battery( veh->fuel_capacity( itype_battery ), false ); + REQUIRE( veh->fuel_left( itype_battery, false ) == + veh->fuel_capacity( itype_battery ) ); + AND_WHEN( "the grid is discharged without energy in battery" ) { + int deficit = veh->discharge_battery( grid.get_resource() - 10 ); + CHECK( deficit == 0 ); + THEN( "the power is drained from vehicle" ) { + CHECK( grid.get_resource() == 10 ); + CHECK( veh->fuel_left( itype_battery, false ) == 10 ); + } + } + + AND_WHEN( "the vehicle is charged despite being full" ) { + int excess = veh->charge_battery( 10 ); + THEN( "the grid contains the added energy" ) { + CHECK( excess == 0 ); + CHECK( grid.get_resource() == veh->fuel_left( itype_battery, false ) + 10 ); + AND_THEN( "the added energy is in the battery" ) { + CHECK( battery->get_resource() == 10 ); + } + } + } + } + + WHEN( "the battery is fully charged" ) { + int excess = battery->mod_resource( battery->max_stored ); + REQUIRE( excess == 0 ); + AND_WHEN( "the vehicle is discharged despite being empty" ) { + int deficit = veh->discharge_battery( 10, true ); + THEN( "the grid provides the needed power" ) { + CHECK( deficit == 0 ); + AND_THEN( "this power comes from the battery" ) { + CHECK( battery->get_resource() == battery->max_stored - 10 ); + } + } + } + + AND_WHEN( "the grid is charged some more" ) { + int excess = grid.mod_resource( 10 ); + THEN( "the grid contains the added energy" ) { + CHECK( excess == 0 ); + CHECK( grid.get_resource() == battery->max_stored + 10 ); + AND_THEN( "the added energy is in the vehicle" ) { + CHECK( veh->fuel_left( itype_battery, false ) == 10 ); + } + } + } + } + } } From debe5e1bf052f9896e78d80e956a53551abe4247 Mon Sep 17 00:00:00 2001 From: Coolthulhu Date: Tue, 2 Feb 2021 18:47:07 +0100 Subject: [PATCH 09/12] Passing useful tests --- src/distribution_grid.cpp | 21 ++-- src/distribution_grid.h | 2 +- src/iuse.cpp | 27 +++--- src/vehicle.cpp | 11 ++- tests/electric_grid_test.cpp | 181 +++++++++++++++++++++-------------- 5 files changed, 145 insertions(+), 97 deletions(-) diff --git a/src/distribution_grid.cpp b/src/distribution_grid.cpp index a71f6a15d750..6a738e35ea2c 100644 --- a/src/distribution_grid.cpp +++ b/src/distribution_grid.cpp @@ -59,7 +59,9 @@ void distribution_grid::update( time_point to ) } } +// TODO: Shouldn't be here #include "vehicle.h" +static itype_id itype_battery( "battery" ); int distribution_grid::mod_resource( int amt, bool recurse ) { std::vector connected_vehicles; @@ -126,7 +128,7 @@ int distribution_grid::get_resource() const continue; } - res += veh->fuel_left( "battery", false ); + res += veh->fuel_left( itype_battery, false ); } } } @@ -144,8 +146,9 @@ distribution_grid_tracker::distribution_grid_tracker( mapbuffer &buffer ) { } -void distribution_grid_tracker::make_distribution_grid_at( const tripoint &sm_pos ) +distribution_grid &distribution_grid_tracker::make_distribution_grid_at( const tripoint &sm_pos ) { + static distribution_grid empty_grid( {}, MAPBUFFER ); const std::set overmap_positions = overmap_buffer.electric_grid_at( sm_to_omt_copy( sm_pos ) ); std::vector submap_positions; @@ -161,11 +164,14 @@ void distribution_grid_tracker::make_distribution_grid_at( const tripoint &sm_po for( const tripoint &smp : submap_positions ) { parent_distribution_grids.erase( smp ); } - return; + + return empty_grid; } for( const tripoint &smp : submap_positions ) { parent_distribution_grids[smp] = dist_grid; } + + return *dist_grid; } void distribution_grid_tracker::on_saved() @@ -184,7 +190,8 @@ void distribution_grid_tracker::on_saved() void distribution_grid_tracker::on_changed( const tripoint &p ) { tripoint sm_pos = ms_to_sm_copy( p ); - if( bounds.contains_half_open( sm_pos.xy() ) ) { + // TODO: If not in bounds, just drop the grid, rebuild lazily + if( parent_distribution_grids.count( sm_pos ) > 0 ) { // TODO: Don't rebuild, update make_distribution_grid_at( sm_pos ); } @@ -193,16 +200,14 @@ void distribution_grid_tracker::on_changed( const tripoint &p ) distribution_grid &distribution_grid_tracker::grid_at( const tripoint &p ) { - // TODO: empty mapbuffer for this case - static distribution_grid empty_grid( {}, MAPBUFFER ); tripoint sm_pos = ms_to_sm_copy( p ); - // TODO: This should load a grid, not just expect to find loaded ones! auto iter = parent_distribution_grids.find( sm_pos ); if( iter != parent_distribution_grids.end() ) { return *iter->second; } - return empty_grid; + // This is ugly for the const case + return make_distribution_grid_at( sm_pos ); } const distribution_grid &distribution_grid_tracker::grid_at( const tripoint &p ) const diff --git a/src/distribution_grid.h b/src/distribution_grid.h index 0d749b821b66..5b36dc56a672 100644 --- a/src/distribution_grid.h +++ b/src/distribution_grid.h @@ -65,7 +65,7 @@ class distribution_grid_tracker /** * @param omt_pos Absolute submap position of one of the tiles of the grid. */ - void make_distribution_grid_at( const tripoint &sm_pos ); + distribution_grid &make_distribution_grid_at( const tripoint &sm_pos ); /** * In submap coords, to mirror @ref map diff --git a/src/iuse.cpp b/src/iuse.cpp index 7fe3fcdb9b64..5c8546d4687b 100644 --- a/src/iuse.cpp +++ b/src/iuse.cpp @@ -8930,7 +8930,7 @@ int iuse::cable_attach( player *p, item *it, bool, const tripoint & ) kmenu.text = _( "Using cable:" ); kmenu.addentry( 0, true, -1, _( "Detach and re-spool the cable" ) ); kmenu.addentry( 1, ( paying_out || cable_cbm ) && !solar_pack && - !UPS, -1, _( "Attach loose end to vehicle" ) ); + !UPS, -1, _( "Attach loose end to vehicle or grid connector" ) ); if( has_bio_cable && loose_ends ) { kmenu.addentry( 2, !cable_cbm, -1, _( "Attach loose end to self" ) ); @@ -8997,14 +8997,16 @@ int iuse::cable_attach( player *p, item *it, bool, const tripoint & ) return 0; } - const cata::optional vpos_ = choose_adjacent( _( "Attach cable to vehicle where?" ) ); + const cata::optional vpos_ = choose_adjacent( _( "Attach cable where?" ) ); if( !vpos_ ) { return 0; } const tripoint vpos = *vpos_; const optional_vpart_position target_vp = g->m.veh_at( vpos ); - vehicle_connector_tile *grid_connection = active_tiles::furn_at( vpos ); + tripoint target_global = g->m.getabs( vpos ); + vehicle_connector_tile *grid_connection = active_tiles::furn_at + ( target_global ); if( !target_vp && !grid_connection ) { p->add_msg_if_player( _( "There's no vehicle or grid connection there." ) ); return 0; @@ -9017,19 +9019,9 @@ int iuse::cable_attach( player *p, item *it, bool, const tripoint & ) p->add_msg_if_player( m_good, _( "You are now plugged to the vehicle." ) ); return 0; } else { - vehicle *const target_veh = &target_vp->vehicle(); - if( source_veh == target_veh ) { - if( p != nullptr && p->has_item( *it ) ) { - p->add_msg_if_player( m_warning, _( "The %s already has access to its own electric system!" ), - source_veh->name ); - } - return 0; - } - tripoint source_global( it->get_var( "source_x", 0 ), it->get_var( "source_y", 0 ), it->get_var( "source_z", 0 ) ); - tripoint target_global = g->m.getabs( vpos ); const vpart_id vpid( it->typeId() ); point vcoords = source_vp->mount(); @@ -9045,6 +9037,15 @@ int iuse::cable_attach( player *p, item *it, bool, const tripoint & ) source_veh->install_part( vcoords, source_part ); } } else { + vehicle *const target_veh = &target_vp->vehicle(); + if( source_veh == target_veh ) { + if( p != nullptr && p->has_item( *it ) ) { + p->add_msg_if_player( m_warning, _( "The %s already has access to its own electric system!" ), + source_veh->name ); + } + return 0; + } + source_part.target.first = target_global; source_part.target.second = g->m.getabs( target_veh->global_pos3() ); source_veh->install_part( vcoords, source_part ); diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 24306704b4e4..350ebbd755ae 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -4878,8 +4878,8 @@ int traverse( StartPoint *start, int amount, connected_elements.emplace( veh ); amount = veh_action( veh, amount ); - g->u.add_msg_if_player( m_debug, "After remote veh %p, %d power", static_cast( veh ), - amount ); + g->u.add_msg_if_player( m_debug, "After remote veh %p, %d power", + static_cast( veh ), amount ); return amount < 1; } @@ -4893,8 +4893,8 @@ int traverse( StartPoint *start, int amount, connected_elements.emplace( grid ); amount = grid_action( grid, amount ); - g->u.add_msg_if_player( m_debug, "After remote grid %p, %d power", static_cast( grid ), - amount ); + g->u.add_msg_if_player( m_debug, "After remote grid %p, %d power", + static_cast( grid ), amount ); return amount < 1; } @@ -5030,7 +5030,8 @@ int vehicle::discharge_battery( int amount, bool recurse ) g->u.add_msg_if_player( m_debug, "CHg: %d", amount ); return -grid->mod_resource( -amount, false ); }; - if( amount > 0 && recurse ) { // need more power! + if( amount > 0 && recurse ) { + // need more power! amount = distribution_graph::traverse( this, amount, discharge_vehicle, discharge_grid ); } diff --git a/tests/electric_grid_test.cpp b/tests/electric_grid_test.cpp index f8b562a38c75..7bb6a7a9ec43 100644 --- a/tests/electric_grid_test.cpp +++ b/tests/electric_grid_test.cpp @@ -11,9 +11,69 @@ #include "overmapbuffer.h" #include "vehicle.h" -static itype_id itype_battery = itype_id( "battery" ); +static itype_id itype_battery( "battery" ); -static void connect_grid_vehicle( vehicle &veh, vehicle_connector_tile &connector, +static inline void test_grid_veh( distribution_grid &grid, vehicle &veh, battery_tile &battery ) +{ + CAPTURE( veh.fuel_capacity( itype_battery ) ); + CAPTURE( battery.max_stored ); + WHEN( "the vehicle is fully charged and battery is discharged" ) { + veh.charge_battery( veh.fuel_capacity( itype_battery ), false ); + REQUIRE( veh.fuel_left( itype_battery, false ) == + veh.fuel_capacity( itype_battery ) ); + REQUIRE( battery.get_resource() == 0 ); + REQUIRE( grid.get_resource() == veh.fuel_capacity( itype_battery ) ); + AND_WHEN( "the grid is discharged without energy in battery" ) { + int deficit = grid.mod_resource( -( grid.get_resource() - 10 ) ); + CHECK( deficit == 0 ); + THEN( "the power is drained from vehicle" ) { + CHECK( grid.get_resource() == 10 ); + CHECK( veh.fuel_left( itype_battery, false ) == 10 ); + } + } + + AND_WHEN( "the vehicle is charged despite being full" ) { + int excess = veh.charge_battery( 10 ); + THEN( "the grid contains the added energy" ) { + CHECK( excess == 0 ); + CHECK( grid.get_resource() == veh.fuel_left( itype_battery, false ) + 10 ); + AND_THEN( "the added energy is in the battery" ) { + CHECK( battery.get_resource() == 10 ); + } + } + } + } + + WHEN( "the battery is fully charged and vehicle is discharged" ) { + int excess = battery.mod_resource( battery.max_stored ); + REQUIRE( excess == 0 ); + REQUIRE( battery.get_resource() == battery.max_stored ); + REQUIRE( veh.fuel_left( itype_battery, false ) == 0 ); + REQUIRE( grid.get_resource() == battery.get_resource() ); + AND_WHEN( "the vehicle is discharged despite being empty" ) { + int deficit = veh.discharge_battery( 10, true ); + THEN( "the grid provides the needed power" ) { + CHECK( deficit == 0 ); + AND_THEN( "this power comes from the battery" ) { + CHECK( battery.get_resource() == battery.max_stored - 10 ); + } + } + } + + AND_WHEN( "the grid is charged some more" ) { + int excess = grid.mod_resource( 10 ); + THEN( "the grid contains the added energy" ) { + CHECK( excess == 0 ); + CHECK( grid.get_resource() == battery.max_stored + 10 ); + AND_THEN( "the added energy is in the vehicle" ) { + CHECK( veh.fuel_left( itype_battery, false ) == 10 ); + } + } + } + } +} + +static void connect_grid_vehicle( map &m, vehicle &veh, vehicle_connector_tile &connector, const tripoint &connector_abs_pos ) { const point cable_part_pos = point( 0, 0 ); @@ -21,88 +81,69 @@ static void connect_grid_vehicle( vehicle &veh, vehicle_connector_tile &connecto source_part.target.first = connector_abs_pos; source_part.target.second = connector_abs_pos; source_part.set_flag( vehicle_part::targets_grid ); - connector.connected_vehicles.emplace_back( veh.global_pos3() ); + connector.connected_vehicles.clear(); + connector.connected_vehicles.emplace_back( m.getabs( veh.global_pos3() ) ); int part_index = veh.install_part( cable_part_pos, source_part ); REQUIRE( part_index >= 0 ); } -TEST_CASE( "grid_and_vehicle_in_bubble", "[grids][vehicle]" ) +struct grid_setup { + distribution_grid &grid; + vehicle &veh; + battery_tile &battery; +}; + +static grid_setup set_up_grid( map &m ) { - clear_map_and_put_player_underground(); + // TODO: clear_grids() + auto om = overmap_buffer.get_om_global( sm_to_omt_copy( m.get_abs_sub() ) ); + om.om->set_electric_grid_connections( om.local, {} ); + const tripoint vehicle_local_pos = tripoint( 10, 10, 0 ); const tripoint connector_local_pos = tripoint( 13, 10, 0 ); const tripoint battery_local_pos = tripoint( 14, 10, 0 ); - - map &m = g->m; const tripoint connector_abs_pos = m.getabs( connector_local_pos ); const tripoint battery_abs_pos = m.getabs( battery_local_pos ); + m.furn_set( connector_local_pos, furn_str_id( "f_cable_connector" ) ); + m.furn_set( battery_local_pos, furn_str_id( "f_battery" ) ); + vehicle *veh = m.add_vehicle( vproto_id( "car" ), vehicle_local_pos, 0, 0, 0, false ); + vehicle_connector_tile *grid_connector = + active_tiles::furn_at( connector_abs_pos ); + battery_tile *battery = active_tiles::furn_at( battery_abs_pos ); - GIVEN( "vehicle and battery on one grid" ) { - m.furn_set( connector_local_pos, furn_str_id( "f_cable_connector" ) ); - m.furn_set( battery_local_pos, furn_str_id( "f_battery" ) ); - vehicle *veh = m.add_vehicle( vproto_id( "car" ), vehicle_local_pos, 0, 0, 0, false ); - vehicle_connector_tile *grid_connector = - active_tiles::furn_at( connector_abs_pos ); - battery_tile *battery = active_tiles::furn_at( battery_abs_pos ); - - REQUIRE( veh ); - REQUIRE( grid_connector ); - REQUIRE( battery ); - - connect_grid_vehicle( *veh, *grid_connector, connector_abs_pos ); - - distribution_grid &grid = get_distribution_grid_tracker().grid_at( connector_abs_pos ); - REQUIRE( !grid.empty() ); - REQUIRE( &grid == &get_distribution_grid_tracker().grid_at( battery_abs_pos ) ); - WHEN( "the vehicle is fully charged" ) { - veh->charge_battery( veh->fuel_capacity( itype_battery ), false ); - REQUIRE( veh->fuel_left( itype_battery, false ) == - veh->fuel_capacity( itype_battery ) ); - AND_WHEN( "the grid is discharged without energy in battery" ) { - int deficit = veh->discharge_battery( grid.get_resource() - 10 ); - CHECK( deficit == 0 ); - THEN( "the power is drained from vehicle" ) { - CHECK( grid.get_resource() == 10 ); - CHECK( veh->fuel_left( itype_battery, false ) == 10 ); - } - } + REQUIRE( veh ); + REQUIRE( grid_connector ); + REQUIRE( battery ); - AND_WHEN( "the vehicle is charged despite being full" ) { - int excess = veh->charge_battery( 10 ); - THEN( "the grid contains the added energy" ) { - CHECK( excess == 0 ); - CHECK( grid.get_resource() == veh->fuel_left( itype_battery, false ) + 10 ); - AND_THEN( "the added energy is in the battery" ) { - CHECK( battery->get_resource() == 10 ); - } - } - } - } + connect_grid_vehicle( m, *veh, *grid_connector, connector_abs_pos ); - WHEN( "the battery is fully charged" ) { - int excess = battery->mod_resource( battery->max_stored ); - REQUIRE( excess == 0 ); - AND_WHEN( "the vehicle is discharged despite being empty" ) { - int deficit = veh->discharge_battery( 10, true ); - THEN( "the grid provides the needed power" ) { - CHECK( deficit == 0 ); - AND_THEN( "this power comes from the battery" ) { - CHECK( battery->get_resource() == battery->max_stored - 10 ); - } - } - } + distribution_grid &grid = get_distribution_grid_tracker().grid_at( connector_abs_pos ); + REQUIRE( !grid.empty() ); + REQUIRE( &grid == &get_distribution_grid_tracker().grid_at( battery_abs_pos ) ); + return grid_setup{grid, *veh, *battery}; +} - AND_WHEN( "the grid is charged some more" ) { - int excess = grid.mod_resource( 10 ); - THEN( "the grid contains the added energy" ) { - CHECK( excess == 0 ); - CHECK( grid.get_resource() == battery->max_stored + 10 ); - AND_THEN( "the added energy is in the vehicle" ) { - CHECK( veh->fuel_left( itype_battery, false ) == 10 ); - } - } - } - } +TEST_CASE( "grid_and_vehicle_in_bubble", "[grids][vehicle]" ) +{ + clear_map_and_put_player_underground(); + GIVEN( "vehicle and battery are on one grid" ) { + auto setup = set_up_grid( g->m ); + test_grid_veh( setup.grid, setup.veh, setup.battery ); + } +} + +TEST_CASE( "grid_and_vehicle_outside_bubble", "[grids][vehicle]" ) +{ + clear_map_and_put_player_underground(); + const tripoint old_abs_sub = g->m.get_abs_sub(); + // Ugly: we move the real map instead of the tinymap to reuse clear_map() results + g->m.load( g->m.get_abs_sub() + point( g->m.getmapsize(), 0 ), true ); + GIVEN( "vehicle and battery are on one grid" ) { + tinymap m; + m.load( old_abs_sub, false ); + auto setup = set_up_grid( m ); + m.save(); + test_grid_veh( setup.grid, setup.veh, setup.battery ); } } From 8ffee31bebdec2281ab2c06c3a63907839367098 Mon Sep 17 00:00:00 2001 From: Coolthulhu Date: Wed, 3 Feb 2021 10:12:51 +0100 Subject: [PATCH 10/12] Enable grid by default Support option to disable grid again --- src/distribution_grid.cpp | 18 ++++++++++++++++-- src/distribution_grid.h | 3 +-- src/game.cpp | 1 + src/options.cpp | 4 ++-- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/distribution_grid.cpp b/src/distribution_grid.cpp index 6a738e35ea2c..827b878d20a0 100644 --- a/src/distribution_grid.cpp +++ b/src/distribution_grid.cpp @@ -7,8 +7,11 @@ #include "mapbuffer.h" #include "map_iterator.h" #include "submap.h" +#include "options.h" #include "overmapbuffer.h" +static distribution_grid empty_grid( {}, MAPBUFFER ); + distribution_grid::distribution_grid( const std::vector &global_submap_coords, mapbuffer &buffer ) : submap_coords( global_submap_coords ), @@ -148,7 +151,9 @@ distribution_grid_tracker::distribution_grid_tracker( mapbuffer &buffer ) distribution_grid &distribution_grid_tracker::make_distribution_grid_at( const tripoint &sm_pos ) { - static distribution_grid empty_grid( {}, MAPBUFFER ); + if( !get_option( "ELECTRIC_GRID" ) ) { + return empty_grid; + } const std::set overmap_positions = overmap_buffer.electric_grid_at( sm_to_omt_copy( sm_pos ) ); std::vector submap_positions; @@ -177,6 +182,9 @@ distribution_grid &distribution_grid_tracker::make_distribution_grid_at( const t void distribution_grid_tracker::on_saved() { parent_distribution_grids.clear(); + if( !get_option( "ELECTRIC_GRID" ) ) { + return; + } tripoint min_bounds = tripoint( bounds.p_min, -OVERMAP_DEPTH ); tripoint max_bounds = tripoint( bounds.p_max, OVERMAP_HEIGHT ); // TODO: Only those which existed before the save @@ -191,13 +199,19 @@ void distribution_grid_tracker::on_changed( const tripoint &p ) { tripoint sm_pos = ms_to_sm_copy( p ); // TODO: If not in bounds, just drop the grid, rebuild lazily - if( parent_distribution_grids.count( sm_pos ) > 0 ) { + if( parent_distribution_grids.count( sm_pos ) > 0 || + bounds.contains_half_open( sm_pos.xy() ) ) { // TODO: Don't rebuild, update make_distribution_grid_at( sm_pos ); } } +void distribution_grid_tracker::on_options_changed() +{ + on_saved(); +} + distribution_grid &distribution_grid_tracker::grid_at( const tripoint &p ) { tripoint sm_pos = ms_to_sm_copy( p ); diff --git a/src/distribution_grid.h b/src/distribution_grid.h index 5b36dc56a672..6f269d2b2b01 100644 --- a/src/distribution_grid.h +++ b/src/distribution_grid.h @@ -101,6 +101,7 @@ class distribution_grid_tracker */ void on_changed( const tripoint &p ); void on_saved(); + void on_options_changed(); }; namespace distribution_graph @@ -113,8 +114,6 @@ template int traverse( StartPoint *start, int amount, VehFunc veh_action, GridFunc grid_action ); - - } /** diff --git a/src/game.cpp b/src/game.cpp index de740cab824d..a887fa87c8c7 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -10055,6 +10055,7 @@ void game::on_options_changed() #if defined(TILES) tilecontext->on_options_changed(); #endif + grid_tracker_ptr->on_options_changed(); } void game::fling_creature( Creature *c, const int &dir, float flvel, bool controlled ) diff --git a/src/options.cpp b/src/options.cpp index 41557b7e35a5..59ff94d1c735 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -2002,8 +2002,8 @@ void options_manager::add_options_debug() ); add( "ELECTRIC_GRID", "debug", translate_marker( "Electric grid testing" ), - translate_marker( "If true, enables completely unfinished electric grid system that only slows downs the game." ), - false + translate_marker( "If true, enables somewhat unfinished electric grid system that may slow the game down." ), + true ); } From 244177e4e63f4a0ca7122fa022e359e393c1a818 Mon Sep 17 00:00:00 2001 From: Coolthulhu Date: Wed, 3 Feb 2021 15:20:39 +0100 Subject: [PATCH 11/12] Fixes for misc issues Grid->veh off-bubble now works Laser turrets can shoot from grid power Grids now report stored amount properly --- .../furniture-appliances.json | 9 ++-- src/active_tile_data.h | 1 + src/distribution_grid.cpp | 21 ++++++-- src/distribution_grid.h | 11 ++-- src/iexamine.cpp | 4 ++ src/iuse.cpp | 4 +- src/turret.cpp | 2 +- src/vehicle.cpp | 52 +++++++++++++------ src/vehicle_part.cpp | 31 +++++++---- 9 files changed, 93 insertions(+), 42 deletions(-) diff --git a/data/json/furniture_and_terrain/furniture-appliances.json b/data/json/furniture_and_terrain/furniture-appliances.json index 29cc014e1a5f..17261b554e4d 100644 --- a/data/json/furniture_and_terrain/furniture-appliances.json +++ b/data/json/furniture_and_terrain/furniture-appliances.json @@ -580,7 +580,7 @@ "move_cost_mod": 2, "required_str": -1, "active": [ "solar", { "power": 50 } ], - "flags": [ "TRANSPARENT", "BASHABLE", "ELECTRIC_GRID" ], + "flags": [ "TRANSPARENT" ], "deconstruct": { "items": [ { "item": "scrap", "count": 6 }, @@ -683,16 +683,17 @@ { "type": "furniture", "id": "f_cable_connector", - "looks_like": "f_utility_shelf", + "looks_like": "f_TV_antenna", "name": "jumper cable connector", - "description": "Power outlet connecting a building's electrical grid to a vehicle. To use, connect a jumper cable to a vehicle, then place the other end on the connector.", + "description": "Power outlet connecting a building's electrical grid to a vehicle. To use, attach a jumper cable to a vehicle and then to the connector.", "symbol": "*", "color": "blue_white", - "move_cost_mod": -1, + "move_cost_mod": 2, "coverage": 10, "required_str": -1, "max_volume": "5000 ml", "active": [ "vehicle_connector", { } ], + "flags": [ "TRANSPARENT" ], "deconstruct": { "items": [ { "item": "power_supply" } ] }, "bash": { "str_min": 16, diff --git a/src/active_tile_data.h b/src/active_tile_data.h index abb6738e394e..71a538289b96 100644 --- a/src/active_tile_data.h +++ b/src/active_tile_data.h @@ -109,6 +109,7 @@ class vehicle_connector_tile : public active_tile_data namespace active_tiles { +// TODO: Don't return a raw pointer template T * furn_at( const tripoint &pos ); diff --git a/src/distribution_grid.cpp b/src/distribution_grid.cpp index 827b878d20a0..6050d64d5454 100644 --- a/src/distribution_grid.cpp +++ b/src/distribution_grid.cpp @@ -26,7 +26,9 @@ distribution_grid::distribution_grid( const std::vector &global_submap for( auto &active : sm->active_furniture ) { const tripoint ms_pos = sm_to_ms_copy( sm_coord ); - contents[sm_coord].emplace_back( active.first, ms_pos + active.first ); + const tripoint abs_pos = ms_pos + active.first; + contents[sm_coord].emplace_back( active.first, abs_pos ); + flat_contents.emplace_back( abs_pos ); } } } @@ -110,14 +112,19 @@ int distribution_grid::mod_resource( int amt, bool recurse ) return amt; } -int distribution_grid::get_resource() const +int distribution_grid::get_resource( bool recurse ) const { int res = 0; + std::vector connected_vehicles; for( const std::pair> &c : contents ) { for( const tile_location &loc : c.second ) { battery_tile *battery = active_tiles::furn_at( loc.absolute ); if( battery != nullptr ) { - res += battery->stored; + res += battery->get_resource(); + continue; + } + + if( !recurse ) { continue; } @@ -130,13 +137,17 @@ int distribution_grid::get_resource() const debugmsg( "lost vehicle at %d,%d,%d", veh_abs.x, veh_abs.y, veh_abs.z ); continue; } - - res += veh->fuel_left( itype_battery, false ); + connected_vehicles.push_back( veh ); } } } } + // TODO: Giga ugly. We only charge the first vehicle to get it to use its recursive graph traversal because it's inaccessible from here due to being a template method + if( !connected_vehicles.empty() ) { + res = connected_vehicles.front()->fuel_left( itype_battery, true ); + } + return res; } diff --git a/src/distribution_grid.h b/src/distribution_grid.h index 6f269d2b2b01..131b48dedfd5 100644 --- a/src/distribution_grid.h +++ b/src/distribution_grid.h @@ -25,19 +25,19 @@ struct tile_location { /** * A cache that organizes producers, storage and consumers * of some resource, like electricity. + * WARNING: Shouldn't be stored, as out of date grids are not updated. */ class distribution_grid { private: friend class distribution_grid_tracker; - // TODO: Remove that public - public: + /** * Map of submap coords to points on this submap * that contain an active tile. */ std::map> contents; - private: + std::vector flat_contents; std::vector submap_coords; mapbuffer &mb; @@ -48,7 +48,10 @@ class distribution_grid explicit operator bool() const; void update( time_point to ); int mod_resource( int amt, bool recurse = true ); - int get_resource() const; + int get_resource( bool recurse = true ) const; + const std::vector &get_contents() const { + return flat_contents; + } }; /** diff --git a/src/iexamine.cpp b/src/iexamine.cpp index c83ba095e083..fb1ac7a9e81f 100644 --- a/src/iexamine.cpp +++ b/src/iexamine.cpp @@ -5643,6 +5643,10 @@ void iexamine::dimensional_portal( player &p, const tripoint &pos ) void iexamine::check_power( player &, const tripoint &pos ) { tripoint abspos = g->m.getabs( pos ); + battery_tile *battery = active_tiles::furn_at( abspos ); + if( battery != nullptr ) { + add_msg( m_info, _( "This battery stores %d kJ of electric power." ), battery->get_resource() ); + } int amt = get_distribution_grid_tracker().grid_at( abspos ).get_resource(); add_msg( m_info, _( "This electric grid stores %d kJ of electric power." ), amt ); } diff --git a/src/iuse.cpp b/src/iuse.cpp index 5c8546d4687b..b07259d4d76b 100644 --- a/src/iuse.cpp +++ b/src/iuse.cpp @@ -8901,7 +8901,7 @@ int iuse::cable_attach( player *p, item *it, bool, const tripoint & ) const auto abspos = g->m.getabs( posp ); it->set_var( "source_x", abspos.x ); it->set_var( "source_y", abspos.y ); - it->set_var( "source_z", g->get_levz() ); + it->set_var( "source_z", abspos.z ); set_cable_active( p, it, "pay_out_cable" ); } } else { @@ -9033,7 +9033,7 @@ int iuse::cable_attach( player *p, item *it, bool, const tripoint & ) if( p != nullptr && p->has_item( *it ) ) { p->add_msg_if_player( m_good, _( "You connect the %s to the electric grid." ), source_veh->name ); - grid_connection->connected_vehicles.emplace_back( source_global ); + grid_connection->connected_vehicles.emplace_back( g->m.getabs( source_veh->global_pos3() ) ); source_veh->install_part( vcoords, source_part ); } } else { diff --git a/src/turret.cpp b/src/turret.cpp index 90a336a99e75..77d2a85688d4 100644 --- a/src/turret.cpp +++ b/src/turret.cpp @@ -245,7 +245,7 @@ turret_data::status turret_data::query() const } auto ups = part->base.get_gun_ups_drain() * part->base.gun_current_mode().qty; - if( ups > veh->fuel_left( fuel_type_battery ) ) { + if( ups > veh->fuel_left( fuel_type_battery, true ) ) { return status::no_power; } diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 350ebbd755ae..cb483b6794cd 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -3219,11 +3219,15 @@ int vehicle::fuel_left( const itype_id &ftype, bool recurse ) const auto fuel_counting_visitor = [&]( vehicle const * veh, int amount ) { return amount + veh->fuel_left( ftype, false ); }; + auto power_counting_visitor = [&]( distribution_grid const * grid, int amount ) { + return amount + grid->get_resource( false ); + }; // HAX: add 1 to the initial amount so traversal doesn't immediately stop just // 'cause we have 0 fuel left in the current vehicle. Subtract the 1 immediately // after traversal. - fl = traverse_vehicle_graph( this, fl + 1, fuel_counting_visitor ) - 1; + fl = distribution_graph::traverse( this, fl + 1, + fuel_counting_visitor, power_counting_visitor ) - 1; } //muscle engines have infinite fuel @@ -4868,14 +4872,19 @@ int traverse( StartPoint *start, int amount, std::queue> connected_elements; std::unordered_set, hash> visited_elements; connected_elements.emplace( start ); + visited_elements.insert( start ); auto &grid_tracker = get_distribution_grid_tracker(); auto process_vehicle = [&]( const tripoint & target_pos ) { auto *veh = vehicle::find_vehicle( target_pos ); + if( veh == nullptr ) { + debugmsg( "lost vehicle at %d,%d,%d", target_pos.x, target_pos.y, target_pos.z ); + } // Add this connected vehicle to the queue of vehicles to search next, // but only if we haven't seen this one before. - if( veh != nullptr && visited_elements.count( veh ) < 1 ) { + if( veh != nullptr && visited_elements.count( veh ) == 0 ) { connected_elements.emplace( veh ); + visited_elements.insert( veh ); amount = veh_action( veh, amount ); g->u.add_msg_if_player( m_debug, "After remote veh %p, %d power", @@ -4889,8 +4898,12 @@ int traverse( StartPoint *start, int amount, auto process_grid = [&]( const tripoint & target_pos ) { auto *grid = &grid_tracker.grid_at( target_pos ); - if( *grid && visited_elements.count( grid ) < 1 ) { + if( !*grid ) { + debugmsg( "lost grid at %d,%d,%d", target_pos.x, target_pos.y, target_pos.z ); + } + if( *grid && visited_elements.count( grid ) == 0 ) { connected_elements.emplace( grid ); + visited_elements.insert( grid ); amount = grid_action( grid, amount ); g->u.add_msg_if_player( m_debug, "After remote grid %p, %d power", @@ -4904,8 +4917,6 @@ int traverse( StartPoint *start, int amount, while( amount > 0 && !connected_elements.empty() ) { auto current = connected_elements.front(); - - visited_elements.insert( current ); connected_elements.pop(); if( current.type == vehicle_or_grid::type_t::vehicle ) { @@ -4930,18 +4941,15 @@ int traverse( StartPoint *start, int amount, } else { // Grids can only be connected to vehicles at the moment auto ¤t_grid = *current.grid; - for( auto &pr : current_grid.contents ) { - for( const tile_location &loc : pr.second ) { - const vehicle_connector_tile *connector = active_tiles::furn_at - ( loc.absolute ); - if( connector == nullptr ) { - continue; - } + for( auto &p : current_grid.get_contents() ) { + const vehicle_connector_tile *connector = active_tiles::furn_at( p ); + if( connector == nullptr ) { + continue; + } - for( const tripoint &target_pos : connector->connected_vehicles ) { - if( process_vehicle( target_pos ) ) { - break; - } + for( const tripoint &target_pos : connector->connected_vehicles ) { + if( process_vehicle( target_pos ) ) { + break; } } } @@ -6065,6 +6073,18 @@ bool vehicle::no_towing_slack() const void vehicle::remove_remote_part( int part_num ) { + if( parts[part_num].has_flag( vehicle_part::targets_grid ) ) { + vehicle_connector_tile *connector = + active_tiles::furn_at( parts[part_num].target.second ); + if( connector != nullptr ) { + auto &vehs = connector->connected_vehicles; + auto iter = std::find( vehs.begin(), vehs.end(), g->m.getabs( global_pos3() ) ); + if( iter != vehs.end() ) { + vehs.erase( iter ); + } + } + return; + } auto veh = find_vehicle( parts[part_num].target.second ); // If the target vehicle is still there, ask it to remove its part diff --git a/src/vehicle_part.cpp b/src/vehicle_part.cpp index 553a889cd8c0..d89319ae78c8 100644 --- a/src/vehicle_part.cpp +++ b/src/vehicle_part.cpp @@ -68,18 +68,29 @@ item vehicle_part::properties_to_item() const // Cables get special handling: their target coordinates need to remain // stored, and if a cable actually drops, it should be half-connected. + // Except grid-connected ones, for now. if( tmp.has_flag( "CABLE_SPOOL" ) && !tmp.has_flag( "TOW_CABLE" ) ) { - const tripoint local_pos = g->m.getlocal( target.first ); - if( !g->m.veh_at( local_pos ) ) { - // That vehicle ain't there no more. - tmp.item_tags.insert( "NO_DROP" ); - } + if( has_flag( targets_grid ) ) { + // Ideally, we'd drop the cable on the charger instead + tmp.erase_var( "source_x" ); + tmp.erase_var( "source_y" ); + tmp.erase_var( "source_z" ); + tmp.erase_var( "state" ); + tmp.active = false; + tmp.charges = tmp.type->maximum_charges(); + } else { + const tripoint local_pos = g->m.getlocal( target.first ); + if( !g->m.veh_at( local_pos ) ) { + // That vehicle ain't there no more. + tmp.item_tags.insert( "NO_DROP" ); + } - tmp.set_var( "source_x", target.first.x ); - tmp.set_var( "source_y", target.first.y ); - tmp.set_var( "source_z", target.first.z ); - tmp.set_var( "state", "pay_out_cable" ); - tmp.active = true; + tmp.set_var( "source_x", target.first.x ); + tmp.set_var( "source_y", target.first.y ); + tmp.set_var( "source_z", target.first.z ); + tmp.set_var( "state", "pay_out_cable" ); + tmp.active = true; + } } // force rationalization of damage values to the middle value of each damage level so From 71b099446e0041514a14fe9485c48e942c625a6d Mon Sep 17 00:00:00 2001 From: Coolthulhu Date: Thu, 4 Feb 2021 16:08:18 +0100 Subject: [PATCH 12/12] Construction recipe for connector --- data/json/construction.json | 14 ++++++++++++++ .../furniture-appliances.json | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/data/json/construction.json b/data/json/construction.json index b9e8a83502d1..e5c85b9fd1a9 100644 --- a/data/json/construction.json +++ b/data/json/construction.json @@ -4099,5 +4099,19 @@ "pre_note": "Will only work if constructed in/on a building that has an electric grid with a mounted battery.", "pre_special": "check_empty", "post_terrain": "f_oven" + }, + { + "type": "construction", + "id": "constr_cable_connector", + "description": "Build Jumper Cable Connector", + "category": "FURN", + "required_skills": [ [ "electronics", 1 ] ], + "time": "45 m", + "qualities": [ [ { "id": "SCREW", "level": 1 } ] ], + "tools": [ [ [ "soldering_iron", 20 ] ] ], + "components": [ [ [ "power_supply", 1 ] ] ], + "pre_note": "Will only work if constructed in/on a building that has an electric grid.", + "pre_special": "check_empty", + "post_terrain": "f_cable_connector" } ] diff --git a/data/json/furniture_and_terrain/furniture-appliances.json b/data/json/furniture_and_terrain/furniture-appliances.json index 17261b554e4d..b1d74a55dc5d 100644 --- a/data/json/furniture_and_terrain/furniture-appliances.json +++ b/data/json/furniture_and_terrain/furniture-appliances.json @@ -696,8 +696,8 @@ "flags": [ "TRANSPARENT" ], "deconstruct": { "items": [ { "item": "power_supply" } ] }, "bash": { - "str_min": 16, - "str_max": 40, + "str_min": 4, + "str_max": 30, "sound": "metal screeching!", "sound_fail": "clang!", "items": [ { "item": "power_supply", "prob": 50 } ]