Skip to content
Permalink
Tree: a2fc42c518
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
8254 lines (7197 sloc) 267 KB
#include "map.h"
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include "ammo.h"
#include "artifact.h"
#include "calendar.h"
#include "coordinate_conversions.h"
#include "clzones.h"
#include "debug.h"
#include "drawing_primitives.h"
#include "event.h"
#include "fragment_cloud.h"
#include "fungal_effects.h"
#include "game.h"
#include "harvest.h"
#include "iexamine.h"
#include "item.h"
#include "item_factory.h"
#include "item_group.h"
#include "iuse_actor.h"
#include "lightmap.h"
#include "line.h"
#include "map_iterator.h"
#include "map_selector.h"
#include "mapbuffer.h"
#include "mapdata.h"
#include "messages.h"
#include "mongroup.h"
#include "monster.h"
#include "mtype.h"
#include "npc.h"
#include "options.h"
#include "output.h"
#include "pathfinding.h"
#include "projectile.h"
#include "rng.h"
#include "scent_map.h"
#include "sounds.h"
#include "string_formatter.h"
#include "submap.h"
#include "translations.h"
#include "trap.h"
#include "veh_type.h"
#include "vehicle.h"
#include "vpart_position.h"
#include "vpart_range.h"
#include "vpart_reference.h"
#include "weather.h"
const mtype_id mon_zombie( "mon_zombie" );
const skill_id skill_traps( "traps" );
const species_id ZOMBIE( "ZOMBIE" );
const efftype_id effect_boomered( "boomered" );
const efftype_id effect_crushed( "crushed" );
const efftype_id effect_stunned( "stunned" );
extern bool is_valid_in_w_terrain( int, int );
#include "overmapbuffer.h"
#define dbg(x) DebugLog((DebugLevel)(x),D_MAP) << __FILE__ << ":" << __LINE__ << ": "
static std::list<item> nulitems; // Returned when &i_at() is asked for an OOB value
static field nulfield; // Returned when &field_at() is asked for an OOB value
static int null_temperature; // Because radiation does it too
static level_cache nullcache; // Dummy cache for z-levels outside bounds
// Map stack methods.
std::list<item>::iterator map_stack::erase( std::list<item>::iterator it )
{
return myorigin->i_rem( location, it );
}
void map_stack::push_back( const item &newitem )
{
myorigin->add_item_or_charges( location, newitem );
}
void map_stack::insert_at( std::list<item>::iterator index,
const item &newitem )
{
myorigin->add_item_at( location, index, newitem );
}
units::volume map_stack::max_volume() const
{
if( !myorigin->inbounds( location ) ) {
return 0_ml;
} else if( myorigin->has_furn( location ) ) {
return myorigin->furn( location ).obj().max_volume;
}
return myorigin->ter( location ).obj().max_volume;
}
// Map class methods.
map::map( int mapsize, bool zlev )
{
my_MAPSIZE = mapsize;
zlevels = zlev;
if( zlevels ) {
grid.resize( my_MAPSIZE * my_MAPSIZE * OVERMAP_LAYERS, nullptr );
} else {
grid.resize( my_MAPSIZE * my_MAPSIZE, nullptr );
}
for( auto &ptr : caches ) {
ptr = std::unique_ptr<level_cache>( new level_cache() );
}
for( auto &ptr : pathfinding_caches ) {
ptr = std::unique_ptr<pathfinding_cache>( new pathfinding_cache() );
}
dbg( D_INFO ) << "map::map(): my_MAPSIZE: " << my_MAPSIZE << " z-levels enabled:" << zlevels;
traplocs.resize( trap::count() );
}
map::~map() = default;
static submap null_submap;
const maptile map::maptile_at( const tripoint &p ) const
{
if( !inbounds( p ) ) {
return maptile( &null_submap, 0, 0 );
}
return maptile_at_internal( p );
}
maptile map::maptile_at( const tripoint &p )
{
if( !inbounds( p ) ) {
return maptile( &null_submap, 0, 0 );
}
return maptile_at_internal( p );
}
const maptile map::maptile_at_internal( const tripoint &p ) const
{
point l;
submap *const sm = get_submap_at( p, l );
return maptile( sm, l );
}
maptile map::maptile_at_internal( const tripoint &p )
{
point l;
submap *const sm = get_submap_at( p, l );
return maptile( sm, l );
}
// Vehicle functions
VehicleList map::get_vehicles()
{
if( !zlevels ) {
return get_vehicles( tripoint( 0, 0, abs_sub.z ),
tripoint( SEEX * my_MAPSIZE, SEEY * my_MAPSIZE, abs_sub.z ) );
}
return get_vehicles( tripoint( 0, 0, -OVERMAP_DEPTH ),
tripoint( SEEX * my_MAPSIZE, SEEY * my_MAPSIZE, OVERMAP_HEIGHT ) );
}
void map::reset_vehicle_cache( const int zlev )
{
clear_vehicle_cache( zlev );
// Cache all vehicles
auto &ch = get_cache( zlev );
ch.veh_in_active_range = false;
for( const auto &elem : ch.vehicle_list ) {
add_vehicle_to_cache( elem );
}
}
void map::add_vehicle_to_cache( vehicle *veh )
{
if( veh == nullptr ) {
debugmsg( "Tried to add null vehicle to cache" );
return;
}
auto &ch = get_cache( veh->smz );
ch.veh_in_active_range = true;
// Get parts
std::vector<vehicle_part> &parts = veh->parts;
int partid = 0;
for( std::vector<vehicle_part>::iterator it = parts.begin(),
end = parts.end(); it != end; ++it, ++partid ) {
if( it->removed ) {
continue;
}
const tripoint p = veh->global_part_pos3( *it );
ch.veh_cached_parts.insert( std::make_pair( p,
std::make_pair( veh, partid ) ) );
if( inbounds( p ) ) {
ch.veh_exists_at[p.x][p.y] = true;
}
}
}
void map::update_vehicle_cache( vehicle *veh, const int old_zlevel )
{
if( veh == nullptr ) {
debugmsg( "Tried to add null vehicle to cache" );
return;
}
// Existing must be cleared
auto &ch = get_cache( old_zlevel );
auto it = ch.veh_cached_parts.begin();
const auto end = ch.veh_cached_parts.end();
while( it != end ) {
if( it->second.first == veh ) {
const tripoint p = it->first;
if( inbounds( p ) ) {
ch.veh_exists_at[p.x][p.y] = false;
}
ch.veh_cached_parts.erase( it++ );
// If something was resting on vehicle, drop it
support_dirty( tripoint( p.x, p.y, old_zlevel + 1 ) );
} else {
++it;
}
}
add_vehicle_to_cache( veh );
}
void map::clear_vehicle_cache( const int zlev )
{
auto &ch = get_cache( zlev );
while( !ch.veh_cached_parts.empty() ) {
const auto part = ch.veh_cached_parts.begin();
const auto &p = part->first;
if( inbounds( p ) ) {
ch.veh_exists_at[p.x][p.y] = false;
}
ch.veh_cached_parts.erase( part );
}
}
void map::clear_vehicle_list( const int zlev )
{
auto &ch = get_cache( zlev );
ch.vehicle_list.clear();
ch.zone_vehicles.clear();
}
void map::update_vehicle_list( submap *const to, const int zlev )
{
// Update vehicle data
auto &ch = get_cache( zlev );
for( const auto &elem : to->vehicles ) {
ch.vehicle_list.insert( elem.get() );
if( !elem->loot_zones.empty() ) {
ch.zone_vehicles.insert( elem.get() );
}
}
}
std::unique_ptr<vehicle> map::detach_vehicle( vehicle *veh )
{
if( veh == nullptr ) {
debugmsg( "map::detach_vehicle was passed nullptr" );
return std::unique_ptr<vehicle>();
}
if( veh->smz < -OVERMAP_DEPTH || veh->smz > OVERMAP_HEIGHT ) {
debugmsg( "detach_vehicle got a vehicle outside allowed z-level range! name=%s, submap:%d,%d,%d",
veh->name.c_str(), veh->smx, veh->smy, veh->smz );
// Try to fix by moving the vehicle here
veh->smz = abs_sub.z;
}
submap *const current_submap = get_submap_at_grid( {veh->smx, veh->smy, veh->smz} );
auto &ch = get_cache( veh->smz );
for( size_t i = 0; i < current_submap->vehicles.size(); i++ ) {
if( current_submap->vehicles[i].get() == veh ) {
const int zlev = veh->smz;
ch.vehicle_list.erase( veh );
ch.zone_vehicles.erase( veh );
reset_vehicle_cache( zlev );
std::unique_ptr<vehicle> result = std::move( current_submap->vehicles[i] );
current_submap->vehicles.erase( current_submap->vehicles.begin() + i );
if( veh->tracking_on ) {
overmap_buffer.remove_vehicle( veh );
}
dirty_vehicle_list.erase( veh );
return result;
}
}
debugmsg( "detach_vehicle can't find it! name=%s, submap:%d,%d,%d", veh->name.c_str(), veh->smx,
veh->smy, veh->smz );
return std::unique_ptr<vehicle>();
}
void map::destroy_vehicle( vehicle *veh )
{
detach_vehicle( veh );
}
void map::on_vehicle_moved( const int smz )
{
set_outside_cache_dirty( smz );
set_transparency_cache_dirty( smz );
set_floor_cache_dirty( smz );
set_pathfinding_cache_dirty( smz );
}
void map::vehmove()
{
// give vehicles movement points
{
VehicleList vehs = get_vehicles();
for( auto &vehs_v : vehs ) {
vehicle *veh = vehs_v.v;
veh->gain_moves();
veh->slow_leak();
}
}
// 15 equals 3 >50mph vehicles, or up to 15 slow (1 square move) ones
// But 15 is too low for V12 death-bikes, let's put 100 here
for( int count = 0; count < 100; count++ ) {
if( !vehproceed() ) {
break;
}
}
// Process item removal on the vehicles that were modified this turn.
// Use a copy because part_removal_cleanup can modify the container.
auto temp = dirty_vehicle_list;
VehicleList vehicle_list = get_vehicles();
for( const auto &elem : temp ) {
auto same_ptr = [ elem ]( const struct wrapped_vehicle & tgt ) {
return elem == tgt.v;
};
if( std::find_if( vehicle_list.begin(), vehicle_list.end(), same_ptr ) !=
vehicle_list.end() ) {
( elem )->part_removal_cleanup();
}
}
dirty_vehicle_list.clear();
}
bool map::vehproceed()
{
VehicleList vehs = get_vehicles();
vehicle *cur_veh = nullptr;
float max_of_turn = 0;
// First horizontal movement
for( auto &vehs_v : vehs ) {
if( vehs_v.v->of_turn > max_of_turn ) {
cur_veh = vehs_v.v;
max_of_turn = cur_veh->of_turn;
}
}
// Then vertical-only movement
if( cur_veh == nullptr ) {
for( auto &vehs_v : vehs ) {
vehicle &cveh = *vehs_v.v;
if( cveh.is_falling ) {
cur_veh = vehs_v.v;
break;
}
}
}
if( cur_veh == nullptr ) {
return false;
}
return cur_veh->act_on_map();
}
static bool sees_veh( const Creature &c, vehicle &veh, bool force_recalc )
{
const auto &veh_points = veh.get_points( force_recalc );
return std::any_of( veh_points.begin(), veh_points.end(), [&c]( const tripoint & pt ) {
return c.sees( pt );
} );
}
void map::move_vehicle( vehicle &veh, const tripoint &dp, const tileray &facing )
{
const bool vertical = dp.z != 0;
if( ( dp.x == 0 && dp.y == 0 && dp.z == 0 ) ||
( abs( dp.x ) > 1 || abs( dp.y ) > 1 || abs( dp.z ) > 1 ) ||
( vertical && ( dp.x != 0 || dp.y != 0 ) ) ) {
debugmsg( "move_vehicle called with %d,%d,%d displacement vector", dp.x, dp.y, dp.z );
return;
}
if( dp.z + veh.smz < -OVERMAP_DEPTH || dp.z + veh.smz > OVERMAP_HEIGHT ) {
return;
}
veh.precalc_mounts( 1, veh.skidding ? veh.turn_dir : facing.dir(), veh.pivot_point() );
// cancel out any movement of the vehicle due only to a change in pivot
tripoint dp1 = dp - veh.pivot_displacement();
int impulse = 0;
std::vector<veh_collision> collisions;
// Find collisions
// Velocity of car before collision
// Split into vertical and horizontal movement
const int &coll_velocity = vertical ? veh.vertical_velocity : veh.velocity;
const int velocity_before = coll_velocity;
if( velocity_before == 0 ) {
debugmsg( "%s tried to move %s with no velocity",
veh.name.c_str(), vertical ? "vertically" : "horizontally" );
return;
}
bool veh_veh_coll_flag = false;
// Try to collide multiple times
size_t collision_attempts = 10;
do {
collisions.clear();
veh.collision( collisions, dp1, false );
// Vehicle collisions
std::map<vehicle *, std::vector<veh_collision> > veh_collisions;
for( auto &coll : collisions ) {
if( coll.type != veh_coll_veh ) {
continue;
}
veh_veh_coll_flag = true;
// Only collide with each vehicle once
veh_collisions[ static_cast<vehicle *>( coll.target ) ].push_back( coll );
}
for( auto &pair : veh_collisions ) {
impulse += vehicle_vehicle_collision( veh, *pair.first, pair.second );
}
// Non-vehicle collisions
for( const auto &coll : collisions ) {
if( coll.type == veh_coll_veh ) {
continue;
}
if( static_cast<size_t>( coll.part ) > veh.parts.size() ) {
continue;
}
const point &collision_point = veh.parts[coll.part].mount;
const int coll_dmg = coll.imp;
impulse += coll_dmg;
// Shock damage
veh.damage( coll.part, coll_dmg, DT_BASH );
veh.damage_all( coll_dmg / 2, coll_dmg, DT_BASH, collision_point );
}
} while( collision_attempts-- > 0 &&
sgn( coll_velocity ) == sgn( velocity_before ) &&
!collisions.empty() && !veh_veh_coll_flag );
if( vertical && !collisions.empty() ) {
// A big hack, should be removed when possible
veh.vertical_velocity = 0;
}
const int velocity_after = coll_velocity;
const bool can_move = velocity_after != 0 && sgn( velocity_after ) == sgn( velocity_before );
int coll_turn = 0;
if( impulse > 0 ) {
coll_turn = shake_vehicle( veh, velocity_before, facing.dir() );
const int volume = std::min<int>( 100, sqrtf( impulse ) );
// TODO: Center the sound at weighted (by impulse) average of collisions
sounds::sound( veh.global_pos3(), volume, sounds::sound_t::combat, _( "crash!" ),
false, "smash_success", "hit_vehicle" );
}
if( veh_veh_coll_flag ) {
// Break here to let the hit vehicle move away
return;
}
// If not enough wheels, mess up the ground a bit.
if( !vertical && !veh.valid_wheel_config() && !veh.is_in_water() ) {
veh.velocity += veh.velocity < 0 ? 2000 : -2000;
for( const auto &p : veh.get_points() ) {
const ter_id &pter = ter( p );
if( pter == t_dirt || pter == t_grass ) {
ter_set( p, t_dirtmound );
}
}
}
// Now we're gonna handle traps we're standing on (if we're still moving).
if( !vertical && can_move ) {
const auto wheel_indices = veh.wheelcache; // Don't use a reference here, it causes a crash.
for( auto &w : wheel_indices ) {
const tripoint wheel_p = veh.global_part_pos3( w );
if( one_in( 2 ) && displace_water( wheel_p ) ) {
sounds::sound( wheel_p, 4, sounds::sound_t::movement, _( "splash!" ), false,
"environment", "splash" );
}
veh.handle_trap( wheel_p, w );
if( !has_flag( "SEALED", wheel_p ) ) {
// TODO: Make this value depend on the wheel
smash_items( wheel_p, 5 );
}
}
}
const int last_turn_dec = 1;
if( veh.last_turn < 0 ) {
veh.last_turn += last_turn_dec;
if( veh.last_turn > -last_turn_dec ) {
veh.last_turn = 0;
}
} else if( veh.last_turn > 0 ) {
veh.last_turn -= last_turn_dec;
if( veh.last_turn < last_turn_dec ) {
veh.last_turn = 0;
}
}
const bool seen = sees_veh( g->u, veh, false );
if( can_move ) {
// Accept new direction
if( veh.skidding ) {
veh.face.init( veh.turn_dir );
} else {
veh.face = facing;
}
veh.move = facing;
if( coll_turn != 0 ) {
veh.skidding = true;
veh.turn( coll_turn );
}
veh.on_move();
// Actually change position
tripoint pt = veh.global_pos3(); // displace_vehicle needs a non-const reference
displace_vehicle( pt, dp1 );
} else if( !vertical ) {
veh.stop();
}
// If the PC is in the currently moved vehicle, adjust the
// view offset.
if( g->u.controlling_vehicle && veh_pointer_or_null( veh_at( g->u.pos() ) ) == &veh ) {
g->calc_driving_offset( &veh );
if( veh.skidding && can_move ) {
// TODO: Make skid recovery in air hard
veh.possibly_recover_from_skid();
}
}
// Redraw scene
// But only if the vehicle was seen before or after the move
if( seen || sees_veh( g->u, veh, true ) ) {
g->draw();
refresh_display();
}
}
float map::vehicle_vehicle_collision( vehicle &veh, vehicle &veh2,
const std::vector<veh_collision> &collisions )
{
if( &veh == &veh2 ) {
debugmsg( "Vehicle %s collided with itself", veh.name.c_str() );
return 0.0f;
}
// Effects of colliding with another vehicle:
// transfers of momentum, skidding,
// parts are damaged/broken on both sides,
// remaining times are normalized
const veh_collision &c = collisions[0];
add_msg( m_bad, _( "The %1$s's %2$s collides with %3$s's %4$s." ),
veh.name.c_str(), veh.part_info( c.part ).name().c_str(),
veh2.name.c_str(), veh2.part_info( c.target_part ).name().c_str() );
const bool vertical = veh.smz != veh2.smz;
// Used to calculate the epicenter of the collision.
point epicenter1( 0, 0 );
point epicenter2( 0, 0 );
float dmg;
// Vertical collisions will be simpler for a while (1D)
if( !vertical ) {
// For reference, a cargo truck weighs ~25300, a bicycle 690,
// and 38mph is 3800 'velocity'
rl_vec2d velo_veh1 = veh.velo_vec();
rl_vec2d velo_veh2 = veh2.velo_vec();
const float m1 = to_kilogram( veh.total_mass() );
const float m2 = to_kilogram( veh2.total_mass() );
//Energy of vehicle1 and vehicle2 before collision
float E = 0.5 * m1 * velo_veh1.magnitude() * velo_veh1.magnitude() +
0.5 * m2 * velo_veh2.magnitude() * velo_veh2.magnitude();
// Collision_axis
point cof1 = veh .rotated_center_of_mass();
point cof2 = veh2.rotated_center_of_mass();
int &x_cof1 = cof1.x;
int &y_cof1 = cof1.y;
int &x_cof2 = cof2.x;
int &y_cof2 = cof2.y;
rl_vec2d collision_axis_y;
collision_axis_y.x = ( veh.global_pos3().x + x_cof1 ) - ( veh2.global_pos3().x + x_cof2 );
collision_axis_y.y = ( veh.global_pos3().y + y_cof1 ) - ( veh2.global_pos3().y + y_cof2 );
collision_axis_y = collision_axis_y.normalized();
rl_vec2d collision_axis_x = collision_axis_y.rotated( M_PI / 2 );
// imp? & delta? & final? reworked:
// newvel1 =( vel1 * ( mass1 - mass2 ) + ( 2 * mass2 * vel2 ) ) / ( mass1 + mass2 )
// as per http://en.wikipedia.org/wiki/Elastic_collision
//velocity of veh1 before collision in the direction of collision_axis_y
float vel1_y = collision_axis_y.dot_product( velo_veh1 );
float vel1_x = collision_axis_x.dot_product( velo_veh1 );
//velocity of veh2 before collision in the direction of collision_axis_y
float vel2_y = collision_axis_y.dot_product( velo_veh2 );
float vel2_x = collision_axis_x.dot_product( velo_veh2 );
// e = 0 -> inelastic collision
// e = 1 -> elastic collision
float e = get_collision_factor( vel1_y / 100 - vel2_y / 100 );
// Velocity after collision
// vel1_x_a = vel1_x, because in x-direction we have no transmission of force
float vel1_x_a = vel1_x;
float vel2_x_a = vel2_x;
// Transmission of force only in direction of collision_axix_y
// Equation: partially elastic collision
float vel1_y_a = ( m2 * vel2_y * ( 1 + e ) + vel1_y * ( m1 - m2 * e ) ) / ( m1 + m2 );
float vel2_y_a = ( m1 * vel1_y * ( 1 + e ) + vel2_y * ( m2 - m1 * e ) ) / ( m1 + m2 );
// Add both components; Note: collision_axis is normalized
rl_vec2d final1 = collision_axis_y * vel1_y_a + collision_axis_x * vel1_x_a;
rl_vec2d final2 = collision_axis_y * vel2_y_a + collision_axis_x * vel2_x_a;
veh.move.init( final1.x, final1.y );
veh.velocity = final1.magnitude();
veh2.move.init( final2.x, final2.y );
veh2.velocity = final2.magnitude();
//give veh2 the initiative to proceed next before veh1
float avg_of_turn = ( veh2.of_turn + veh.of_turn ) / 2;
if( avg_of_turn < .1f ) {
avg_of_turn = .1f;
}
veh.of_turn = avg_of_turn * .9;
veh2.of_turn = avg_of_turn * 1.1;
//Energy after collision
float E_a = 0.5 * m1 * final1.magnitude() * final1.magnitude() +
0.5 * m2 * final2.magnitude() * final2.magnitude();
float d_E = E - E_a; //Lost energy at collision -> deformation energy
dmg = std::abs( d_E / 1000 / 2000 ); //adjust to balance damage
} else {
const float m1 = to_kilogram( veh.total_mass() );
// Collision is perfectly inelastic for simplicity
// Assume veh2 is standing still
dmg = abs( veh.vertical_velocity / 100 ) * m1 / 10;
veh.vertical_velocity = 0;
}
float dmg_veh1 = dmg * 0.5;
float dmg_veh2 = dmg * 0.5;
int coll_parts_cnt = 0; //quantity of colliding parts between veh1 and veh2
for( const auto &veh_veh_coll : collisions ) {
if( &veh2 == static_cast<vehicle *>( veh_veh_coll.target ) ) {
coll_parts_cnt++;
}
}
const float dmg1_part = dmg_veh1 / coll_parts_cnt;
const float dmg2_part = dmg_veh2 / coll_parts_cnt;
//damage colliding parts (only veh1 and veh2 parts)
for( const auto &veh_veh_coll : collisions ) {
if( &veh2 != static_cast<vehicle *>( veh_veh_coll.target ) ) {
continue;
}
int parm1 = veh.part_with_feature( veh_veh_coll.part, VPFLAG_ARMOR, true );
if( parm1 < 0 ) {
parm1 = veh_veh_coll.part;
}
int parm2 = veh2.part_with_feature( veh_veh_coll.target_part, VPFLAG_ARMOR, true );
if( parm2 < 0 ) {
parm2 = veh_veh_coll.target_part;
}
epicenter1 += veh.parts[parm1].mount;
veh.damage( parm1, dmg1_part, DT_BASH );
epicenter2 += veh2.parts[parm2].mount;
veh2.damage( parm2, dmg2_part, DT_BASH );
}
epicenter1.x /= coll_parts_cnt;
epicenter1.y /= coll_parts_cnt;
epicenter2.x /= coll_parts_cnt;
epicenter2.y /= coll_parts_cnt;
if( dmg2_part > 100 ) {
// Shake vehicle because of collision
veh2.damage_all( dmg2_part / 2, dmg2_part, DT_BASH, epicenter2 );
}
if( dmg_veh1 > 800 ) {
veh.skidding = true;
}
if( dmg_veh2 > 800 ) {
veh2.skidding = true;
}
// Return the impulse of the collision
return dmg_veh1;
}
bool map::check_vehicle_zones( const int zlev )
{
for( auto veh : get_cache( zlev ).zone_vehicles ) {
if( veh->zones_dirty ) {
return true;
}
}
return false;
}
std::vector<zone_data *> map::get_vehicle_zones( const int zlev )
{
std::vector<zone_data *> veh_zones;
bool rebuild = false;
for( auto veh : get_cache( zlev ).zone_vehicles ) {
if( veh->refresh_zones() ) {
rebuild = true;
}
for( auto &zone : veh->loot_zones ) {
veh_zones.emplace_back( &zone.second );
}
}
if( rebuild ) {
zone_manager::get_manager().cache_vzones();
}
return veh_zones;
}
void map::register_vehicle_zone( vehicle *veh, const int zlev )
{
auto &ch = get_cache( zlev );
ch.zone_vehicles.insert( veh );
}
bool map::deregister_vehicle_zone( zone_data &zone )
{
if( const cata::optional<vpart_reference> vp = g->m.veh_at( g->m.getlocal(
zone.get_start_point() ) ).part_with_feature( "CARGO", false ) ) {
auto bounds = vp->vehicle().loot_zones.equal_range( vp->mount() );
for( auto it = bounds.first; it != bounds.second; it++ ) {
if( &zone == &( it->second ) ) {
vp->vehicle().loot_zones.erase( it );
return true;
}
}
}
return false;
}
// 3D vehicle functions
VehicleList map::get_vehicles( const tripoint &start, const tripoint &end )
{
const int chunk_sx = std::max( 0, ( start.x / SEEX ) - 1 );
const int chunk_ex = std::min( my_MAPSIZE - 1, ( end.x / SEEX ) + 1 );
const int chunk_sy = std::max( 0, ( start.y / SEEY ) - 1 );
const int chunk_ey = std::min( my_MAPSIZE - 1, ( end.y / SEEY ) + 1 );
const int chunk_sz = start.z;
const int chunk_ez = end.z;
VehicleList vehs;
for( int cx = chunk_sx; cx <= chunk_ex; ++cx ) {
for( int cy = chunk_sy; cy <= chunk_ey; ++cy ) {
for( int cz = chunk_sz; cz <= chunk_ez; ++cz ) {
submap *current_submap = get_submap_at_grid( { cx, cy, cz } );
for( const auto &elem : current_submap->vehicles ) {
// Ensure the vehicle z-position is correct
elem->smz = cz;
wrapped_vehicle w;
w.v = elem.get();
w.x = w.v->posx + cx * SEEX;
w.y = w.v->posy + cy * SEEY;
w.z = cz;
w.i = cx;
w.j = cy;
vehs.push_back( w );
}
}
}
}
return vehs;
}
optional_vpart_position map::veh_at( const tripoint &p ) const
{
if( !const_cast<map *>( this )->get_cache( p.z ).veh_in_active_range || !inbounds( p ) ) {
return optional_vpart_position( cata::nullopt );
}
int part_num = 1;
vehicle *const veh = const_cast<map *>( this )->veh_at_internal( p, part_num );
if( !veh ) {
return optional_vpart_position( cata::nullopt );
}
return optional_vpart_position( vpart_position( *veh, part_num ) );
}
const vehicle *map::veh_at_internal( const tripoint &p, int &part_num ) const
{
// This function is called A LOT. Move as much out of here as possible.
const auto &ch = get_cache_ref( p.z );
if( !ch.veh_in_active_range || !ch.veh_exists_at[p.x][p.y] ) {
part_num = -1;
return nullptr; // Clear cache indicates no vehicle. This should optimize a great deal.
}
const auto it = ch.veh_cached_parts.find( p );
if( it != ch.veh_cached_parts.end() ) {
part_num = it->second.second;
return it->second.first;
}
debugmsg( "vehicle part cache indicated vehicle not found: %d %d %d", p.x, p.y, p.z );
part_num = -1;
return nullptr;
}
vehicle *map::veh_at_internal( const tripoint &p, int &part_num )
{
return const_cast<vehicle *>( const_cast<const map *>( this )->veh_at_internal( p, part_num ) );
}
void map::board_vehicle( const tripoint &pos, player *p )
{
if( p == nullptr ) {
debugmsg( "map::board_vehicle: null player" );
return;
}
const cata::optional<vpart_reference> vp = veh_at( pos ).part_with_feature( VPFLAG_BOARDABLE,
true );
if( !vp ) {
if( p->grab_point.x == 0 && p->grab_point.y == 0 ) {
debugmsg( "map::board_vehicle: vehicle not found" );
}
return;
}
if( vp->part().has_flag( vehicle_part::passenger_flag ) ) {
player *psg = vp->vehicle().get_passenger( vp->part_index() );
debugmsg( "map::board_vehicle: passenger (%s) is already there",
psg ? psg->name.c_str() : "<null>" );
unboard_vehicle( pos );
}
vp->part().set_flag( vehicle_part::passenger_flag );
vp->part().passenger_id = p->getID();
vp->vehicle().invalidate_mass();
p->setpos( pos );
p->in_vehicle = true;
if( p == &g->u ) {
g->update_map( g->u );
}
}
void map::unboard_vehicle( const tripoint &p )
{
const cata::optional<vpart_reference> vp = veh_at( p ).part_with_feature( VPFLAG_BOARDABLE, false );
player *passenger = nullptr;
if( !vp ) {
debugmsg( "map::unboard_vehicle: vehicle not found" );
// Try and force unboard the player anyway.
passenger = g->critter_at<player>( p );
if( passenger ) {
passenger->in_vehicle = false;
passenger->controlling_vehicle = false;
}
return;
}
passenger = vp->vehicle().get_passenger( vp->part_index() );
if( !passenger ) {
debugmsg( "map::unboard_vehicle: passenger not found" );
return;
}
passenger->in_vehicle = false;
passenger->controlling_vehicle = false;
vp->part().remove_flag( vehicle_part::passenger_flag );
vp->vehicle().skidding = true;
vp->vehicle().invalidate_mass();
}
vehicle *map::displace_vehicle( tripoint &p, const tripoint &dp )
{
const tripoint p2 = p + dp;
const tripoint src = p;
const tripoint dst = p2;
if( !inbounds( src ) ) {
add_msg( m_debug, "map::displace_vehicle: coordinates out of bounds %d,%d,%d->%d,%d,%d",
src.x, src.y, src.z, dst.x, dst.y, dst.z );
return nullptr;
}
point src_offset;
point dst_offset;
submap *src_submap = get_submap_at( src, src_offset );
submap *const dst_submap = get_submap_at( dst, dst_offset );
// first, let's find our position in current vehicles vector
int our_i = -1;
for( size_t i = 0; i < src_submap->vehicles.size(); i++ ) {
if( src_submap->vehicles[i]->posx == src_offset.x &&
src_submap->vehicles[i]->posy == src_offset.y ) {
our_i = i;
break;
}
}
if( our_i < 0 ) {
vehicle *v = veh_pointer_or_null( veh_at( p ) );
for( auto &smap : grid ) {
for( size_t i = 0; i < smap->vehicles.size(); i++ ) {
if( smap->vehicles[i].get() == v ) {
our_i = i;
src_submap = smap;
break;
}
}
}
}
if( our_i < 0 ) {
add_msg( m_debug, "displace_vehicle our_i=%d", our_i );
return nullptr;
}
// move the vehicle
vehicle *veh = src_submap->vehicles[our_i].get();
// don't let it go off grid
if( !inbounds( p2 ) ) {
veh->stop();
// Silent debug
dbg( D_ERROR ) << "map:displace_vehicle: Stopping vehicle, displaced dp=("
<< dp.x << ", " << dp.y << ", " << dp.z << ")";
return veh;
}
// Need old coordinates to check for remote control
const bool remote = veh->remote_controlled( g->u );
// record every passenger inside
std::vector<int> psg_parts = veh->boarded_parts();
std::vector<player *> psgs;
for( auto &prt : psg_parts ) {
psgs.push_back( veh->get_passenger( prt ) );
}
bool need_update = false;
int z_change = 0;
// Move passengers
for( size_t i = 0; i < psg_parts.size(); i++ ) {
player *psg = psgs[i];
const int prt = psg_parts[i];
const tripoint part_pos = veh->global_part_pos3( prt );
if( psg == nullptr ) {
debugmsg( "Empty passenger part %d pcoord=%d,%d,%d u=%d,%d,%d?",
prt,
part_pos.x, part_pos.y, part_pos.z,
g->u.posx(), g->u.posy(), g->u.posz() );
veh->parts[prt].remove_flag( vehicle_part::passenger_flag );
continue;
}
if( psg->pos() != part_pos ) {
add_msg( m_debug, "Passenger/part position mismatch: passenger %d,%d,%d, part %d %d,%d,%d",
g->u.posx(), g->u.posy(), g->u.posz(),
prt,
part_pos.x, part_pos.y, part_pos.z );
}
// Place passenger on the new part location
const vehicle_part &veh_part = veh->parts[prt];
tripoint psgp( part_pos.x + dp.x + veh_part.precalc[1].x - veh_part.precalc[0].x,
part_pos.y + dp.y + veh_part.precalc[1].y - veh_part.precalc[0].y,
psg->posz() );
if( psg == &g->u ) {
// If passenger is you, we need to update the map
psg->setpos( psgp );
need_update = true;
z_change = dp.z;
} else {
// Player gets z position changed by g->vertical_move()
psgp.z += dp.z;
psg->setpos( psgp );
}
}
veh->shed_loose_parts();
for( auto &prt : veh->parts ) {
prt.precalc[0] = prt.precalc[1];
}
veh->pivot_anchor[0] = veh->pivot_anchor[1];
veh->pivot_rotation[0] = veh->pivot_rotation[1];
veh->posx = dst_offset.x;
veh->posy = dst_offset.y;
veh->smz = p2.z;
// Invalidate vehicle's point cache
veh->occupied_cache_time = calendar::before_time_starts;
if( src_submap != dst_submap ) {
veh->set_submap_moved( int( p2.x / SEEX ), int( p2.y / SEEY ) );
auto src_submap_veh_it = src_submap->vehicles.begin() + our_i;
dst_submap->vehicles.push_back( std::move( *src_submap_veh_it ) );
src_submap->vehicles.erase( src_submap_veh_it );
dst_submap->is_uniform = false;
}
p = p2;
update_vehicle_cache( veh, src.z );
if( need_update ) {
g->update_map( g->u );
}
if( z_change != 0 ) {
g->vertical_move( z_change, true );
}
if( remote ) {
// Has to be after update_map or coordinates won't be valid
g->setremoteveh( veh );
}
veh->check_falling_or_floating();
//global positions of vehicle loot zones have changed.
veh->zones_dirty = true;
on_vehicle_moved( veh->smz );
return veh;
}
bool map::displace_water( const tripoint &p )
{
// Check for shallow water
if( has_flag( "SWIMMABLE", p ) && !has_flag( TFLAG_DEEP_WATER, p ) ) {
int dis_places = 0;
int sel_place = 0;
for( int pass = 0; pass < 2; pass++ ) {
// we do 2 passes.
// first, count how many non-water places around
// then choose one within count and fill it with water on second pass
if( pass != 0 ) {
sel_place = rng( 0, dis_places - 1 );
dis_places = 0;
}
for( const tripoint &temp : points_in_radius( p, 1 ) ) {
if( temp != p
|| impassable_ter_furn( temp )
|| has_flag( TFLAG_DEEP_WATER, temp ) ) {
continue;
}
ter_id ter0 = ter( temp );
if( ter0 == t_water_sh ||
ter0 == t_water_dp ) {
continue;
}
if( pass != 0 && dis_places == sel_place ) {
ter_set( temp, t_water_sh );
ter_set( temp, t_dirt );
return true;
}
dis_places++;
}
}
}
return false;
}
// End of 3D vehicle
// 2D overloads for furniture
// To be removed once not needed
void map::set( const int x, const int y, const ter_id &new_terrain, const furn_id &new_furniture )
{
furn_set( x, y, new_furniture );
ter_set( x, y, new_terrain );
}
std::string map::name( const int x, const int y )
{
return name( tripoint( x, y, abs_sub.z ) );
}
bool map::has_furn( const int x, const int y ) const
{
return furn( x, y ) != f_null;
}
furn_id map::furn( const int x, const int y ) const
{
const point p( x, y );
if( !inbounds( p ) ) {
return f_null;
}
point l;
submap *const current_submap = get_submap_at( p, l );
return current_submap->get_furn( l );
}
void map::furn_set( const int x, const int y, const furn_id &new_furniture )
{
furn_set( tripoint( x, y, abs_sub.z ), new_furniture );
}
std::string map::furnname( const int x, const int y )
{
return furnname( tripoint( x, y, abs_sub.z ) );
}
// End of 2D overloads for furniture
void map::set( const tripoint &p, const ter_id &new_terrain, const furn_id &new_furniture )
{
furn_set( p, new_furniture );
ter_set( p, new_terrain );
}
std::string map::name( const tripoint &p )
{
return has_furn( p ) ? furnname( p ) : tername( p );
}
std::string map::disp_name( const tripoint &p )
{
return string_format( _( "the %s" ), name( p ).c_str() );
}
std::string map::obstacle_name( const tripoint &p )
{
if( const cata::optional<vpart_reference> vp = veh_at( p ).obstacle_at_part() ) {
return vp->info().name();
}
return name( p );
}
bool map::has_furn( const tripoint &p ) const
{
return furn( p ) != f_null;
}
furn_id map::furn( const tripoint &p ) const
{
if( !inbounds( p ) ) {
return f_null;
}
point l;
submap *const current_submap = get_submap_at( p, l );
return current_submap->get_furn( l );
}
void map::furn_set( const tripoint &p, const furn_id &new_furniture )
{
if( !inbounds( p ) ) {
return;
}
point l;
submap *const current_submap = get_submap_at( p, l );
const furn_id old_id = current_submap->get_furn( l );
if( old_id == new_furniture ) {
// Nothing changed
return;
}
current_submap->set_furn( l, new_furniture );
// Set the dirty flags
const furn_t &old_t = old_id.obj();
const furn_t &new_t = new_furniture.obj();
// If player has grabbed this furniture and it's no longer grabbable, release the grab.
if( g->u.get_grab_type() == OBJECT_FURNITURE && g->u.grab_point == p && new_t.move_str_req < 0 ) {
add_msg( _( "The %s you were grabbing is destroyed!" ), old_t.name().c_str() );
g->u.grab( OBJECT_NONE );
}
if( old_t.transparent != new_t.transparent ) {
set_transparency_cache_dirty( p.z );
}
if( old_t.has_flag( TFLAG_INDOORS ) != new_t.has_flag( TFLAG_INDOORS ) ) {
set_outside_cache_dirty( p.z );
}
if( old_t.has_flag( TFLAG_NO_FLOOR ) != new_t.has_flag( TFLAG_NO_FLOOR ) ) {
set_floor_cache_dirty( p.z );
}
set_memory_seen_cache_dirty( p );
// @todo: Limit to changes that affect move cost, traps and stairs
set_pathfinding_cache_dirty( p.z );
// Make sure the furniture falls if it needs to
support_dirty( p );
tripoint above( p.x, p.y, p.z + 1 );
// Make sure that if we supported something and no longer do so, it falls down
support_dirty( above );
}
bool map::can_move_furniture( const tripoint &pos, player *p )
{
const furn_t &furniture_type = furn( pos ).obj();
int required_str = furniture_type.move_str_req;
// Object can not be moved (or nothing there)
if( required_str < 0 ) {
return false;
}
///\EFFECT_STR determines what furniture the player can move
if( p != nullptr && p->str_cur < required_str ) {
return false;
}
return true;
}
std::string map::furnname( const tripoint &p )
{
const furn_t &f = furn( p ).obj();
if( f.has_flag( "PLANT" ) && !i_at( p ).empty() ) {
const item &seed = i_at( p ).front();
const std::string &plant = seed.get_plant_name();
return string_format( "%s (%s)", f.name().c_str(), plant.c_str() );
} else {
return f.name();
}
}
// 2D overloads for terrain
// To be removed once not needed
ter_id map::ter( const int x, const int y ) const
{
const point p( x, y );
if( !inbounds( p ) ) {
return t_null;
}
point l;
submap *const current_submap = get_submap_at( p, l );
return current_submap->get_ter( l );
}
bool map::ter_set( const int x, const int y, const ter_id &new_terrain )
{
return ter_set( tripoint( x, y, abs_sub.z ), new_terrain );
}
std::string map::tername( const int x, const int y ) const
{
return tername( tripoint( x, y, abs_sub.z ) );
}
// End of 2D overloads for terrain
/*
* Get the terrain integer id. This is -not- a number guaranteed to remain
* the same across revisions; it is a load order, and can change when mods
* are loaded or removed. The old t_floor style constants will still work but
* are -not- guaranteed; if a mod removes t_lava, t_lava will equal t_null;
* New terrains added to the core game generally do not need this, it's
* retained for high performance comparisons, save/load, and gradual transition
* to string terrain.id
*/
ter_id map::ter( const tripoint &p ) const
{
if( !inbounds( p ) ) {
return t_null;
}
point l;
submap *const current_submap = get_submap_at( p, l );
return current_submap->get_ter( l );
}
uint8_t map::get_known_connections( const tripoint &p, int connect_group ) const
{
constexpr std::array<point, 4> offsets = {{
{ 0, 1 }, { 1, 0 }, { -1, 0 }, { 0, -1 }
}
};
auto &ch = access_cache( p.z );
bool is_transparent =
ch.transparency_cache[p.x][p.y] > LIGHT_TRANSPARENCY_SOLID;
uint8_t val = 0;
std::function<bool( const tripoint & )> is_memorized;
if( use_tiles ) {
is_memorized =
[&]( const tripoint & q ) {
return !g->u.get_memorized_tile( getabs( q ) ).tile.empty();
};
} else {
is_memorized =
[&]( const tripoint & q ) {
return g->u.get_memorized_symbol( getabs( q ) );
};
}
// populate connection information
for( int i = 0; i < 4; ++i ) {
tripoint neighbour = p + offsets[i];
if( !inbounds( neighbour ) ) {
continue;
}
if( is_transparent ||
ch.visibility_cache[neighbour.x][neighbour.y] <= LL_BRIGHT ||
is_memorized( neighbour ) ) {
const ter_t &neighbour_terrain = ter( neighbour ).obj();
if( neighbour_terrain.connects_to( connect_group ) ) {
val += 1 << i;
}
}
}
return val;
}
/*
* Get the results of harvesting this tile's furniture or terrain
*/
const harvest_id &map::get_harvest( const tripoint &pos ) const
{
const auto furn_here = furn( pos );
if( furn_here->examine != iexamine::none ) {
// Note: if furniture can be examined, the terrain can NOT (until furniture is removed)
if( furn_here->has_flag( TFLAG_HARVESTED ) ) {
return harvest_id::NULL_ID();
}
return furn_here->get_harvest();
}
const auto ter_here = ter( pos );
if( ter_here->has_flag( TFLAG_HARVESTED ) ) {
return harvest_id::NULL_ID();
}
return ter_here->get_harvest();
}
const std::set<std::string> &map::get_harvest_names( const tripoint &pos ) const
{
static const std::set<std::string> null_harvest_names = {};
const auto furn_here = furn( pos );
if( furn_here->examine != iexamine::none ) {
if( furn_here->has_flag( TFLAG_HARVESTED ) ) {
return null_harvest_names;
}
return furn_here->get_harvest_names();
}
const auto ter_here = ter( pos );
if( ter_here->has_flag( TFLAG_HARVESTED ) ) {
return null_harvest_names;
}
return ter_here->get_harvest_names();
}
/*
* Get the terrain transforms_into id (what will the terrain transforms into)
*/
ter_id map::get_ter_transforms_into( const tripoint &p ) const
{
return ter( p ).obj().transforms_into.id();
}
/**
* Examines the tile pos, with character as the "examinator"
* Casts Character to player because player/NPC split isn't done yet
*/
void map::examine( Character &p, const tripoint &pos )
{
const auto furn_here = furn( pos ).obj();
if( furn_here.examine != iexamine::none ) {
furn_here.examine( dynamic_cast<player &>( p ), pos );
} else {
ter( pos ).obj().examine( dynamic_cast<player &>( p ), pos );
}
}
bool map::is_harvestable( const tripoint &pos ) const
{
const auto &harvest_here = get_harvest( pos );
return !harvest_here.is_null() && !harvest_here->empty();
}
/*
* set terrain via string; this works for -any- terrain id
*/
bool map::ter_set( const tripoint &p, const ter_id &new_terrain )
{
if( !inbounds( p ) ) {
return false;
}
point l;
submap *const current_submap = get_submap_at( p, l );
const ter_id old_id = current_submap->get_ter( l );
if( old_id == new_terrain ) {
// Nothing changed
return false;
}
current_submap->set_ter( l, new_terrain );
// Set the dirty flags
const ter_t &old_t = old_id.obj();
const ter_t &new_t = new_terrain.obj();
// Hack around ledges in traplocs or else it gets NASTY in z-level mode
if( old_t.trap != tr_null && old_t.trap != tr_ledge ) {
auto &traps = traplocs[old_t.trap];
const auto iter = std::find( traps.begin(), traps.end(), p );
if( iter != traps.end() ) {
traps.erase( iter );
}
}
if( new_t.trap != tr_null && new_t.trap != tr_ledge ) {
traplocs[new_t.trap].push_back( p );
}
if( old_t.transparent != new_t.transparent ) {
set_transparency_cache_dirty( p.z );
}
if( old_t.has_flag( TFLAG_INDOORS ) != new_t.has_flag( TFLAG_INDOORS ) ) {
set_outside_cache_dirty( p.z );
}
if( new_t.has_flag( TFLAG_NO_FLOOR ) && !old_t.has_flag( TFLAG_NO_FLOOR ) ) {
set_floor_cache_dirty( p.z );
// It's a set, not a flag
support_cache_dirty.insert( p );
}
set_memory_seen_cache_dirty( p );
// @todo: Limit to changes that affect move cost, traps and stairs
set_pathfinding_cache_dirty( p.z );
tripoint above( p.x, p.y, p.z + 1 );
// Make sure that if we supported something and no longer do so, it falls down
support_dirty( above );
return true;
}
std::string map::tername( const tripoint &p ) const
{
return ter( p ).obj().name();
}
std::string map::features( const int x, const int y )
{
return features( tripoint( x, y, abs_sub.z ) );
}
std::string map::features( const tripoint &p )
{
// This is used in an info window that is 46 characters wide, and is expected
// to take up one line. So, make sure it does that.
// FIXME: can't control length of localized text.
std::stringstream ret;
if( is_bashable( p ) ) {
ret << _( "Smashable. " );
}
if( has_flag( "DIGGABLE", p ) ) {
ret << _( "Diggable. " );
}
if( has_flag( "PLOWABLE", p ) ) {
ret << _( "Plowable. " );
}
if( has_flag( "ROUGH", p ) ) {
ret << _( "Rough. " );
}
if( has_flag( "UNSTABLE", p ) ) {
ret << _( "Unstable. " );
}
if( has_flag( "SHARP", p ) ) {
ret << _( "Sharp. " );
}
if( has_flag( "FLAT", p ) ) {
ret << _( "Flat. " );
}
if( has_flag( "EASY_DECONSTRUCT", p ) ) {
ret << _( "Simple. " );
}
if( has_flag( "MOUNTABLE", p ) ) {
ret << _( "Mountable. " );
}
return ret.str();
}
int map::move_cost_internal( const furn_t &furniture, const ter_t &terrain, const vehicle *veh,
const int vpart ) const
{
if( terrain.movecost == 0 || ( furniture.id && furniture.movecost < 0 ) ) {
return 0;
}
if( veh != nullptr ) {
const vpart_position vp( const_cast<vehicle &>( *veh ), vpart );
if( vp.obstacle_at_part() ) {
return 0;
} else if( vp.part_with_feature( VPFLAG_AISLE, true ) ) {
return 2;
} else {
return 8;
}
}
if( furniture.id ) {
return std::max( terrain.movecost + furniture.movecost, 0 );
}
return std::max( terrain.movecost, 0 );
}
// Move cost: 2D overloads
int map::move_cost( const int x, const int y, const vehicle *ignored_vehicle ) const
{
return move_cost( tripoint( x, y, abs_sub.z ), ignored_vehicle );
}
bool map::impassable( const int x, const int y ) const
{
return !passable( x, y );
}
bool map::passable( const int x, const int y ) const
{
return passable( tripoint( x, y, abs_sub.z ) );
}
int map::move_cost_ter_furn( const int x, const int y ) const
{
const point p( x, y );
if( !inbounds( p ) ) {
return 0;
}
point l;
submap *const current_submap = get_submap_at( p, l );
const int tercost = current_submap->get_ter( l ).obj().movecost;
if( tercost == 0 ) {
return 0;
}
const int furncost = current_submap->get_furn( l ).obj().movecost;
if( furncost < 0 ) {
return 0;
}
const int cost = tercost + furncost;
return cost > 0 ? cost : 0;
}
// Move cost: 3D
int map::move_cost( const tripoint &p, const vehicle *ignored_vehicle ) const
{
if( !inbounds( p ) ) {
return 0;
}
const furn_t &furniture = furn( p ).obj();
const ter_t &terrain = ter( p ).obj();
const optional_vpart_position vp = veh_at( p );
vehicle *const veh = ( !vp || &vp->vehicle() == ignored_vehicle ) ? nullptr : &vp->vehicle();
const int part = veh ? vp->part_index() : -1;
return move_cost_internal( furniture, terrain, veh, part );
}
bool map::impassable( const tripoint &p ) const
{
return !passable( p );
}
bool map::passable( const tripoint &p ) const
{
return move_cost( p ) != 0;
}
int map::move_cost_ter_furn( const tripoint &p ) const
{
if( !inbounds( p ) ) {
return 0;
}
point l;
submap *const current_submap = get_submap_at( p, l );
const int tercost = current_submap->get_ter( l ).obj().movecost;
if( tercost == 0 ) {
return 0;
}
const int furncost = current_submap->get_furn( l ).obj().movecost;
if( furncost < 0 ) {
return 0;
}
const int cost = tercost + furncost;
return cost > 0 ? cost : 0;
}
bool map::impassable_ter_furn( const tripoint &p ) const
{
return !passable_ter_furn( p );
}
bool map::passable_ter_furn( const tripoint &p ) const
{
return move_cost_ter_furn( p ) != 0;
}
int map::combined_movecost( const tripoint &from, const tripoint &to,
const vehicle *ignored_vehicle,
const int modifier, const bool flying ) const
{
const int mults[4] = { 0, 50, 71, 100 };
const int cost1 = move_cost( from, ignored_vehicle );
const int cost2 = move_cost( to, ignored_vehicle );
// Multiply cost depending on the number of differing axes
// 0 if all axes are equal, 100% if only 1 differs, 141% for 2, 200% for 3
size_t match = trigdist ? ( from.x != to.x ) + ( from.y != to.y ) + ( from.z != to.z ) : 1;
if( flying || from.z == to.z ) {
return ( cost1 + cost2 + modifier ) * mults[match] / 2;
}
// Inter-z-level movement by foot (not flying)
if( !valid_move( from, to, false ) ) {
return 0;
}
// TODO: Penalize for using stairs
return ( cost1 + cost2 + modifier ) * mults[match] / 2;
}
bool map::valid_move( const tripoint &from, const tripoint &to,
const bool bash, const bool flying ) const
{
// Used to account for the fact that older versions of GCC can trip on the if statement here.
assert( to.z > std::numeric_limits<int>::min() );
// Note: no need to check inbounds here, because maptile_at will do that
// If oob tile is supplied, the maptile_at will be an unpassable "null" tile
if( abs( from.x - to.x ) > 1 || abs( from.y - to.y ) > 1 || abs( from.z - to.z ) > 1 ) {
return false;
}
if( from.z == to.z ) {
// But here we need to, to prevent bashing critters
return passable( to ) || ( bash && inbounds( to ) );
} else if( !zlevels ) {
return false;
}
const bool going_up = from.z < to.z;
const tripoint &up_p = going_up ? to : from;
const tripoint &down_p = going_up ? from : to;
const maptile up = maptile_at( up_p );
const ter_t &up_ter = up.get_ter_t();
// Checking for ledge is a workaround for the case when mapgen doesn't
// actually make a valid ledge drop location with zlevels on, this forces
// at least one zlevel drop and if down_ter is impassible it's probably
// inside a wall, we could workaround that further but it's unnecessary.
const bool up_is_ledge = tr_at( up_p ).loadid == tr_ledge;
if( up_ter.movecost == 0 ) {
// Unpassable tile
return false;
}
const maptile down = maptile_at( down_p );
const ter_t &down_ter = down.get_ter_t();
if( !up_is_ledge && down_ter.movecost == 0 ) {
// Unpassable tile
return false;
}
if( !up_ter.has_flag( TFLAG_NO_FLOOR ) && !up_ter.has_flag( TFLAG_GOES_DOWN ) && !up_is_ledge ) {
// Can't move from up to down
if( abs( from.x - to.x ) == 1 || abs( from.y - to.y ) == 1 ) {
// Break the move into two - vertical then horizontal
tripoint midpoint( down_p.x, down_p.y, up_p.z );
return valid_move( down_p, midpoint, bash, flying ) &&
valid_move( midpoint, up_p, bash, flying );
}
return false;
}
if( !flying && !down_ter.has_flag( TFLAG_GOES_UP ) && !down_ter.has_flag( TFLAG_RAMP ) &&
!up_is_ledge ) {
// Can't safely reach the lower tile
return false;
}
if( bash ) {
return true;
}
int part_up;
const vehicle *veh_up = veh_at_internal( up_p, part_up );
if( veh_up != nullptr ) {
// TODO: Hatches below the vehicle, passable frames
return false;
}
int part_down;
const vehicle *veh_down = veh_at_internal( down_p, part_down );
if( veh_down != nullptr && veh_down->roof_at_part( part_down ) >= 0 ) {
// TODO: OPEN (and only open) hatches from above
return false;
}
// Currently only furniture can block movement if everything else is OK
// TODO: Vehicles with boards in the given spot
return up.get_furn_t().movecost >= 0;
}
// End of move cost
double map::ranged_target_size( const tripoint &p ) const
{
if( impassable( p ) ) {
return 1.0;
}
if( !has_floor( p ) ) {
return 0.0;
}
// @todo: Handle cases like shrubs, trees, furniture, sandbags...
return 0.1;
}
int map::climb_difficulty( const tripoint &p ) const
{
if( p.z > OVERMAP_HEIGHT || p.z < -OVERMAP_DEPTH ) {
debugmsg( "climb_difficulty on out of bounds point: %d, %d, %d", p.x, p.y, p.z );
return INT_MAX;
}
int best_difficulty = INT_MAX;
int blocks_movement = 0;
if( has_flag( "LADDER", p ) ) {
// Really easy, but you have to stand on the tile
return 1;
} else if( has_flag( TFLAG_RAMP, p ) ) {
// We're on something stair-like, so halfway there already
best_difficulty = 7;
}
for( const auto &pt : points_in_radius( p, 1 ) ) {
if( impassable_ter_furn( pt ) ) {
// TODO: Non-hardcoded climbability
best_difficulty = std::min( best_difficulty, 10 );
blocks_movement++;
} else if( veh_at( pt ) ) {
// Vehicle tiles are quite good for climbing
// TODO: Penalize spiked parts?
best_difficulty = std::min( best_difficulty, 7 );
}
if( best_difficulty > 5 && has_flag( "CLIMBABLE", pt ) ) {
best_difficulty = 5;
}
}
// TODO: Make this more sensible - check opposite sides, not just movement blocker count
return best_difficulty - blocks_movement;
}
bool map::has_floor( const tripoint &p ) const
{
if( !zlevels || p.z < -OVERMAP_DEPTH + 1 || p.z > OVERMAP_HEIGHT ) {
return true;
}
if( !inbounds( p ) ) {
return true;
}
return get_cache_ref( p.z ).floor_cache[p.x][p.y];
}
bool map::supports_above( const tripoint &p ) const
{
const maptile tile = maptile_at( p );
const ter_t &ter = tile.get_ter_t();
if( ter.movecost == 0 ) {
return true;
}
const furn_id frn_id = tile.get_furn();
if( frn_id != f_null ) {
const furn_t &frn = frn_id.obj();
if( frn.movecost < 0 ) {
return true;
}
}
if( veh_at( p ) ) {
return true;
}
return false;
}
bool map::has_floor_or_support( const tripoint &p ) const
{
const tripoint below( p.x, p.y, p.z - 1 );
return !valid_move( p, below, false, true );
}
void map::drop_everything( const tripoint &p )
{
if( has_floor( p ) ) {
return;
}
drop_furniture( p );
drop_items( p );
drop_vehicle( p );
drop_fields( p );
}
void map::drop_furniture( const tripoint &p )
{
const furn_id frn = furn( p );
if( frn == f_null ) {
return;
}
enum support_state {
SS_NO_SUPPORT = 0,
SS_BAD_SUPPORT, // TODO: Implement bad, shaky support
SS_GOOD_SUPPORT,
SS_FLOOR, // Like good support, but bash floor instead of tile below
SS_CREATURE
};
// Checks if the tile:
// has floor (supports unconditionally)
// has support below
// has unsupporting furniture below (bad support, things should "slide" if possible)
// has no support and thus allows things to fall through
const auto check_tile = [this]( const tripoint & pt ) {
if( has_floor( pt ) ) {
return SS_FLOOR;
}
tripoint below_dest( pt.x, pt.y, pt.z - 1 );
if( supports_above( below_dest ) ) {
return SS_GOOD_SUPPORT;
}
const furn_id frn_id = furn( below_dest );
if( frn_id != f_null ) {
const furn_t &frn = frn_id.obj();
// Allow crushing tiny/nocollide furniture
if( !frn.has_flag( "TINY" ) && !frn.has_flag( "NOCOLLIDE" ) ) {
return SS_BAD_SUPPORT;
}
}
if( g->critter_at( below_dest ) != nullptr ) {
// Smash a critter
return SS_CREATURE;
}
return SS_NO_SUPPORT;
};
tripoint current( p.x, p.y, p.z + 1 );
support_state last_state = SS_NO_SUPPORT;
while( last_state == SS_NO_SUPPORT ) {
current.z--;
// Check current tile
last_state = check_tile( current );
}
if( current == p ) {
// Nothing happened
if( last_state != SS_FLOOR ) {
support_dirty( current );
}
return;
}
furn_set( p, f_null );
furn_set( current, frn );
// If it's sealed, we need to drop items with it
const auto &frn_obj = frn.obj();
if( frn_obj.has_flag( TFLAG_SEALED ) && has_items( p ) ) {
auto old_items = i_at( p );
auto new_items = i_at( current );
for( const auto &it : old_items ) {
new_items.push_back( it );
}
i_clear( p );
}
// Approximate weight/"bulkiness" based on strength to drag
int weight;
if( frn_obj.has_flag( "TINY" ) || frn_obj.has_flag( "NOCOLLIDE" ) ) {
weight = 5;
} else {
weight = frn_obj.move_str_req >= 0 ? frn_obj.move_str_req : 20;
}
if( frn_obj.has_flag( "ROUGH" ) || frn_obj.has_flag( "SHARP" ) ) {
weight += 5;
}
// TODO: Balance this.
int dmg = weight * ( p.z - current.z );
if( last_state == SS_FLOOR ) {
// Bash the same tile twice - once for furniture, once for the floor
bash( current, dmg, false, false, true );
bash( current, dmg, false, false, true );
} else if( last_state == SS_BAD_SUPPORT || last_state == SS_GOOD_SUPPORT ) {
bash( current, dmg, false, false, false );
tripoint below( current.x, current.y, current.z - 1 );
bash( below, dmg, false, false, false );
} else if( last_state == SS_CREATURE ) {
const std::string &furn_name = frn_obj.name();
bash( current, dmg, false, false, false );
tripoint below( current.x, current.y, current.z - 1 );
Creature *critter = g->critter_at( below );
if( critter == nullptr ) {
debugmsg( "drop_furniture couldn't find creature at %d,%d,%d",
below.x, below.y, below.z );
return;
}
critter->add_msg_player_or_npc( m_bad, _( "Falling %s hits you!" ),
_( "Falling %s hits <npcname>" ),
furn_name.c_str() );
// TODO: A chance to dodge/uncanny dodge
player *pl = dynamic_cast<player *>( critter );
monster *mon = dynamic_cast<monster *>( critter );
if( pl != nullptr ) {
pl->deal_damage( nullptr, bp_torso, damage_instance( DT_BASH, rng( dmg / 3, dmg ), 0, 0.5f ) );
pl->deal_damage( nullptr, bp_head, damage_instance( DT_BASH, rng( dmg / 3, dmg ), 0, 0.5f ) );
pl->deal_damage( nullptr, bp_leg_l, damage_instance( DT_BASH, rng( dmg / 2, dmg ), 0, 0.4f ) );
pl->deal_damage( nullptr, bp_leg_r, damage_instance( DT_BASH, rng( dmg / 2, dmg ), 0, 0.4f ) );
pl->deal_damage( nullptr, bp_arm_l, damage_instance( DT_BASH, rng( dmg / 2, dmg ), 0, 0.4f ) );
pl->deal_damage( nullptr, bp_arm_r, damage_instance( DT_BASH, rng( dmg / 2, dmg ), 0, 0.4f ) );
} else if( mon != nullptr ) {
// TODO: Monster's armor and size - don't crush hulks with chairs
mon->apply_damage( nullptr, bp_torso, rng( dmg, dmg * 2 ) );
}
}
// Re-queue for another check, in case bash destroyed something
support_dirty( current );
}
void map::drop_items( const tripoint &p )
{
if( !has_items( p ) ) {
return;
}
auto items = i_at( p );
// TODO: Make items check the volume tile below can accept
// rather than disappearing if it would be overloaded
tripoint below( p );
while( !has_floor( below ) ) {
below.z--;
}
if( below == p ) {
return;
}
for( const auto &i : items ) {
// TODO: Bash the item up before adding it
// TODO: Bash the creature, terrain, furniture and vehicles on the tile
add_item_or_charges( below, i );
}
// Just to make a sound for now
bash( below, 1 );
i_clear( p );
}
void map::drop_vehicle( const tripoint &p )
{
const optional_vpart_position vp = veh_at( p );
if( !vp ) {
return;
}
vp->vehicle().is_falling = true;
}
void map::drop_fields( const tripoint &p )
{
field &fld = field_at( p );
if( fld.fieldCount() == 0 ) {
return;
}
std::list<field_id> dropped;
const tripoint below = p - tripoint( 0, 0, 1 );
for( const auto &iter : fld ) {
const field_entry &entry = iter.second;
// For now only drop cosmetic fields, which don't warrant per-turn check
// Active fields "drop themselves"
if( entry.decays_on_actualize() ) {
add_field( below, entry.getFieldType(), entry.getFieldDensity(), entry.getFieldAge() );
dropped.push_back( entry.getFieldType() );
}
}
for( const auto &entry : dropped ) {
fld.removeField( entry );
}
}
void map::support_dirty( const tripoint &p )
{
if( zlevels ) {
support_cache_dirty.insert( p );
}
}
void map::process_falling()
{
if( !zlevels ) {
support_cache_dirty.clear();
return;
}
if( !support_cache_dirty.empty() ) {
add_msg( m_debug, "Checking %d tiles for falling objects",
support_cache_dirty.size() );
// We want the cache to stay constant, but falling can change it
std::set<tripoint> last_cache = std::move( support_cache_dirty );
support_cache_dirty.clear();
for( const tripoint &p : last_cache ) {
drop_everything( p );
}
}
}
// 2D flags
bool map::has_flag( const std::string &flag, const int x, const int y ) const
{
return has_flag( flag, tripoint( x, y, abs_sub.z ) );
}
bool map::can_put_items_ter_furn( const int x, const int y ) const
{
return !has_flag( "NOITEM", x, y ) && !has_flag( "SEALED", x, y );
}
bool map::has_flag_ter( const std::string &flag, const int x, const int y ) const
{
return has_flag_ter( flag, tripoint( x, y, abs_sub.z ) );
}
bool map::has_flag_furn( const std::string &flag, const int x, const int y ) const
{
return has_flag_furn( flag, tripoint( x, y, abs_sub.z ) );
}
bool map::has_flag_ter_or_furn( const std::string &flag, const int x, const int y ) const
{
const point p( x, y );
if( !inbounds( p ) ) {
return false;
}
point l;
submap *const current_submap = get_submap_at( p, l );
return current_submap->get_ter( l ).obj().has_flag( flag ) ||
current_submap->get_furn( l ).obj().has_flag( flag );
}
/////
bool map::has_flag( const ter_bitflags flag, const int x, const int y ) const
{
return has_flag( flag, tripoint( x, y, abs_sub.z ) );
}
bool map::has_flag_ter( const ter_bitflags flag, const int x, const int y ) const
{
return has_flag_ter( flag, tripoint( x, y, abs_sub.z ) );
}
bool map::has_flag_furn( const ter_bitflags flag, const int x, const int y ) const
{
return has_flag_furn( flag, tripoint( x, y, abs_sub.z ) );
}
bool map::has_flag_ter_or_furn( const ter_bitflags flag, const int x, const int y ) const
{
const point p( x, y );
if( !inbounds( p ) ) {
return false;
}
point l;
submap *const current_submap = get_submap_at( p, l );
return current_submap->get_ter( l ).obj().has_flag( flag ) ||
current_submap->get_furn( l ).obj().has_flag( flag );
}
// End of 2D flags
bool map::has_flag( const std::string &flag, const tripoint &p ) const
{
return has_flag_ter_or_furn( flag, p ); // Does bound checking
}
bool map::can_put_items( const tripoint &p ) const
{
if( can_put_items_ter_furn( p ) ) {
return true;
}
const optional_vpart_position vp = veh_at( p );
return static_cast<bool>( vp.part_with_feature( "CARGO", true ) );
}
bool map::can_put_items_ter_furn( const tripoint &p ) const
{
return !has_flag( "NOITEM", p ) && !has_flag( "SEALED", p );
}
bool map::has_flag_ter( const std::string &flag, const tripoint &p ) const
{
return ter( p ).obj().has_flag( flag );
}
bool map::has_flag_furn( const std::string &flag, const tripoint &p ) const
{
return furn( p ).obj().has_flag( flag );
}
bool map::has_flag_ter_or_furn( const std::string &flag, const tripoint &p ) const
{
if( !inbounds( p ) ) {
return false;
}
point l;
submap *const current_submap = get_submap_at( p, l );
return current_submap->get_ter( l ).obj().has_flag( flag ) ||
current_submap->get_furn( l ).obj().has_flag( flag );
}
bool map::has_flag( const ter_bitflags flag, const tripoint &p ) const
{
return has_flag_ter_or_furn( flag, p ); // Does bound checking
}
bool map::has_flag_ter( const ter_bitflags flag, const tripoint &p ) const
{
return ter( p ).obj().has_flag( flag );
}
bool map::has_flag_furn( const ter_bitflags flag, const tripoint &p ) const
{
return furn( p ).obj().has_flag( flag );
}
bool map::has_flag_ter_or_furn( const ter_bitflags flag, const tripoint &p ) const
{
if( !inbounds( p ) ) {
return false;
}
point l;
submap *const current_submap = get_submap_at( p, l );
return current_submap->get_ter( l ).obj().has_flag( flag ) ||
current_submap->get_furn( l ).obj().has_flag( flag );
}
// End of 3D flags
// Bashable - common function
int map::bash_rating_internal( const int str, const furn_t &furniture,
const ter_t &terrain, const bool allow_floor,
const vehicle *veh, const int part ) const
{
bool furn_smash = false;
bool ter_smash = false;
///\EFFECT_STR determines what furniture can be smashed
if( furniture.id && furniture.bash.str_max != -1 ) {
furn_smash = true;
///\EFFECT_STR determines what terrain can be smashed
} else if( terrain.bash.str_max != -1 && ( !terrain.bash.bash_below || allow_floor ) ) {
ter_smash = true;
}
if( veh != nullptr && vpart_position( const_cast<vehicle &>( *veh ), part ).obstacle_at_part() ) {
// Monsters only care about rating > 0, NPCs should want to path around cars instead
return 2; // Should probably be a function of part hp (+armor on tile)
}
int bash_min = 0;
int bash_max = 0;
if( furn_smash ) {
bash_min = furniture.bash.str_min;
bash_max = furniture.bash.str_max;
} else if( ter_smash ) {
bash_min = terrain.bash.str_min;
bash_max = terrain.bash.str_max;
} else {
return -1;
}
///\EFFECT_STR increases smashing damage
if( str < bash_min ) {
return 0;
} else if( str >= bash_max ) {
return 10;
}
int ret = ( 10 * ( str - bash_min ) ) / ( bash_max - bash_min );
// Round up to 1, so that desperate NPCs can try to bash down walls
return std::max( ret, 1 );
}
// 2D bashable
bool map::is_bashable( const int x, const int y ) const
{
return is_bashable( tripoint( x, y, abs_sub.z ) );
}
bool map::is_bashable_ter( const int x, const int y ) const
{
return is_bashable_ter( tripoint( x, y, abs_sub.z ) );
}
bool map::is_bashable_furn( const int x, const int y ) const
{
return is_bashable_furn( tripoint( x, y, abs_sub.z ) );
}
bool map::is_bashable_ter_furn( const int x, const int y ) const
{
return is_bashable_ter_furn( tripoint( x, y, abs_sub.z ) );
}
int map::bash_strength( const int x, const int y ) const
{
return bash_strength( tripoint( x, y, abs_sub.z ) );
}
int map::bash_resistance( const int x, const int y ) const
{
return bash_resistance( tripoint( x, y, abs_sub.z ) );
}
int map::bash_rating( const int str, const int x, const int y ) const
{
return bash_rating( str, tripoint( x, y, abs_sub.z ) );
}
// 3D bashable
bool map::is_bashable( const tripoint &p, const bool allow_floor ) const
{
if( !inbounds( p ) ) {
DebugLog( D_WARNING, D_MAP ) << "Looking for out-of-bounds is_bashable at "
<< p.x << ", " << p.y << ", " << p.z;
return false;
}
if( veh_at( p ).obstacle_at_part() ) {
return true;
}
if( has_furn( p ) && furn( p ).obj().bash.str_max != -1 ) {
return true;
}
const auto &ter_bash = ter( p ).obj().bash;
return ter_bash.str_max != -1 && ( !ter_bash.bash_below || allow_floor );
}
bool map::is_bashable_ter( const tripoint &p, const bool allow_floor ) const
{
const auto &ter_bash = ter( p ).obj().bash;
return ter_bash.str_max != -1 && ( !ter_bash.bash_below || allow_floor );
}
bool map::is_bashable_furn( const tripoint &p ) const
{
return has_furn( p ) && furn( p ).obj().bash.str_max != -1;
}
bool map::is_bashable_ter_furn( const tripoint &p, const bool allow_floor ) const
{
return is_bashable_furn( p ) || is_bashable_ter( p, allow_floor );
}
int map::bash_strength( const tripoint &p, const bool allow_floor ) const
{
if( has_furn( p ) && furn( p ).obj().bash.str_max != -1 ) {
return furn( p ).obj().bash.str_max;
}
const auto &ter_bash = ter( p ).obj().bash;
if( ter_bash.str_max != -1 && ( !ter_bash.bash_below || allow_floor ) ) {
return ter_bash.str_max;
}
return -1;
}
int map::bash_resistance( const tripoint &p, const bool allow_floor ) const
{
if( has_furn( p ) && furn( p ).obj().bash.str_min != -1 ) {
return furn( p ).obj().bash.str_min;
}
const auto &ter_bash = ter( p ).obj().bash;
if( ter_bash.str_min != -1 && ( !ter_bash.bash_below || allow_floor ) ) {
return ter_bash.str_min;
}
return -1;
}
int map::bash_rating( const int str, const tripoint &p, const bool allow_floor ) const
{
if( !inbounds( p ) ) {
DebugLog( D_WARNING, D_MAP ) << "Looking for out-of-bounds is_bashable at "
<< p.x << ", " << p.y << ", " << p.z;
return -1;
}
if( str <= 0 ) {
return -1;
}
const furn_t &furniture = furn( p ).obj();
const ter_t &terrain = ter( p ).obj();
const optional_vpart_position vp = veh_at( p );
vehicle *const veh = vp ? &vp->vehicle() : nullptr;
const int part = vp ? vp->part_index() : -1;
return bash_rating_internal( str, furniture, terrain, allow_floor, veh, part );
}
// End of 3D bashable
void map::make_rubble( const tripoint &p )
{
make_rubble( p, f_rubble, false, t_dirt, false );
}
void map::make_rubble( const tripoint &p, const furn_id &rubble_type, const bool items )
{
make_rubble( p, rubble_type, items, t_dirt, false );
}
void map::make_rubble( const tripoint &p, const furn_id &rubble_type, const bool items,
const ter_id &floor_type, bool overwrite )
{
if( overwrite ) {
ter_set( p, floor_type );
furn_set( p, rubble_type );
} else {
// First see if there is existing furniture to destroy
if( is_bashable_furn( p ) ) {
destroy_furn( p, true );
}
// Leave the terrain alone unless it interferes with furniture placement
if( impassable( p ) && is_bashable_ter( p ) ) {
destroy( p, true );
}
// Check again for new terrain after potential destruction
if( impassable( p ) ) {
ter_set( p, floor_type );
}
furn_set( p, rubble_type );
}
if( !items ) {
return;
}
//Still hardcoded, but a step up from the old stuff due to being in only one place
if( rubble_type == f_wreckage ) {
item chunk( "steel_chunk", calendar::turn );
item scrap( "scrap", calendar::turn );
add_item_or_charges( p, chunk );
add_item_or_charges( p, scrap );
if( one_in( 5 ) ) {
item pipe( "pipe", calendar::turn );
item wire( "wire", calendar::turn );
add_item_or_charges( p, pipe );
add_item_or_charges( p, wire );
}
} else if( rubble_type == f_rubble_rock ) {
item rock( "rock", calendar::turn );
int rock_count = rng( 1, 3 );
for( int i = 0; i < rock_count; i++ ) {
add_item_or_charges( p, rock );
}
} else if( rubble_type == f_rubble ) {
item splinter( "splinter", calendar::turn );
item nail( "nail", calendar::turn );
int splinter_count = rng( 2, 8 );
int nail_count = rng( 5, 10 );
for( int i = 0; i < splinter_count; i++ ) {
add_item_or_charges( p, splinter );
}
for( int i = 0; i < nail_count; i++ ) {
add_item_or_charges( p, nail );
}
}
}
/**
* Returns whether or not the terrain at the given location can be dived into
* (by monsters that can swim or are aquatic or non-breathing).
* @param x The x coordinate to look at.
* @param y The y coordinate to look at.
* @return true if the terrain can be dived into; false if not.
*/
bool map::is_divable( const int x, const int y ) const
{
return has_flag( "SWIMMABLE", x, y ) && has_flag( TFLAG_DEEP_WATER, x, y );
}
bool map::is_divable( const tripoint &p ) const
{
return has_flag( "SWIMMABLE", p ) && has_flag( TFLAG_DEEP_WATER, p );
}
bool map::is_outside( const int x, const int y ) const
{
const point p( x, y );
if( !inbounds( p ) ) {
return true;
}
const auto &outside_cache = get_cache_ref( abs_sub.z ).outside_cache;
return outside_cache[x][y];
}
bool map::is_outside( const tripoint &p ) const
{
if( !inbounds( p ) ) {
return true;
}
const auto &outside_cache = get_cache_ref( p.z ).outside_cache;
return outside_cache[p.x][p.y];
}
bool map::is_last_ter_wall( const bool no_furn, const int x, const int y,
const int xmax, const int ymax, const direction dir ) const
{
int xmov = 0;
int ymov = 0;
switch( dir ) {
case NORTH:
ymov = -1;
break;
case SOUTH:
ymov = 1;
break;
case WEST:
xmov = -1;
break;
case EAST:
xmov = 1;
break;
default:
break;
}
int x2 = x;
int y2 = y;
bool result = true;
bool loop = true;
while( ( loop ) && ( ( dir == NORTH && y2 >= 0 ) ||
( dir == SOUTH && y2 < ymax ) ||
( dir == WEST && x2 >= 0 ) ||
( dir == EAST && x2 < xmax ) ) ) {
if( no_furn && has_furn( x2, y2 ) ) {
loop = false;
result = false;
} else if( !has_flag_ter( "FLAT", x2, y2 ) ) {
loop = false;
if( !has_flag_ter( "WALL", x2, y2 ) ) {
result = false;
}
}
x2 += xmov;
y2 += ymov;
}
return result;
}
bool map::flammable_items_at( const tripoint &p, int threshold )
{
if( !has_items( p ) ||
( has_flag( TFLAG_SEALED, p ) && !has_flag( TFLAG_ALLOW_FIELD_EFFECT, p ) ) ) {
// Sealed containers don't allow fire, so shouldn't allow setting the fire either
return false;
}
for( const auto &i : i_at( p ) ) {
if( i.flammable( threshold ) ) {
return true;
}
}
return false;
}
void map::decay_fields_and_scent( const time_duration &amount )
{
// Decay scent separately, so that later we can use field count to skip empty submaps
tripoint tmp;
tmp.z = abs_sub.z; // TODO: Make this happen on all z-levels
g->scent.decay();
const time_duration amount_fire = amount / 3; // Decay fire by this much
const time_duration amount_liquid = amount / 2; // Decay washable fields (blood, guts etc.) by this
const time_duration amount_gas = amount / 5; // Decay gas type fields by this
// Coordinate code copied from lightmap calculations
// TODO: Z
const int smz = abs_sub.z;
const auto &outside_cache = get_cache_ref( smz ).outside_cache;
for( int smx = 0; smx < my_MAPSIZE; ++smx ) {
for( int smy = 0; smy < my_MAPSIZE; ++smy ) {
const auto cur_submap = get_submap_at_grid( { smx, smy, smz } );
int to_proc = cur_submap->field_count;
if( to_proc < 1 ) {
if( to_proc < 0 ) {
cur_submap->field_count = 0;
dbg( D_ERROR ) << "map::decay_fields_and_scent: submap at "
<< abs_sub.x + smx << "," << abs_sub.y + smy << "," << abs_sub.z
<< "has " << to_proc << " field_count";
}
// This submap has no fields
continue;
}
for( int sx = 0; sx < SEEX; ++sx ) {
if( to_proc < 1 ) {
// This submap had some fields, but all got proc'd already
break;
}
for( int sy = 0; sy < SEEY; ++sy ) {
const int x = sx + smx * SEEX;
const int y = sy + smy * SEEY;
field &fields = cur_submap->fld[sx][sy];
if( !outside_cache[x][y] ) {
to_proc -= fields.fieldCount();
continue;
}
for( auto &fp : fields ) {
to_proc--;
field_entry &cur = fp.second;
const field_id type = cur.getFieldType();
switch( type ) {
case fd_fire:
cur.setFieldAge( cur.getFieldAge() + amount_fire );
break;
case fd_blood:
case fd_bile:
case fd_gibs_flesh:
case fd_gibs_veggy:
case fd_slime:
case fd_blood_veggy:
case fd_blood_insect:
case fd_blood_invertebrate:
case fd_gibs_insect:
case fd_gibs_invertebrate:
cur.setFieldAge( cur.getFieldAge() + amount_liquid );
break;
case fd_smoke:
case fd_toxic_gas:
case fd_fungicidal_gas:
case fd_tear_gas:
case fd_nuke_gas:
case fd_cigsmoke:
case fd_weedsmoke:
case fd_cracksmoke:
case fd_methsmoke:
case fd_relax_gas:
case fd_fungal_haze:
case fd_hot_air1:
case fd_hot_air2:
case fd_hot_air3:
case fd_hot_air4:
cur.setFieldAge( cur.getFieldAge() + amount_gas );
break;
default:
break;
}
}
}
}
if( to_proc > 0 ) {
cur_submap->field_count = cur_submap->field_count - to_proc;
dbg( D_ERROR ) << "map::decay_fields_and_scent: submap at "
<< abs_sub.x + smx << "," << abs_sub.y + smy << "," << abs_sub.z
<< "has " << cur_submap->field_count - to_proc << "fields, but "
<< cur_submap->field_count << " field_count";
}
}
}
}
point map::random_outdoor_tile()
{
std::vector<point> options;
for( int x = 0; x < SEEX * my_MAPSIZE; x++ ) {
for( int y = 0; y < SEEY * my_MAPSIZE; y++ ) {
if( is_outside( x, y ) ) {
options.push_back( point( x, y ) );
}
}
}
return random_entry( options, point( -1, -1 ) );
}
bool map::has_adjacent_furniture( const tripoint &p )
{
const signed char cx[4] = { 0, -1, 0, 1};
const signed char cy[4] = { -1, 0, 1, 0};
for( int i = 0; i < 4; i++ ) {
const int adj_x = p.x + cx[i];
const int adj_y = p.y + cy[i];
if( has_furn( tripoint( adj_x, adj_y, p.z ) ) &&
furn( tripoint( adj_x, adj_y, p.z ) ).obj().has_flag( "BLOCKSDOOR" ) ) {
return true;
}
}
return false;
}
bool map::has_nearby_fire( const tripoint &p, int radius )
{
for( const tripoint &pt : points_in_radius( p, radius ) ) {
if( get_field( pt, fd_fire ) != nullptr ) {
return true;
}
if( ter( pt ) == t_lava ) {
return true;
}
}
return false;
}
bool map::mop_spills( const tripoint &p )
{
bool retval = false;
if( !has_flag( "LIQUIDCONT", p ) ) {
auto items = i_at( p );
auto new_end = std::remove_if( items.begin(), items.end(), []( const item & it ) {
return it.made_of( LIQUID );
} );
retval = new_end != items.end();
while( new_end != items.end() ) {
new_end = items.erase( new_end );
}
}
field &fld = field_at( p );
static const std::vector<field_id> to_check = {
fd_blood,
fd_blood_veggy,
fd_blood_insect,
fd_blood_invertebrate,
fd_gibs_flesh,
fd_gibs_veggy,
fd_gibs_insect,
fd_gibs_invertebrate,
fd_bile,
fd_slime,
fd_sludge
};
for( field_id fid : to_check ) {
retval |= fld.removeField( fid );
}
if( const optional_vpart_position vp = veh_at( p ) ) {
vehicle *const veh = &vp->vehicle();
std::vector<int> parts_here = veh->parts_at_relative( vp->mount(), true );
for( auto &elem : parts_here ) {
if( veh->parts[elem].blood > 0 ) {
veh->parts[elem].blood = 0;
retval = true;
}
//remove any liquids that somehow didn't fall through to the ground
vehicle_stack here = veh->get_items( elem );
auto new_end = std::remove_if( here.begin(), here.end(), []( const item & it ) {
return it.made_of( LIQUID );
} );
retval |= ( new_end != here.end() );
while( new_end != here.end() ) {
new_end = here.erase( new_end );
}
}
} // if veh != 0
return retval;
}
int map::collapse_check( const tripoint &p )
{
const bool collapses = has_flag( "COLLAPSES", p );
const bool supports_roof = has_flag( "SUPPORTS_ROOF", p );
int num_supports = 0;
for( const tripoint &t : points_in_radius( p, 1 ) ) {
if( p == t ) {
continue;
}
if( collapses ) {
if( has_flag( "COLLAPSES", t ) ) {
num_supports++;
} else if( has_flag( "SUPPORTS_ROOF", t ) ) {
num_supports += 2;
}
} else if( supports_roof ) {
if( has_flag( "SUPPORTS_ROOF", t ) && !has_flag( "COLLAPSES", t ) ) {
num_supports += 3;
}
}
}
return 1.7 * num_supports;
}
void map::collapse_at( const tripoint &p, const bool silent )
{
destroy( p, silent );
crush( p );
make_rubble( p );
for( const tripoint &t : points_in_radius( p, 1 ) ) {
if( p == t ) {
continue;
}
if( has_flag( "COLLAPSES", t ) && one_in( collapse_check( t ) ) ) {
destroy( t, silent );
// We only check for rubble spread if it doesn't already collapse to prevent double crushing
} else if( has_flag( "FLAT", t ) && one_in( 8 ) ) {
crush( t );
make_rubble( t );
}
}
}
void map::smash_items( const tripoint &p, const int power )
{
if( !has_items( p ) ) {
return;
}
std::vector<item> contents;
auto items = g->m.i_at( p );
for( auto i = items.begin(); i != items.end(); ) {
if( i->active ) {
// Get the explosion item actor
if( i->type->get_use( "explosion" ) != nullptr ) {
const explosion_iuse *actor = dynamic_cast<const explosion_iuse *>(
i->type->get_use( "explosion" )->get_actor_ptr() );
if( actor != nullptr ) {
// If we're looking at another bomb, don't blow it up early for now.
// i++ here because we aren't iterating in the loop header.
i++;
continue;
}
}
}
const float material_factor = i->chip_resistance( true );
if( power < material_factor ) {
i++;
continue;
}
// The volume check here pretty much only influences corpses and very large items
const float volume_factor = std::max<float>( 40, i->volume() / units::legacy_volume_factor );
float damage_chance = 10.0f * power / volume_factor;
// Example:
// Power 40 (just below C4 epicenter) vs two-by-four
// damage_chance = 10 * 40 / 40 = 10, material_factor = 8
// Will deal 1 damage, then 20% chance for another point
// Power 20 (grenade minus shrapnel) vs glass bottle
// 10 * 20 / 40 = 5 vs 1
// 5 damage (destruction)
const bool by_charges = i->count_by_charges();
// See if they were damaged
if( by_charges ) {
damage_chance *= i->charges_per_volume( units::from_milliliter( 250 ) );
while( ( damage_chance > material_factor ||
x_in_y( damage_chance, material_factor ) ) &&
i->charges > 0 ) {
i->charges--;
damage_chance -= material_factor;
}
} else {
const field_id type_blood = i->is_corpse() ? i->get_mtype()->bloodType() : fd_null;
while( ( damage_chance > material_factor ||
x_in_y( damage_chance, material_factor ) ) &&
i->damage() < i->max_damage() ) {
i->inc_damage( DT_BASH );
add_splash( type_blood, p, 1, damage_chance );
damage_chance -= material_factor;
}
}
// Remove them if they were damaged too much
if( i->damage() == i->max_damage() || ( by_charges && i->charges == 0 ) ) {
// But save the contents, except for irremovable gunmods
for( auto &elem : i->contents ) {
if( !elem.is_irremovable() ) {
contents.push_back( elem );
}
}
i = i_rem( p, i );
} else {
i++;
}
}
for( const item &it : contents ) {
add_item_or_charges( p, it );
}
}
ter_id map::get_roof( const tripoint &p, const bool allow_air )
{
// This function should not be called from the 2D mode
// Just use t_dirt instead
assert( zlevels );
if( p.z <= -OVERMAP_DEPTH ) {
// Could be magma/"void" instead
return t_rock_floor;
}
const auto &ter_there = ter( p ).obj();
const auto &roof = ter_there.roof;
if( !roof ) {
// No roof
// Not acceptable if the tile is not passable
if( !allow_air ) {
return t_dirt;
}
return t_open_air;
}
ter_id new_ter = roof.id();
if( new_ter == t_null ) {
debugmsg( "map::get_new_floor: %d,%d,%d has invalid roof type %s",
p.x, p.y, p.z, roof.c_str() );
return t_dirt;
}
if( p.z == -1 && new_ter == t_rock_floor ) {
// A hack to work around not having a "solid earth" tile
new_ter = t_dirt;
}
return new_ter;
}
void map::bash_ter_furn( const tripoint &p, bash_params &params )
{
std::string sound;
int sound_volume = 0;
std::string soundfxid;
std::string soundfxvariant;
const auto &terid = ter( p ).obj();
const auto &furnid = furn( p ).obj();
bool smash_furn = false;
bool smash_ter = false;
const map_bash_info *bash = nullptr;
bool success = false;
if( has_furn( p ) && furnid.bash.str_max != -1 ) {
bash = &furnid.bash;
smash_furn = true;
} else if( ter( p ).obj().bash.str_max != -1 ) {
bash = &ter( p ).obj().bash;
smash_ter = true;
}
// Floor bashing check
// Only allow bashing floors when we want to bash floors and we're in z-level mode
// Unless we're destroying, then it gets a little weird
if( smash_ter && bash->bash_below && ( !zlevels || !params.bash_floor ) ) {
if( !params.destroy ) {
smash_ter = false;
bash = nullptr;
} else if( !bash->ter_set && zlevels ) {
// A hack for destroy && !bash_floor
// We have to check what would we create and cancel if it is what we have now
tripoint below( p.x, p.y, p.z - 1 );
const auto roof = get_roof( below, false );
if( roof == ter( p ) ) {
smash_ter = false;
bash = nullptr;
}
} else if( !bash->ter_set && ter( p ) == t_dirt ) {
// As above, except for no-z-levels case
smash_ter = false;
bash = nullptr;
}
}
// TODO: what if silent is true?
if( has_flag( "ALARMED", p ) && !g->events.queued( EVENT_WANTED ) ) {
sounds::sound( p, 40, sounds::sound_t::alarm, _( "an alarm go off!" ),
false, "environment", "alarm" );
// Blame nearby player
if( rl_dist( g->u.pos(), p ) <= 3 ) {
g->u.add_memorial_log( pgettext( "memorial_male", "Set off an alarm." ),
pgettext( "memorial_female", "Set off an alarm." ) );
const point abs = ms_to_sm_copy( getabs( p.x, p.y ) );
g->events.add( EVENT_WANTED, calendar::turn + 30_minutes, 0, tripoint( abs.x, abs.y, p.z ) );
}
}
if( bash == nullptr || ( bash->destroy_only && !params.destroy ) ) {
// Nothing bashable here
if( impassable( p ) ) {
if( !params.silent ) {
sounds::sound( p, 18, sounds::sound_t::combat, _( "thump!" ),
false, "smash_thump", "smash_success" );
}
params.did_bash = true;
params.bashed_solid = true;
}
return;
}
int smin = bash->str_min;
int smax = bash->str_max;
int sound_vol = bash->sound_vol;
int sound_fail_vol = bash->sound_fail_vol;
if( !params.destroy ) {
if( bash->str_min_blocked != -1 || bash->str_max_blocked != -1 ) {
if( has_adjacent_furniture( p ) ) {
if( bash->str_min_blocked != -1 ) {
smin = bash->str_min_blocked;
}
if( bash->str_max_blocked != -1 ) {
smax = bash->str_max_blocked;
}
}
}
if( bash->str_min_supported != -1 || bash->str_max_supported != -1 ) {
tripoint below( p.x, p.y, p.z - 1 );
if( !zlevels || has_flag( "SUPPORTS_ROOF", below ) ) {
if( bash->str_min_supported != -1 ) {
smin = bash->str_min_supported;
}
if( bash->str_max_supported != -1 ) {
smax = bash->str_max_supported;
}
}
}
// Linear interpolation from str_min to str_max
const int resistance = smin + ( params.roll * ( smax - smin ) );
if( params.strength >= resistance ) {
success = true;
}
}
if( smash_furn ) {
soundfxvariant = furnid.id.str();
} else {
soundfxvariant = terid.id.str();
}
if( !params.destroy && !success ) {
if( sound_fail_vol == -1 ) {
sound_volume = 12;
} else {
sound_volume = sound_fail_vol;
}
sound = bash->sound_fail.empty() ? _( "Thnk!" ) : _( bash->sound_fail.c_str() );
params.did_bash = true;
if( !params.silent ) {
sounds::sound( p, sound_volume, sounds::sound_t::combat, sound, false,
"smash_fail", soundfxvariant );
}
return;
}
// Clear out any partially grown seeds
if( has_flag_ter_or_furn( "PLANT", p ) ) {
i_clear( p );
}
if( ( smash_furn && has_flag_furn( "FUNGUS", p ) ) ||
( smash_ter && has_flag_ter( "FUNGUS", p ) ) ) {
fungal_effects( *g, *this ).create_spores( p );
}
if( params.destroy ) {
sound_volume = smin * 2;
} else {
if( sound_vol == -1 ) {
sound_volume = std::min( int( smin * 1.5 ), smax );
} else {
sound_volume = sound_vol;
}
}
soundfxid = "smash_success";
sound = _( bash->sound.c_str() );
// Set this now in case the ter_set below changes this
const bool collapses = smash_ter && has_flag( "COLLAPSES", p );
const bool supports = smash_ter && has_flag( "SUPPORTS_ROOF", p );
const bool tent = smash_furn && !bash->tent_centers.empty();
// Special code to collapse the tent if destroyed
if( tent ) {
// Get ids of possible centers
std::set<furn_id> centers;
for( const auto &cur_id : bash->tent_centers ) {
if( cur_id.is_valid() ) {
centers.insert( cur_id );
}
}
cata::optional<std::pair<tripoint, furn_id>> tentp;
// Find the center of the tent
// First check if we're not currently bashing the center
if( centers.count( furn( p ) ) > 0 ) {
tentp.emplace( p, furn( p ) );
} else {
for( const tripoint &pt : points_in_radius( p, bash->collapse_radius ) ) {
const furn_id &f_at = furn( pt );
// Check if we found the center of the current tent
if( centers.count( f_at ) > 0 ) {
tentp.emplace( pt, f_at );
break;
}
}
}
// Didn't find any tent center, wreck the current tile
if( !tentp ) {
spawn_items( p, item_group::items_from( bash->drop_group, calendar::turn ) );
furn_set( p, bash->furn_set );
} else {
// Take the tent down
const int rad = tentp->second.obj().bash.collapse_radius;
for( const tripoint &pt : points_in_radius( tentp->first, rad ) ) {
const auto frn = furn( pt );
if( frn == f_null ) {
continue;
}
const auto recur_bash = &frn.obj().bash;
// Check if we share a center type and thus a "tent type"
for( const auto &cur_id : recur_bash->tent_centers ) {
if( centers.count( cur_id.id() ) > 0 ) {
// Found same center, wreck current tile
spawn_items( p, item_group::items_from( recur_bash->drop_group, calendar::turn ) );
furn_set( pt, recur_bash->furn_set );
break;
}
}
}
}
soundfxvariant = "smash_cloth";
} else if( smash_furn ) {
furn_set( p, bash->furn_set );
for( item &it : i_at( p ) ) {
it.on_drop( p, *this );
}
// Hack alert.
// Signs have cosmetics associated with them on the submap since
// furniture can't store dynamic data to disk. To prevent writing
// mysteriously appearing for a sign later built here, remove the
// writing from the submap.
delete_signage( p );
} else if( !smash_ter ) {
// Handle error earlier so that we can assume smash_ter is true below
debugmsg( "data/json/terrain.json does not have %s.bash.ter_set set!",
ter( p ).obj().id.c_str() );
} else if( params.bashing_from_above && bash->ter_set_bashed_from_above ) {
// If this terrain is being bashed from above and this terrain
// has a valid post-destroy bashed-from-above terrain, set it
ter_set( p, bash->ter_set_bashed_from_above );
} else if( bash->ter_set ) {
// If the terrain has a valid post-destroy terrain, set it
ter_set( p, bash->ter_set );
} else {
tripoint below( p.x, p.y, p.z - 1 );
const auto &ter_below = ter( below ).obj();
if( bash->bash_below && ter_below.has_flag( "SUPPORTS_ROOF" ) ) {
// When bashing the tile below, don't allow bashing the floor
bash_params params_below = params; // Make a copy
params_below.bashing_from_above = true;
bash_ter_furn( below, params_below );
}
ter_set( p, t_open_air );
}
if( !tent ) {
spawn_items( p, item_group::items_from( bash->drop_group, calendar::turn ) );
}
if( smash_ter && ter( p ) == t_open_air ) {
if( !zlevels ) {
// We destroyed something, so we aren't just "plugging" air with dirt here
ter_set( p, t_dirt );
} else {
tripoint below( p.x, p.y, p.z - 1 );
const auto roof = get_roof( below, params.bash_floor && ter( below ).obj().movecost != 0 );
ter_set( p, roof );
}
}
if( bash->explosive > 0 ) {
g->explosion( p, bash->explosive, 0.8, false );
}
if( collapses ) {
collapse_at( p, params.silent );
}
// Check the flag again to ensure the new terrain doesn't support anything
if( supports && !has_flag( "SUPPORTS_ROOF", p ) ) {
for( const tripoint &t : points_in_radius( p, 1 ) ) {
if( p == t || !has_flag( "COLLAPSES", t ) ) {
continue;
}
if( one_in( collapse_check( t ) ) ) {
collapse_at( t, params.silent );
}
}
}
params.did_bash = true;
params.success |= success; // Not always true, so that we can tell when to stop destroying
params.bashed_solid = true;
if( !sound.empty() && !params.silent ) {
sounds::sound( p, sound_volume, sounds::sound_t::combat, sound, false,
soundfxid, soundfxvariant );
}
}
bash_params map::bash( const tripoint &p, const int str,
bool silent, bool destroy, bool bash_floor,
const vehicle *bashing_vehicle )
{
bash_params bsh{
str, silent, destroy, bash_floor, static_cast<float>( rng_float( 0, 1.0f ) ), false, false, false, false
};
if( !inbounds( p ) ) {
return bsh;
}
bash_field( p, bsh );
bash_items( p, bsh );
// Don't bash the vehicle doing the bashing
const vehicle *veh = veh_pointer_or_null( veh_at( p ) );
if( veh != nullptr && veh != bashing_vehicle ) {
bash_vehicle( p, bsh );
}
// If we still didn't bash anything solid (a vehicle), bash furn/ter
if( !bsh.bashed_solid ) {
bash_ter_furn( p, bsh );
}
return bsh;
}
void map::bash_items( const tripoint &p, bash_params &params )
{
if( !has_items( p ) ) {
return;
}
std::vector<item> smashed_contents;
auto bashed_items = i_at( p );
bool smashed_glass = false;
for( auto bashed_item = bashed_items.begin(); bashed_item != bashed_items.end(); ) {
// the check for active suppresses Molotovs smashing themselves with their own explosion
if( bashed_item->made_of( material_id( "glass" ) ) && !bashed_item->active && one_in( 2 ) ) {
params.did_bash = true;
smashed_glass = true;
for( const item &bashed_content : bashed_item->contents ) {
smashed_contents.push_back( bashed_content );
}
bashed_item = bashed_items.erase( bashed_item );
} else {
++bashed_item;
}
}
// Now plunk in the contents of the smashed items.
spawn_items( p, smashed_contents );
// Add a glass sound even when something else also breaks
if( smashed_glass && !params.silent ) {
sounds::sound( p, 12, sounds::sound_t::combat, _( "glass shattering" ), false,
"smash_success", "smash_glass_contents" );
}
}
void map::bash_vehicle( const tripoint &p, bash_params &params )
{
// Smash vehicle if present
if( const optional_vpart_position vp = veh_at( p ) ) {
vp->vehicle().damage( vp->part_index(), params.strength, DT_BASH );
if( !params.silent ) {
sounds::sound( p, 18, sounds::sound_t::combat, _( "crash!" ), false,
"smash_success", "hit_vehicle" );
}
params.did_bash = true;
params.success = true;
params.bashed_solid = true;
}
}
void map::bash_field( const tripoint &p, bash_params &params )
{
if( get_field( p, fd_web ) != nullptr ) {
params.did_bash = true;
params.bashed_solid = true; // To prevent bashing furniture/vehicles
remove_field( p, fd_web );
}
}
void map::destroy( const tripoint &p, const bool silent )
{
// Break if it takes more than 25 destructions to remove to prevent infinite loops
// Example: A bashes to B, B bashes to A leads to A->B->A->...
int count = 0;
while( count <= 25 && bash( p, 999, silent, true ).success ) {
count++;
}
}
void map::destroy_furn( const tripoint &p, const bool silent )
{
// Break if it takes more than 25 destructions to remove to prevent infinite loops
// Example: A bashes to B, B bashes to A leads to A->B->A->...
int count = 0;
while( count <= 25 && furn( p ) != f_null && bash( p, 999, silent, true ).success ) {
count++;
}
}
void map::crush( const tripoint &p )
{
player *crushed_player = g->critter_at<player>( p );
if( crushed_player != nullptr ) {
bool player_inside = false;
if( crushed_player->in_vehicle ) {
const optional_vpart_position vp = veh_at( p );
player_inside = vp && vp->is_inside();
}
if( !player_inside ) { //If there's a player at p and he's not in a covered vehicle...
//This is the roof coming down on top of us, no chance to dodge
crushed_player->add_msg_player_or_npc( m_bad, _( "You are crushed by the falling debris!" ),
_( "<npcname> is crushed by the falling debris!" ) );
// TODO: Make this depend on the ceiling material
const int dam = rng( 0, 40 );
// Torso and head take the brunt of the blow
body_part hit = bp_head;
crushed_player->deal_damage( nullptr, hit, damage_instance( DT_BASH, dam * .25 ) );
hit = bp_torso;
crushed_player->deal_damage( nullptr, hit, damage_instance( DT_BASH, dam * .45 ) );
// Legs take the next most through transferred force
hit = bp_leg_l;
crushed_player->deal_damage( nullptr, hit, damage_instance( DT_BASH, dam * .10 ) );
hit = bp_leg_r;
crushed_player->deal_damage( nullptr, hit, damage_instance( DT_BASH, dam * .10 ) );
// Arms take the least
hit = bp_arm_l;
crushed_player->deal_damage( nullptr, hit, damage_instance( DT_BASH, dam * .05 ) );
hit = bp_arm_r;
crushed_player->deal_damage( nullptr, hit, damage_instance( DT_BASH, dam * .05 ) );
// Pin whoever got hit
crushed_player->add_effect( effect_crushed, 1_turns, num_bp, true );
crushed_player->check_dead_state();
}
}
if( monster *const monhit = g->critter_at<monster>( p ) ) {
// 25 ~= 60 * .45 (torso)
monhit->deal_damage( nullptr, bp_torso, damage_instance( DT_BASH, rng( 0, 25 ) ) );
// Pin whoever got hit
monhit->add_effect( effect_crushed, 1_turns, num_bp, true );
monhit->check_dead_state();
}
if( const optional_vpart_position vp = veh_at( p ) ) {
// Arbitrary number is better than collapsing house roof crushing APCs
vp->vehicle().damage( vp->part_index(), rng( 100, 1000 ), DT_BASH, false );
}
}
void map::shoot( const tripoint &p, projectile &proj, const bool hit_items )
{
// TODO: Make bashing count fully, but other types much less
const float initial_damage = proj.impact.total_damage();
if( initial_damage < 0 ) {
return;
}
float dam = initial_damage;
const auto &ammo_effects = proj.proj_effects;
if( has_flag( "ALARMED", p ) && !g->events.queued( EVENT_WANTED ) ) {
sounds::sound( p, 30, sounds::sound_t::alarm, _( "an alarm sound!" ) );
const tripoint abs = ms_to_sm_copy( getabs( p ) );
g->events.add( EVENT_WANTED, calendar::turn + 30_minutes, 0, abs );
}
const bool inc = ( ammo_effects.count( "INCENDIARY" ) || ammo_effects.count( "FLAME" ) );
if( const optional_vpart_position vp = veh_at( p ) ) {
dam = vp->vehicle().damage( vp->part_index(), dam, inc ? DT_HEAT : DT_STAB, hit_items );
}
const auto break_glass = []( const tripoint & p, int vol ) {
sounds::sound( p, vol, sounds::sound_t::combat, _( "glass breaking!" ), false,
"smash", "glass" );
};
ter_id terrain = ter( p );
if( terrain == t_wall_wood_broken ||
terrain == t_wall_log_broken ||
terrain == t_door_b ) {
if( hit_items || one_in( 8 ) ) { // 1 in 8 chance of hitting the door
dam -= rng( 20, 40 );
if( dam > 0 ) {
sounds::sound( p, 10, sounds::sound_t::combat, _( "crash!" ), false,
"smash", "wall" );
ter_set( p, t_dirt );
}
} else {
dam -= rng( 0, 1 );
}
} else if( terrain == t_door_c ||
terrain == t_door_locked ||
terrain == t_door_locked_peep ||
terrain == t_door_locked_alarm ) {
dam -= rng( 15, 30 );
if( dam > 0 ) {
sounds::sound( p, 10, sounds::sound_t::combat, _( "smash!" ), false, "smash", "door" );
ter_set( p, t_door_b );
}
} else if( terrain == t_door_boarded ||
terrain == t_door_boarded_damaged ||
terrain == t_rdoor_boarded ||
terrain == t_rdoor_boarded_damaged ) {
dam -= rng( 15, 35 );
if( dam > 0 ) {
sounds::sound( p, 10, sounds::sound_t::combat, _( "crash!" ), false,
"smash", "door_boarded" );
ter_set( p, t_door_b );
}
} else if( terrain == t_window_domestic_taped ||
terrain == t_curtains ||
terrain == t_window_domestic ) {
if( ammo_effects.count( "LASER" ) ) {
if( terrain == t_window_domestic_taped ||
terrain == t_curtains ) {
dam -= rng( 1, 5 );
}
dam -= rng( 0, 5 );
} else {
dam -= rng( 1, 3 );
if( dam > 0 ) {
break_glass( p, 16 );
ter_set( p, t_window_frame );
spawn_item( p, "sheet", 1 );
spawn_item( p, "stick" );
spawn_item( p, "string_36" );
}
}
} else if( terrain == t_window_taped ||
terrain == t_window_alarm_taped ||
terrain == t_window ||
terrain == t_window_no_curtains ||
terrain == t_window_no_curtains_taped ||
terrain == t_window_alarm ) {
if( ammo_effects.count( "LASER" ) ) {
if( terrain == t_window_taped ||
terrain == t_window_alarm_taped ||
terrain == t_window_no_curtains_taped ) {
dam -= rng( 1, 5 );
}
dam -= rng( 0, 5 );
} else {
dam -= rng( 1, 3 );
if( dam > 0 ) {
break_glass( p, 16 );
ter_set( p, t_window_frame );
}
}
} else if( terrain == t_window_bars_alarm ) {
dam -= rng( 1, 3 );
if( dam > 0 ) {
break_glass( p, 16 );
ter_set( p, t_window_bars );
spawn_item( p, "glass_shard", 5 );
}
} else if( terrain == t_window_boarded ) {
dam -= rng( 10, 30 );
if( dam > 0 ) {
break_glass( p, 16 );
ter_set( p, t_window_frame );
}
} else if( terrain == t_wall_glass ||
terrain == t_wall_glass_alarm ||
terrain == t_door_glass_c ) {
if( ammo_effects.count( "LASER" ) ) {
dam -= rng( 0, 5 );
} else {
dam -= rng( 1, 8 );
if( dam > 0 ) {
break_glass( p, 16 );
ter_set( p, t_floor );
}
}
} else if( terrain == t_reinforced_glass || terrain == t_reinforced_door_glass_c ) {
// reinforced glass stops most bullets
// laser beams are attenuated
if( ammo_effects.count( "LASER" ) ) {
dam -= rng( 0, 8 );
} else {
//Greatly weakens power of bullets
dam -= 40;
if( dam <= 0 && g->u.sees( p ) ) {
if( terrain == t_reinforced_glass ) {
add_msg( _( "The shot is stopped by the reinforced glass wall!" ) );
} else {
add_msg( _( "The shot is stopped by the reinforced glass door!" ) );
}
} else if( dam >= 40 ) {
//high powered bullets penetrate the glass, but only extremely strong
// ones (80 before reduction) actually destroy the glass itself.
break_glass( p, 16 );
ter_set( p, t_floor );
}
}
} else if( terrain == t_paper ) {
dam -= rng( 4, 16 );
if( dam > 0 ) {
sounds::sound( p, 8, sounds::sound_t::combat, _( "rrrrip!" ) );
ter_set( p, t_dirt );
}
if( inc ) {
add_field( p, fd_fire, 1 );
}
} else if( terrain == t_gas_pump ) {
if( hit_items || one_in( 3 ) ) {
if( dam > 15 ) {
if( inc ) {
g->explosion( p, 40, 0.8, true );
} else {
for( const tripoint &pt : points_in_radius( p, 2 ) ) {
if( one_in( 3 ) && passable( pt ) ) {
int gas_amount = rng( 10, 100 );
item gas_spill( "gasoline", calendar::turn );
gas_spill.charges = gas_amount;
add_item_or_charges( pt, gas_spill );
}
}
sounds::sound( p, 10, sounds::sound_t::combat, _( "smash!" ) );
}
ter_set( p, t_gas_pump_smashed );
}
dam -= 60;
}
} else if( terrain == t_vat ) {
if( dam >= 10 ) {
sounds::sound( p, 20, sounds::sound_t::combat, _( "ke-rash!" ) );
ter_set( p, t_floor );
} else {
dam = 0;
}
} else if( impassable( p ) && !trans( p ) ) {
bash( p, dam, false );
dam = 0; // TODO: Preserve some residual damage when it makes sense.
}
if( ammo_effects.count( "TRAIL" ) && !one_in( 4 ) ) {
add_field( p, fd_smoke, rng( 1, 2 ) );
}
if( ammo_effects.count( "STREAM" ) && !one_in( 3 ) ) {
add_field( p, fd_fire, rng( 1, 2 ) );
}
if( ammo_effects.count( "STREAM_GAS_FUNGICIDAL" ) && !one_in( 3 ) ) {
add_field( p, fd_fungicidal_gas, rng( 1, 2 ) );
}
if( ammo_effects.count( "STREAM_BIG" ) && !one_in( 4 ) ) {
add_field( p, fd_fire, 2 );
}
if( ammo_effects.count( "LIGHTNING" ) ) {
add_field( p, fd_electricity, rng( 2, 3 ) );
}
if( ammo_effects.count( "PLASMA" ) && one_in( 2 ) ) {
add_field( p, fd_plasma, rng( 1, 2 ) );
}
if( ammo_effects.count( "LASER" ) || ammo_effects.count( "DRAW_LASER_BEAM" ) ) {
add_field( p, fd_laser, 2 );
}
dam = std::max( 0.0f, dam );
// Check fields?
const field_entry *fieldhit = get_field( p, fd_web );
if( fieldhit != nullptr ) {
if( inc ) {
add_field( p, fd_fire, fieldhit->getFieldDensity() - 1 );
} else if( dam > 5 + fieldhit->getFieldDensity() * 5 &&
one_in( 5 - fieldhit->getFieldDensity() ) ) {
dam -= rng( 1, 2 + fieldhit->getFieldDensity() * 2 );
remove_field( p, fd_web );
}
}
// Rescale the damage
if( dam <= 0 ) {
proj.impact.damage_units.clear();
return;
} else if( dam < initial_damage ) {
proj.impact.mult_damage( dam / static_cast<double>( initial_damage ) );
}
// Now, destroy items on that tile.
if( ( move_cost( p ) == 2 && !hit_items ) || !inbounds( p ) ) {
return; // Items on floor-type spaces won't be shot up.
}
// dam / 3, because bullets aren't all that good at destroying items...
smash_items( p, dam / 3 );
}
bool map::hit_with_acid( const tripoint &p )
{
if( passable( p ) ) {
return false; // Didn't hit the tile!
}
const ter_id t = ter( p );
if( t == t_wall_glass || t == t_wall_glass_alarm ||
t == t_vat ) {
ter_set( p, t_floor );
} else if( t == t_door_c || t == t_door_locked || t == t_door_locked_peep ||
t == t_door_locked_alarm ) {
if( one_in( 3 ) ) {
ter_set( p, t_door_b );
}
} else if( t == t_door_bar_c || t == t_door_bar_o || t == t_door_bar_locked || t == t_bars ||
t == t_reb_cage ) {
ter_set( p, t_floor );
add_msg( m_warning, _( "The metal bars melt!" ) );
} else if( t == t_door_b ) {
if( one_in( 4 ) ) {
ter_set( p, t_door_frame );
} else {
return false;
}
} else if( t == t_window || t == t_window_alarm || t == t_window_no_curtains ) {
ter_set( p, t_window_empty );
} else if( t == t_wax ) {
ter_set( p, t_floor_wax );
} else if( t == t_gas_pump || t == t_gas_pump_smashed ) {
return false;
} else if( t == t_card_science || t == t_card_military ) {
ter_set( p, t_card_reader_broken );
}
return true;
}
// returns true if terrain stops fire
bool map::hit_with_fire( const tripoint &p )
{
if( passable( p ) ) {
return false; // Didn't hit the tile!
}
// non passable but flammable terrain, set it on fire
if( has_flag( "FLAMMABLE", p ) || has_flag( "FLAMMABLE_ASH", p ) ) {
add_field( p, fd_fire, 3 );
}
return true;
}
bool map::open_door( const tripoint &p, const bool inside, const bool check_only )
{
const auto &ter = this->ter( p ).obj();
const auto &furn = this->furn( p ).obj();
if( ter.open ) {
if( has_flag( "OPENCLOSE_INSIDE", p ) && !inside ) {
return false;
}
if( !check_only ) {
sounds::sound( p, 6, sounds::sound_t::movement, _( "swish" ), true,
"open_door", ter.id.str() );
ter_set( p, ter.open );
if( ( g->u.has_trait( trait_id( "SCHIZOPHRENIC" ) ) || g->u.has_artifact_with( AEP_SCHIZO ) )
&& one_in( 50 ) && !ter.has_flag( "TRANSPARENT" ) ) {
tripoint mp = p + tripoint( ( p.x - g->u.pos().x ) * 2, ( p.y - g->u.pos().y ) * 2, p.z );
g->spawn_hallucination( mp );
}
}
return true;
} else if( furn.open ) {
if( has_flag( "OPENCLOSE_INSIDE", p ) && !inside ) {
return false;
}
if( !check_only ) {
sounds::sound( p, 6, sounds::sound_t::movement, _( "swish" ), true,
"open_door", furn.id.str() );
furn_set( p, furn.open );
}
return true;
} else if( const optional_vpart_position vp = veh_at( p ) ) {
int openable = vp->vehicle().next_part_to_open( vp->part_index(), true );
if( openable >= 0 ) {
if( !check_only ) {
vp->vehicle().open_all_at( openable );
}
return true;
}
return false;
}
return false;
}
void map::translate( const ter_id &from, const ter_id &to )
{
if( from == to ) {
debugmsg( "map::translate %s => %s",
from.obj().name().c_str(),
from.obj().name().c_str() );
return;
}
tripoint p( 0, 0, abs_sub.z );
int &x = p.x;
int &y = p.y;
for( x = 0; x < SEEX * my_MAPSIZE; x++ ) {
for( y = 0; y < SEEY * my_MAPSIZE; y++ ) {
if( ter( p ) == from ) {
ter_set( p, to );
}
}
}
}
//This function performs the translate function within a given radius of the player.
void map::translate_radius( const ter_id &from, const ter_id &to, float radi, const tripoint &p,
const bool same_submap )
{
if( from == to ) {
debugmsg( "map::translate %s => %s",
from.obj().name().c_str(),
from.obj().name().c_str() );
return;
}
const int uX = p.x;
const int uY = p.y;
tripoint t( 0, 0, abs_sub.z );
int &x = t.x;
int &y = t.y;
for( x = 0; x < SEEX * my_MAPSIZE; x++ ) {
for( y = 0; y < SEEY * my_MAPSIZE; y++ ) {
if( ter( t ) == from ) {
float radiX = sqrt( float( ( uX - x ) * ( uX - x ) + ( uY - y ) * ( uY - y ) ) );
// within distance, and either no submap limitation or same overmap coords.
if( radiX <= radi && ( !same_submap ||
ms_to_omt_copy( getabs( x, y ) ) == ms_to_omt_copy( getabs( uX, uY ) ) ) ) {
ter_set( t, to );
}
}
}
}
}
bool map::close_door( const tripoint &p, const bool inside, const bool check_only )
{
if( has_flag( "OPENCLOSE_INSIDE", p ) && !inside ) {
return false;
}
const auto &ter = this->ter( p ).obj();
const auto &furn = this->furn( p ).obj();
if( ter.close && !furn.id ) {
if( !check_only ) {
sounds::sound( p, 10, sounds::sound_t::movement, _( "swish" ), true,
"close_door", ter.id.str() );
ter_set( p, ter.close );
}
return true;
} else if( furn.close ) {
if( !check_only ) {
sounds::sound( p, 10, sounds::sound_t::movement, _( "swish" ), true,
"close_door", furn.id.str() );
furn_set( p, furn.close );
}
return true;
}
return false;
}
const std::string map::get_signage( const tripoint &p ) const
{
if( !inbounds( p ) ) {
return "";
}
point l;
submap *const current_submap = get_submap_at( p, l );
return current_submap->get_signage( l );
}
void map::set_signage( const tripoint &p, const std::string &message ) const
{
if( !inbounds( p ) ) {
return;
}
point l;
submap *const current_submap = get_submap_at( p, l );
current_submap->set_signage( l, message );
}
void map::delete_signage( const tripoint &p ) const
{
if( !inbounds( p ) ) {
return;
}
point l;
submap *const current_submap = get_submap_at( p, l );
current_submap->delete_signage( l );
}
int map::get_radiation( const tripoint &p ) const
{
if( !inbounds( p ) ) {
return 0;
}
point l;
submap *const current_submap = get_submap_at( p, l );
return current_submap->get_radiation( l );
}
void map::set_radiation( const int x, const int y, const int value )
{
set_radiation( tripoint( x, y, abs_sub.z ), value );
}
void map::set_radiation( const tripoint &p, const int value )
{
if( !inbounds( p ) ) {
return;
}
point l;
submap *const current_submap = get_submap_at( p, l );
current_submap->set_radiation( l, value );
}
void map::adjust_radiation( const int x, const int y, const int delta )
{
adjust_radiation( tripoint( x, y, abs_sub.z ), delta );
}
void map::adjust_radiation( const tripoint &p, const int delta )
{
if( !inbounds( p ) ) {
return;
}
point l;
submap *const current_submap = get_submap_at( p, l );
int current_radiation = current_submap->get_radiation( l );
current_submap->set_radiation( l, current_radiation + delta );
}
int &map::temperature( const tripoint &p )
{
if( !inbounds( p ) ) {
null_temperature = 0;
return null_temperature;
}
return get_submap_at( p )->temperature;
}
void map::set_temperature( const tripoint &p, int new_temperature )
{
temperature( p ) = new_temperature;
temperature( tripoint( p.x + SEEX, p.y, p.z ) ) = new_temperature;
temperature( tripoint( p.x, p.y + SEEY, p.z ) ) = new_temperature;
temperature( tripoint( p.x + SEEX, p.y + SEEY, p.z ) ) = new_temperature;
}
void map::set_temperature( const int x, const int y, int new_temperature )
{
set_temperature( tripoint( x, y, abs_sub.z ), new_temperature );
}
// Items: 2D
map_stack map::i_at( const int x, const int y )
{
const point p( x, y );
if( !inbounds( p ) ) {
nulitems.clear();
return map_stack{ &nulitems, tripoint( p, abs_sub.z ), this };
}
point l;
submap *const current_submap = get_submap_at( p, l );
return map_stack{ &current_submap->itm[l.x][l.y], tripoint( p, abs_sub.z ), this };
}
std::list<item>::iterator map::i_rem( const point &location, std::list<item>::iterator it )
{
return i_rem( tripoint( location, abs_sub.z ), it );
}
int map::i_rem( const int x, const int y, const int index )
{
return i_rem( tripoint( x, y, abs_sub.z ), index );
}
void map::i_rem( const int x, const int y, item *it )
{
i_rem( tripoint( x, y, abs_sub.z ), it );
}
void map::i_clear( const int x, const int y )
{
i_clear( tripoint( x, y, abs_sub.z ) );
}
void map::spawn_an_item( const int x, const int y, item new_item,
const long charges, const int damlevel )
{
spawn_an_item( tripoint( x, y, abs_sub.z ), new_item, charges, damlevel );
}
void map::spawn_items( const int x, const int y, const std::vector<item> &new_items )
{
spawn_items( tripoint( x, y, abs_sub.z ), new_items );
}
void map::spawn_item( const int x, const int y, const std::string &type_id,
const unsigned quantity, const long charges,
const time_point &birthday, const int damlevel )
{
spawn_item( tripoint( x, y, abs_sub.z ), type_id,
quantity, charges, birthday, damlevel );
}
item &map::add_item_or_charges( const int x, const int y, item obj, bool overflow )
{
return add_item_or_charges( tripoint( x, y, abs_sub.z ), obj, overflow );
}
void map::add_item( const int x, const int y, item new_item )
{
add_item( tripoint( x, y, abs_sub.z ), new_item );
}
// Items: 3D
map_stack map::i_at( const tripoint &p )
{
if( !inbounds( p ) ) {
nulitems.clear();
return map_stack{ &nulitems, p, this };
}
point l;
submap *const current_submap = get_submap_at( p, l );
return map_stack{ &current_submap->itm[l.x][l.y], p, this };
}
std::list<item>::iterator map::i_rem( const tripoint &p, std::list<item>::iterator it )
{
point l;
submap *const current_submap = get_submap_at( p, l );
if( current_submap->active_items.has( it, l ) ) {
current_submap->active_items.remove( it, l );
}
current_submap->update_lum_rem( l, *it );
return current_submap->itm[l.x][l.y].erase( it );
}
int map::i_rem( const tripoint &p, const int index )
{
if( index < 0 ) {
debugmsg( "i_rem called with negative index %d", index );
return index;
}
if( index >= static_cast<int>( i_at( p ).size() ) ) {
return index;
}
auto map_items = i_at( p );
auto iter = map_items.begin();
std::advance( iter, index );
map_items.erase( iter );
return index;
}
void map::i_rem( const tripoint &p, const item *it )
{
auto map_items = i_at( p );
for( auto iter = map_items.begin(); iter != map_items.end(); iter++ ) {
//delete the item if the pointer memory addresses are the same
if( it == &*iter ) {
map_items.erase( iter );
break;
}
}
}
void map::i_clear( const tripoint &p )
{
point l;
submap *const current_submap = get_submap_at( p, l );
for( auto item_it = current_submap->itm[l.x][l.y].begin();
item_it != current_submap->itm[l.x][l.y].end(); ++item_it ) {
if( current_submap->active_items.has( item_it, l ) ) {
current_submap->active_items.remove( item_it, l );
}
}
current_submap->lum[l.x][l.y] = 0;
current_submap->itm[l.x][l.y].clear();
}
item &map::spawn_an_item( const tripoint &p, item new_item,
const long charges, const int damlevel )
{
if( charges && new_item.charges > 0 ) {
//let's fail silently if we specify charges for an item that doesn't support it
new_item.charges = charges;
}
new_item = new_item.in_its_container();
if( ( new_item.made_of( LIQUID ) && has_flag( "SWIMMABLE", p ) ) ||
has_flag( "DESTROY_ITEM", p ) ) {
return null_item_reference();
}
new_item.set_damage( damlevel );
return add_item_or_charges( p, new_item );
}
std::vector<item *> map::spawn_items( const tripoint &p, const std::vector<item> &new_items )
{
std::vector<item *> ret;
if( !inbounds( p ) || has_flag( "DESTROY_ITEM", p ) ) {
return ret;
}
const bool swimmable = has_flag( "SWIMMABLE", p );
for( const item &new_item : new_items ) {
if( new_item.made_of( LIQUID ) && swimmable ) {
continue;
}
item &it = add_item_or_charges( p, new_item );
if( !it.is_null() ) {
ret.push_back( &it );
}
}
return ret;
}
void map::spawn_artifact( const tripoint &p )
{
add_item_or_charges( p, item( new_artifact(), 0 ) );
}
void map::spawn_natural_artifact( const tripoint &p, artifact_natural_property prop )
{
add_item_or_charges( p, item( new_natural_artifact( prop ), 0 ) );
}
void map::spawn_item( const tripoint &p, const std::string &type_id,
const unsigned quantity, const long charges,
const time_point &birthday, const int damlevel )
{
if( type_id == "null" ) {
return;
}
if( item_is_blacklisted( type_id ) ) {
return;
}
// recurse to spawn (quantity - 1) items
for( size_t i = 1; i < quantity; i++ ) {
spawn_item( p, type_id, 1, charges, birthday, damlevel );
}
// spawn the item
item new_item( type_id, birthday );
if( one_in( 3 ) && new_item.has_flag( "VARSIZE" ) ) {
new_item.item_tags.insert( "FIT" );
}
spawn_an_item( p, new_item, charges, damlevel );
}
units::volume map::max_volume( const tripoint &p )
{
return i_at( p ).max_volume();
}
// total volume of all the things
units::volume map::stored_volume( const tripoint &p )
{
return i_at( p ).stored_volume();
}
// free space
units::volume map::free_volume( const tripoint &p )
{
return i_at( p ).free_volume();
}
item &map::add_item_or_charges( const tripoint &pos, item obj, bool overflow )
{
// Checks if item would not be destroyed if added to this tile
auto valid_tile = [&]( const tripoint & e ) {
if( !inbounds( e ) ) {
dbg( D_INFO ) << e; // should never happen
return false;
}
// Some tiles destroy items (e.g. lava)
if( has_flag( "DESTROY_ITEM", e ) ) {
return false;
}
// Cannot drop liquids into tiles that are comprised of liquid
if( obj.made_of_from_type( LIQUID ) && has_flag( "SWIMMABLE", e ) ) {
return false;
}
return true;
};
// Checks if sufficient space at tile to add item
auto valid_limits = [&]( const tripoint & e ) {
return obj.volume() <= free_volume( e ) && i_at( e ).size() < MAX_ITEM_IN_SQUARE;
};
// Performs the actual insertion of the object onto the map
auto place_item = [&]( const tripoint & tile ) -> item& {
if( obj.count_by_charges() )
{
for( auto &e : i_at( tile ) ) {
if( e.merge_charges( obj ) ) {
return e;
}
}
}
support_dirty( tile );
return add_item( tile, obj );
};
// Some items never exist on map as a discrete item (must be contained by another item)
if( obj.has_flag( "NO_DROP" ) ) {
return null_item_reference();
}
// If intended drop tile destroys the item then we don't attempt to overflow
if( !valid_tile( pos ) ) {
return null_item_reference();
}
if( ( !has_flag( "NOITEM", pos ) || ( has_flag( "LIQUIDCONT", pos ) && obj.made_of( LIQUID ) ) )
&& valid_limits( pos ) ) {
// Pass map into on_drop, because this map may not be the global map object (in mapgen, for instance).
if( obj.on_drop( pos, *this ) ) {
return null_item_reference();
}
// If tile can contain items place here...
return place_item( pos );
} else if( overflow ) {
// ...otherwise try to overflow to adjacent tiles (if permitted)
auto tiles = closest_tripoints_first( 2, pos );
tiles.erase( tiles.begin() ); // we already tried this position
for( const auto &e : tiles ) {
if( !inbounds( e ) ) {
continue;
}
if( obj.on_drop( e, *this ) ) {
return null_item_reference();
}
if( !valid_tile( e ) || has_flag( "NOITEM", e ) || !valid_limits( e ) ) {
continue;
}
return place_item( e );
}
}
// failed due to lack of space at target tile (+/- overflow tiles)
return null_item_reference();
}
item &map::add_item( const tripoint &p, item new_item )
{
if( !inbounds( p ) ) {
return null_item_reference();
}
point l;
submap *const current_submap = get_submap_at( p, l );
// Process foods when they are added to the map, here instead of add_item_at()
// to avoid double processing food during active item processing.
if( /*new_item.needs_processing() &&*/ new_item.is_food() ) {
new_item.process( nullptr, p, false );
}
return add_item_at( p, current_submap->itm[l.x][l.y].end(), new_item );
}
item &map::add_item_at( const tripoint &p,
std::list<item>::iterator index, item new_item )
{
if( new_item.made_of( LIQUID ) && has_flag( "SWIMMABLE", p ) ) {
return null_item_reference();
}
if( has_flag( "DESTROY_ITEM", p ) ) {
return null_item_reference();
}
if( new_item.has_flag( "ACT_IN_FIRE" ) && get_field( p, fd_fire ) != nullptr ) {
new_item.active = true;
}
point l;
submap *const current_submap = get_submap_at( p, l );
current_submap->is_uniform = false;
if( new_item.is_map() && !new_item.has_var( "reveal_map_center_omt" ) ) {
new_item.set_var( "reveal_map_center_omt", ms_to_omt_copy( g->m.getabs( p ) ) );
}
current_submap->update_lum_add( l, new_item );
const auto new_pos = current_submap->itm[l.x][l.y].insert( index, new_item );
if( new_item.needs_processing() ) {
current_submap->active_items.add( new_pos, l );
}
return *new_pos;
}
item map::water_from( const tripoint &p )
{
if( has_flag( "SALT_WATER", p ) ) {
return item( "salt_water", 0, item::INFINITE_CHARGES );
}
const ter_id terrain_id = g->m.ter( p );
if( terrain_id == t_sewage ) {
item ret( "water_sewage", 0, item::INFINITE_CHARGES );
ret.poison = rng( 1, 7 );
return ret;
}
item ret( "water", 0, item::INFINITE_CHARGES );
if( terrain_id == t_water_sh ) {
if( one_in( 3 ) ) {
ret.poison = rng( 1, 4 );
}
return ret;
}
if( terrain_id == t_water_dp ) {
if( one_in( 4 ) ) {
ret.poison = rng( 1, 4 );
}
return ret;
}
// iexamine::water_source requires a valid liquid from this function.
if( terrain_id.obj().examine == &iexamine::water_source ) {
return ret;
}
if( furn( p ).obj().examine == &iexamine::water_source ) {
return ret;
}
return item();
}
void map::make_active( item_location &loc )
{
item *target = loc.get_item();
// Trust but verify, don't let stinking callers set items active when they shouldn't be.
if( !target->needs_processing() ) {
return;
}
point l;
submap *const current_submap = get_submap_at( loc.position(), l );
auto &item_stack = current_submap->itm[l.x][l.y];
auto iter = std::find_if( item_stack.begin(), item_stack.end(),
[&target]( const item & i ) {
return &i == target;
} );
current_submap->active_items.add( iter, l );
}
void map::update_lum( item_location &loc, bool add )
{
item *target = loc.get_item();
// if the item is not emissive, do nothing
if( !target->is_emissive() ) {
return;
}
point l;
submap *const current_submap = get_submap_at( loc.position(), l );
if( add ) {
current_submap->update_lum_add( l, *target );
} else {
current_submap->update_lum_rem( l, *target );
}
}
// This is an ugly and dirty hack to prevent invalidating the item_location
// references the player is using for an activity. What needs to happen is
// activity targets gets refactored in some way that it can reference items
// between turns that doesn't rely on a pointer to the item. A really nice
// solution would be something like UUIDs but that requires special
// considerations.
static bool item_is_in_activity( const item *it )
{
const auto targs = &g->u.activity.targets;
return !targs->empty() &&
std::find_if( targs->begin(), targs->end(), [it]( const item_location & it_loc ) {
return it_loc.get_item() == it;
} ) != targs->end();
}
static bool process_item( item_stack &items, std::list<item>::iterator &n, const tripoint &location,
const bool activate, const int temp, const float insulation )
{
if( !item_is_in_activity( &*n ) ) {
// make a temporary copy, remove the item (in advance)
// and use that copy to process it
item temp_item = *n;
auto insertion_point = items.erase( n );
if( !temp_item.process( nullptr, location, activate, temp, insulation ) ) {
// Not destroyed, must be inserted again.
// If the item lost its active flag in processing,
// it won't be re-added to the active list, tidy!
// Re-insert at the item's previous position.
// This assumes that the item didn't invalidate any iterators
// As a result of activation, because everything that does that
// destroys itself.
items.insert_at( insertion_point, temp_item );
return false;
}
return true;
} else if( n->process( nullptr, location, activate, temp, insulation ) ) {
items.erase( n );
return true;
}
return false;
}
static bool process_map_items( item_stack &items, std::list<item>::iterator &n,
const tripoint &location, const std::string &, const int temp,
const float insulation )
{
return process_item( items, n, location, false, temp, insulation );
}
static void process_vehicle_items( vehicle &cur_veh, int part )
{
const bool washmachine_here = cur_veh.part_flag( part, VPFLAG_WASHING_MACHINE ) &&
cur_veh.is_part_on( part );
bool washing_machine_finished = false;
if( washmachine_here ) {
for( auto &n : cur_veh.get_items( part ) ) {
const time_duration washing_time = 90_minutes;
const time_duration time_left = washing_time - n.age();
static const std::string filthy( "FILTHY" );
if( time_left <= 0_turns ) {
n.item_tags.erase( filthy );
washing_machine_finished = true;
cur_veh.parts[part].enabled = false;
} else if( calendar::once_every( 15_minutes ) ) {
add_msg( _( "It should take %d minutes to finish washing items in the %s." ),
to_minutes<int>( time_left ) + 1, cur_veh.name.c_str() );
break;
}
}
if( washing_machine_finished ) {
add_msg( _( "The washing machine in the %s has finished washing." ), cur_veh.name.c_str() );
}
}
if( cur_veh.part_with_feature( part, VPFLAG_RECHARGE, true ) >= 0 &&
cur_veh.has_part( "RECHARGE", true ) ) {
for( auto &n : cur_veh.get_items( part ) ) {
static const std::string recharge_s( "RECHARGE" );
static const std::string ups_s( "USE_UPS" );
if( !n.has_flag( recharge_s ) && !n.has_flag( ups_s ) ) {
continue;
}
if( n.ammo_capacity() > n.ammo_remaining() ) {
constexpr int per_charge = 10;
const int missing = cur_veh.discharge_battery( per_charge, false );
if( missing < per_charge &&
( missing == 0 || x_in_y( per_charge - missing, per_charge ) ) ) {
n.ammo_set( "battery", n.ammo_remaining() + 1 );
}
if( missing > 0 ) {
// Not enough charge - stop charging
break;
}
}
}
}
}
void map::process_active_items()
{
process_items( true, process_map_items, std::string {} );
}
void map::process_items( const bool active, map::map_process_func processor,
const std::string &signal )
{
const int minz = zlevels ? -OVERMAP_DEPTH : abs_sub.z;
const int maxz = zlevels ? OVERMAP_HEIGHT : abs_sub.z;
tripoint gp( 0, 0, 0 );
int &gx = gp.x;
int &gy = gp.y;
int &gz = gp.z;
for( gz = minz; gz <= maxz; ++gz ) {
for( gx = 0; gx < my_MAPSIZE; ++gx ) {
for( gy = 0; gy < my_MAPSIZE; ++gy ) {
submap *const current_submap = get_submap_at_grid( gp );
// Vehicles first in case they get blown up and drop active items on the map.
if( !current_submap->vehicles.empty() ) {
process_items_in_vehicles( *current_submap, gz, processor, signal );
}
}
}
}
for( gz = minz; gz <= maxz; ++gz ) {
for( gx = 0; gx < my_MAPSIZE; ++gx ) {
for( gy = 0; gy < my_MAPSIZE; ++gy ) {
submap *const current_submap = get_submap_at_grid( gp );
if( !active || !current_submap->active_items.empty() ) {
process_items_in_submap( *current_submap, gp, processor, signal );
}
}
}
}
}
void map::process_items_in_submap( submap &current_submap, const tripoint &gridp,
map::map_process_func processor, const std::string &signal )
{
// Get a COPY of the active item list for this submap.
// If more are added as a side effect of processing, they are ignored this turn.
// If they are destroyed before processing, they don't get processed.
std::list<item_reference> active_items = current_submap.active_items.get();
const auto grid_offset = point {gridp.x * SEEX, gridp.y * SEEY};
for( auto &active_item : active_items ) {
if( !current_submap.active_items.has( active_item ) ) {
continue;
}
const tripoint map_location = tripoint( grid_offset + active_item.location, gridp.z );
// root cellars are special
const int loc_temp = g->m.ter( map_location ) == t_rootcellar ?
AVERAGE_ANNUAL_TEMPERATURE :
g->get_temperature( map_location );
auto items = i_at( map_location );
processor( items, active_item.item_iterator, map_location, signal, loc_temp, 1 );
}
}
void map::process_items_in_vehicles( submap &current_submap, const int gridz,
map::map_process_func processor, const std::string &signal )
{
// a copy, important if the vehicle list changes because a
// vehicle got destroyed by a bomb (an active item!), this list
// won't change, but veh_in_nonant will change.
std::vector<vehicle *> vehicles;
for( const auto &veh : current_submap.vehicles ) {
vehicles.push_back( veh.get() );
}
for( auto &cur_veh : vehicles ) {
if( !current_submap.contains_vehicle( cur_veh ) ) {
// vehicle not in the vehicle list of the nonant, has been
// destroyed (or moved to another nonant?)
// Can't be sure that it still exists, so skip it
continue;
}
process_items_in_vehicle( *cur_veh, current_submap, gridz, processor, signal );
}
}
void map::process_items_in_vehicle( vehicle &cur_veh, submap &current_submap, const int /*gridz*/,
map::map_process_func processor, const std::string &signal )
{
const bool engine_heater_is_on = cur_veh.has_part( "E_HEATER", true ) && cur_veh.engine_on;
for( const vpart_reference &vp : cur_veh.get_any_parts( VPFLAG_FLUIDTANK ) ) {
vp.part().process_contents( vp.pos(), engine_heater_is_on );
}
auto cargo_parts = cur_veh.get_parts_including_carried( VPFLAG_CARGO );
for( const vpart_reference &vp : cargo_parts ) {
process_vehicle_items( cur_veh, vp.part_index() );
}
for( auto &active_item : cur_veh.active_items.get() ) {
if( empty( cargo_parts ) ) {
return;
} else if( !cur_veh.active_items.has( active_item ) ) {
continue;
}
const auto it = std::find_if( begin( cargo_parts ),
end( cargo_parts ), [&]( const vpart_reference & part ) {
return active_item.location == part.mount();
} );
if( it == end( cargo_parts ) ) {
continue; // Can't find a cargo part matching the active item.
}
auto &item_iter = active_item.item_iterator;
// Find the cargo part and coordinates corresponding to the current active item.
const vehicle_part &pt = it->part();
const tripoint item_loc = it->pos();
auto items = cur_veh.get_items( static_cast<int>( it->part_index() ) );
int it_temp = g->get_temperature( item_loc );
float it_insulation = 1.0;
if( item_iter->is_food() || item_iter->is_food_container() ) {
const vpart_info &pti = pt.info();
if( engine_heater_is_on ) {
it_temp = std::max( it_temp, temperatures::normal );
}
// some vehicle parts provide insulation, default is 1
it_insulation = item::find_type( pti.item )->insulation_factor;
if( pt.enabled && pti.has_flag( VPFLAG_FRIDGE ) ) {
it_temp = std::min( it_temp, temperatures::fridge );
it_insulation = 1; // ignore fridge insulation if on
} else if( pt.enabled && pti.has_flag( VPFLAG_FREEZER ) ) {
it_temp = std::min( it_temp, temperatures::freezer );
it_insulation = 1; // ignore freezer insulation if on
}
}
if( !processor( items, item_iter, item_loc, signal, it_temp, it_insulation ) ) {
// If the item was NOT destroyed, we can skip the remainder,
// which handles fallout from the vehicle being damaged.
continue;
}
// item does not exist anymore, might have been an exploding bomb,
// check if the vehicle is still valid (does exist)
if( !current_submap.contains_vehicle( &cur_veh ) ) {
// Nope, vehicle is not in the vehicle list of the submap,
// it might have moved to another submap (unlikely)
// or be destroyed, anyway it does not need to be processed here
return;
}
// Vehicle still valid, reload the list of cargo parts,
// the list of cargo parts might have changed (imagine a part with
// a low index has been removed by an explosion, all the other
// parts would move up to fill the gap).
cargo_parts = cur_veh.get_any_parts( VPFLAG_CARGO );
}
}
// Crafting/item finding functions
// Note: this is called quite a lot when drawing tiles
// Console build has the most expensive parts optimized out
bool map::sees_some_items( const tripoint &p, const Creature &who ) const
{
// Can only see items if there are any items.
return has_items( p ) && could_see_items( p, who );
}
bool map::could_see_items( const tripoint &p, const Creature &who ) const
{
static const std::string container_string( "CONTAINER" );
const bool container = has_flag_ter_or_furn( container_string, p );
const bool sealed = has_flag_ter_or_furn( TFLAG_SEALED, p );
if( sealed && container ) {
// never see inside of sealed containers
return false;
}
if( container ) {
// can see inside of containers if adjacent or
// on top of the container
return ( abs( p.x - who.posx() ) <= 1 &&
abs( p.y - who.posy() ) <= 1 &&
abs( p.z - who.posz() ) <= 1 );
}
return true;
}
bool map::has_items( const tripoint &p ) const
{
if( !inbounds( p ) ) {
return false;
}
point l;
submap *const current_submap = get_submap_at( p, l );
return !current_submap->itm[l.x][l.y].empty();
}
template <typename Stack>
std::list<item> use_amount_stack( Stack stack, const itype_id type, long &quantity,
const std::function<bool( const item & )> &filter )
{
std::list<item> ret;
for( auto a = stack.begin(); a != stack.end() && quantity > 0; ) {
if( a->use_amount( type, quantity, ret, filter ) ) {
a = stack.erase( a );
} else {
++a;
}
}
return ret;
}
std::list<item> map::use_amount_square( const tripoint &p, const itype_id type,
long &quantity, const std::function<bool( const item & )> &filter )
{
std::list<item> ret;
// Handle infinite map sources.
item water = water_from( p );
if( water.typeId() == type ) {
ret.push_back( water );
quantity = 0;
return ret;
}
if( const cata::optional<vpart_reference> vp = veh_at( p ).part_with_feature( "CARGO", true ) ) {
std::list<item> tmp = use_amount_stack( vp->vehicle().get_items( vp->part_index() ), type,
quantity, filter );
ret.splice( ret.end(), tmp );
}
std::list<item> tmp = use_amount_stack( i_at( p ), type, quantity, filter );
ret.splice( ret.end(), tmp );
return ret;
}
std::list<item> map::use_amount( const tripoint &origin, const int range, const itype_id type,
long &quantity, const std::function<bool( const item & )> &filter )
{
std::list<item> ret;
for( int radius = 0; radius <= range && quantity > 0; radius++ ) {
for( const tripoint &p : points_in_radius( origin, radius ) ) {
if( rl_dist( origin, p ) >= radius ) {
std::list<item> tmp = use_amount_square( p, type, quantity, filter );
ret.splice( ret.end(), tmp );
}
}
}
return ret;
}
template <typename Stack>
std::list<item> use_charges_from_stack( Stack stack, const itype_id type, long &quantity,
const tripoint &pos )
{
std::list<item> ret;
for( auto a = stack.begin(); a != stack.end() && quantity > 0; ) {
if( !a->made_of( LIQUID ) && a->use_charges( type, quantity, ret, pos ) ) {
a = stack.erase( a );
} else {
++a;
}
}
return ret;
}
long remove_charges_in_list( const itype *type, map_stack stack, long quantity )
{
auto target = stack.begin();
for( ; target != stack.end(); ++target ) {
if( target->type == type ) {
break;
}
}
if( target != stack.end() ) {
if( target->charges > quantity ) {
target->charges -= quantity;
return quantity;
} else {
const long charges = target->charges;
target->charges = 0;
if( target->destroyed_at_zero_charges() ) {
stack.erase( target );
}
return charges;
}
}
return 0;
}
void use_charges_from_furn( const furn_t &f, const itype_id &type, long &quantity,
map *m, const tripoint &p, std::list<item> &ret )
{
if( m->has_flag( "LIQUIDCONT", p ) ) {
auto item_list = m->i_at( p );
auto current_item = item_list.begin();
for( ; current_item != item_list.end(); ++current_item ) {
// looking for a liquid that matches
if( current_item->made_of( LIQUID ) && type == current_item->typeId() ) {
ret.push_back( *current_item );
if( current_item->charges - quantity > 0 ) {
// Update the returned liquid amount to match the requested amount
ret.back().charges = quantity;
// Update the liquid item in the world to contain the leftover liquid
current_item->charges -= quantity;
// All the liquid needed was found, no other sources will be needed
quantity = 0;
} else {
// The liquid copy in ret already contains how much was available
// The leftover quantity returned will check other sources
quantity -= current_item->charges;
// Remove liquid item from the world
item_list.erase( current_item );
}
return;
}
}
}
const itype *itt = f.crafting_pseudo_item_type();
if( itt != nullptr && itt->tool && itt->tool->ammo_id ) {
const itype_id ammo = itt->tool->ammo_id->default_ammotype();
auto stack = m->i_at( p );
auto iter = std::find_if( stack.begin(), stack.end(),
[ammo]( const item & i ) {
return i.typeId() == ammo;
} );
if( iter != stack.end() ) {
item furn_item( itt, -1, iter->charges );
// The const itemructor limits the charges to the (type specific) maximum.
// Setting it separately circumvents that it is synchronized with the code that creates
// the pseudo item (and fills its charges) in inventory.cpp
furn_item.charges = iter->charges;
if( furn_item.use_charges( type, quantity, ret, p ) ) {
stack.erase( iter );
} else {
iter->charges = furn_item.charges;
}
}
}
}
std::list<item> map::use_charges( const tripoint &origin, const int range,
const itype_id type, long &quantity )
{
std::list<item> ret;
// We prefer infinite map sources where available, so search for those
// first
for( const tripoint &p : closest_tripoints_first( range, origin ) ) {
// can not reach this -> can not access its contents
if( origin != p && !clear_path( origin, p, range, 1, 100 ) ) {
continue;
}
// Handle infinite map sources.
item water = water_from( p );
if( water.typeId() == type ) {
ret.push_back( water );
quantity = 0;
return ret;
}
}
for( const tripoint &p : closest_tripoints_first( range, origin ) ) {
// can not reach this -> can not access its contents
if( origin != p && !clear_path( origin, p, range, 1, 100 ) ) {
continue;
}
if( has_furn( p ) ) {
use_charges_from_furn( furn( p ).obj(), type, quantity, this, p, ret );
if( quantity <= 0 ) {
return ret;
}
}
if( accessible_items( p ) ) {
std::list<item> tmp = use_charges_from_stack( i_at( p ), type, quantity, p );
ret.splice( ret.end(), tmp );
if( quantity <= 0 ) {
return ret;
}
}
const optional_vpart_position vp = veh_at( p );
if( !vp ) {
continue;
}
const cata::optional<vpart_reference> kpart = vp.part_with_feature( "FAUCET", true );
const cata::optional<vpart_reference> weldpart = vp.part_with_feature( "WELDRIG", true );
const cata::optional<vpart_reference> craftpart = vp.part_with_feature( "CRAFTRIG", true );
const cata::optional<vpart_reference> forgepart = vp.part_with_feature( "FORGE", true );
const cata::optional<vpart_reference> kilnpart = vp.part_with_feature( "KILN", true );
const cata::optional<vpart_reference> chempart = vp.part_with_feature( "CHEMLAB", true );
const cata::optional<vpart_reference> cargo = vp.part_with_feature( "CARGO", true );
if( kpart ) { // we have a faucet, now to see what to drain
itype_id ftype = "null";
// Special case hotplates which draw battery power
if( type == "hotplate" ) {
ftype = "battery";
} else {
ftype = type;
}
item tmp( type, 0 ); //TODO add a sane birthday arg
tmp.charges = kpart->vehicle().drain( ftype, quantity );
// TODO: Handle water poison when crafting starts respecting it
quantity -= tmp.charges;
ret.push_back( tmp );
if( quantity == 0 ) {
return ret;
}
}
if( weldpart ) { // we have a weldrig, now to see what to drain
itype_id ftype = "null";
if( type == "welder" ) {
ftype = "battery";
} else if( type == "soldering_iron" ) {
ftype = "battery";
}
item tmp( type, 0 ); //TODO add a sane birthday arg
tmp.charges = weldpart->vehicle().drain( ftype, quantity );
quantity -= tmp.charges;
ret.push_back( tmp );
if( quantity == 0 ) {
return ret;
}
}
if( craftpart ) { // we have a craftrig, now to see what to drain
itype_id ftype = "null";
if( type == "press" ) {
ftype = "battery";
} else if( type == "vac_sealer" ) {
ftype = "battery";
} else if( type == "dehydrator" ) {
ftype = "battery";
} else if( type == "food_processor" ) {
ftype = "battery";
}
item tmp( type, 0 ); //TODO add a sane birthday arg
tmp.charges = craftpart->vehicle().drain( ftype, quantity );
quantity -= tmp.charges;
ret.push_back( tmp );
if( quantity == 0 ) {
return ret;
}
}
if( forgepart ) { // we have a veh_forge, now to see what to drain
itype_id ftype = "null";
if( type == "forge" ) {
ftype = "battery";
}
item tmp( type, 0 ); //TODO add a sane birthday arg
tmp.charges = forgepart->vehicle().drain( ftype, quantity );
quantity -= tmp.charges;
ret.push_back( tmp );
if( quantity == 0 ) {
return ret;
}
}
if( kilnpart ) { // we have a veh_kiln, now to see what to drain
itype_id ftype = "null";
if( type == "kiln" ) {
ftype = "battery";
}
item tmp( type, 0 ); //TODO add a sane birthday arg
tmp.charges = kilnpart->vehicle().drain( ftype, quantity );
quantity -= tmp.charges;
ret.push_back( tmp );
if( quantity == 0 ) {
return ret;
}
}
if( chempart ) { // we have a chem_lab, now to see what to drain
itype_id ftype = "null";
if( type == "chemistry_set" ) {
ftype = "battery";
} else if( type == "hotplate" ) {
ftype = "battery";
}
item tmp( type, 0 ); //TODO add a sane birthday arg
tmp.charges = chempart->vehicle().drain( ftype, quantity );
quantity -= tmp.charges;
ret.push_back( tmp );
if( quantity == 0 ) {
return ret;
}
}
if( cargo ) {
std::list<item> tmp =
use_charges_from_stack( cargo->vehicle().get_items( cargo->part_index() ), type, quantity, p );
ret.splice( ret.end(), tmp );
if( quantity <= 0 ) {
return ret;
}
}
}
return ret;
}
std::list<std::pair<tripoint, item *> > map::get_rc_items( int x, int y, int z )
{
std::list<std::pair<tripoint, item *> > rc_pairs;
tripoint pos;
( void )z;
pos.z = abs_sub.z;
for( pos.x = 0; pos.x < MAPSIZE_X; pos.x++ ) {
if( x != -1 && x != pos.x ) {
continue;
}
for( pos.y = 0; pos.y < MAPSIZE_Y; pos.y++ ) {
if( y != -1 && y != pos.y ) {
continue;
}
auto items = i_at( pos );
for( auto &elem : items ) {
if( elem.has_flag( "RADIO_ACTIVATION" ) || elem.has_flag( "RADIO_CONTAINER" ) ) {
rc_pairs.push_back( std::make_pair( pos, &( elem ) ) );
}
}
}
}
return rc_pairs;
}
static bool trigger_radio_item( item_stack &items, std::list<item>::iterator &n,
const tripoint &pos, const std::string &signal,
const int, const float )
{
bool trigger_item = false;
if( n->has_flag( "RADIO_ACTIVATION" ) && n->has_flag( signal ) ) {
sounds::sound( pos, 6, sounds::sound_t::alarm, _( "beep." ) );
if( n->has_flag( "RADIO_INVOKE_PROC" ) ) {
// Invoke twice: first to transform, then later to proc
// Can't use process_item here - invalidates our iterator
n->process( nullptr, pos, true );
}
if( n->has_flag( "BOMB" ) ) {
// Set charges to 0 to ensure it detonates now
n->charges = 0;
n->item_counter = 0;
}
trigger_item = true;
} else if( n->has_flag( "RADIO_CONTAINER" ) && !n->contents.empty() ) {
auto it = std::find_if( n->contents.begin(), n->contents.end(), [ &signal ]( const item & c ) {
return c.has_flag( signal );
} );
if( it != n->contents.end() ) {
n->convert( it->typeId() );
if( n->has_flag( "RADIO_INVOKE_PROC" ) ) {
n->process( nullptr, pos, true );
}
// Clear possible mods to prevent unnecessary pop-ups.
n->contents.clear();
n->charges = 0;
trigger_item = true;
}
}
if( trigger_item ) {
return process_item( items, n, pos, true, 0, 1 );
}
return false;
}
void map::trigger_rc_items( const std::string &signal )
{
process_items( false, trigger_radio_item, signal );
}
item *map::item_from( const tripoint &pos, size_t index )
{
auto items = i_at( pos );
if( index >= items.size() ) {
return nullptr;
} else {
return &items[index];
}
}
item *map::item_from( vehicle *veh, int cargo_part, size_t index )
{
auto items = veh->get_items( cargo_part );
if( index >= items.size() ) {
return nullptr;
} else {
return &items[index];
}
}
const trap &map::tr_at( const tripoint &p ) const
{
if( !inbounds( p ) ) {
return tr_null.obj();
}
point l;
submap *const current_submap = get_submap_at( p, l );
if( current_submap->get_ter( l ).obj().trap != tr_null ) {
return current_submap->get_ter( l ).obj().trap.obj();
}
return current_submap->get_trap( l ).obj();
}
void map::trap_set( const tripoint &p, const trap_id type )
{
if( !inbounds( p ) ) {
return;
}
point l;
submap *const current_submap = get_submap_at( p, l );
const ter_t &ter = current_submap->get_ter( l ).obj();
if( ter.trap != tr_null ) {
debugmsg( "set trap %s on top of terrain %s which already has a builit-in trap",
type.obj().name().c_str(), ter.name().c_str() );
return;
}
// If there was already a trap here, remove it.
if( current_submap->get_trap( l ) != tr_null ) {
remove_trap( p );
}
current_submap->set_trap( l, type );
if( type != tr_null ) {
traplocs[type].push_back( p );
}
}
void map::disarm_trap( const tripoint &p )
{
const trap &tr = tr_at( p );
if( tr.is_null() ) {
debugmsg( "Tried to disarm a trap where there was none (%d %d %d)", p.x, p.y, p.z );
return;
}
const int tSkillLevel = g->u.get_skill_level( skill_traps );
const int diff = tr.get_difficulty();
int roll = rng( tSkillLevel, 4 * tSkillLevel );
// Some traps are not actual traps. Skip the rolls, different message and give the option to grab it right away.
if( tr.get_avoidance() == 0 && tr.get_difficulty() == 0 ) {
add_msg( _( "You take down the %s." ), tr.name().c_str() );
tr.on_disarmed( *this, p );
return;
}
///\EFFECT_PER increases chance of disarming trap
///\EFFECT_DEX increases chance of disarming trap
///\EFFECT_TRAPS increases chance of disarming trap
while( ( rng( 5, 20 ) < g->u.per_cur || rng( 1, 20 ) < g->u.dex_cur ) && roll < 50 ) {
roll++;
}
if( roll >= diff ) {
add_msg( _( "You disarm the trap!" ) );
tr.on_disarmed( *this, p );
if( diff > 1.25 * tSkillLevel ) { // failure might have set off trap
g->u.practice( skill_traps, 1.5 * ( diff - tSkillLevel ) );
}
} else if( roll >= diff * .8 ) {
add_msg( _( "You fail to disarm the trap." ) );
if( diff > 1.25 * tSkillLevel ) {
g->u.practice( skill_traps, 1.5 * ( diff - tSkillLevel ) );
}
} else {
add_msg( m_bad, _( "You fail to disarm the trap, and you set it off!" ) );
tr.trigger( p, &g->u );
if( diff - roll <= 6 ) {
// Give xp for failing, but not if we failed terribly (in which
// case the trap may not be disarmable).
g->u.practice( skill_traps, 2 * diff );
}
}
}
void map::remove_trap( const tripoint &p )
{
if( !inbounds( p ) ) {
return;
}
point l;
submap *const current_submap = get_submap_at( p, l );
trap_id tid = current_submap->get_trap( l );
if( tid != tr_null ) {
if( g != nullptr && this == &g->m ) {
g->u.add_known_trap( p, tr_null.obj() );
}
current_submap->set_trap( l, tr_null );
auto &traps = traplocs[tid];
const auto iter = std::find( traps.begin(), traps.end(), p );
if( iter != traps.end() ) {
traps.erase( iter );
}
}
}
/*
* Get wrapper for all fields at xyz
*/
const field &map::field_at( const tripoint &p ) const
{
if( !inbounds( p ) ) {
nulfield = field();
return nulfield;
}
point l;
submap *const current_submap = get_submap_at( p, l );
return current_submap->fld[l.x][l.y];
}
/*
* As above, except not const
*/
field &map::field_at( const tripoint &p )
{
if( !inbounds( p ) ) {
nulfield = field();
return nulfield;
}
point l;
submap *const current_submap = get_submap_at( p, l );
return current_submap->fld[l.x][l.y];
}
time_duration map::adjust_field_age( const tripoint &p, const field_id type,
const time_duration &offset )
{
return set_field_age( p, type, offset, true );
}
int map::adjust_field_strength( const tripoint &p, const field_id type, const int offset )
{
return set_field_strength( p, type, offset, true );
}
time_duration map::set_field_age( const tripoint &p, const field_id type, const time_duration &age,
const bool isoffset )
{
if( field_entry *const field_ptr = get_field( p, type ) ) {
return field_ptr->setFieldAge( ( isoffset ? field_ptr->getFieldAge() : 0_turns ) + age );
}
return -1_turns;
}
/*
* set strength of field type at point, creating if not present, removing if strength is 0
* returns resulting strength, or 0 for not present
*/
int map::set_field_strength( const tripoint &p, const field_id type, const int str, bool isoffset )
{
field_entry *field_ptr = get_field( p, type );
if( field_ptr != nullptr ) {
int adj = ( isoffset ? field_ptr->getFieldDensity() : 0 ) + str;
if( adj > 0 ) {
field_ptr->setFieldDensity( adj );
return adj;
} else {
remove_field( p, type );
return 0;
}
} else if( 0 + str > 0 ) {
return add_field( p, type, str ) ? str : 0;
}
return 0;
}
time_duration map::get_field_age( const tripoint &p, const field_id type ) const
{
auto field_ptr = field_at( p ).findField( type );
return field_ptr == nullptr ? -1_turns : field_ptr->getFieldAge();
}
int map::get_field_strength( const tripoint &p, const field_id type ) const
{
auto field_ptr = field_at( p ).findField( type );
return ( field_ptr == nullptr ? 0 : field_ptr->getFieldDensity() );
}
field_entry *map::get_field( const tripoint &p, const field_id type )
{
if( !inbounds( p ) ) {
return nullptr;
}
point l;
submap *const current_submap = get_submap_at( p, l );
return current_submap->fld[l.x][l.y].findField( type );
}
bool map::add_field( const tripoint &p, const field_id type, int density, const time_duration &age )
{
if( !inbounds( p ) ) {
return false;
}
density = std::min( density, MAX_FIELD_DENSITY );
if( density <= 0 ) {
return false;
}
if( type == fd_null ) {
return false;
}
point l;
submap *const current_submap = get_submap_at( p, l );
current_submap->is_uniform = false;
if( current_submap->fld[l.x][l.y].addField( type, density, age ) ) {
//Only adding it to the count if it doesn't exist.
current_submap->field_count++;
}
if( g != nullptr && this == &g->m && p == g->u.pos() ) {
creature_in_field( g->u ); //Hit the player with the field if it spawned on top of them.
}
// Dirty the transparency cache now that field processing doesn't always do it
// TODO: Make it skip transparent fields
set_transparency_cache_dirty( p.z );
const field_t &ft = fieldlist[type];
if( field_type_dangerous( type ) ) {
set_pathfinding_cache_dirty( p.z );
}
// Ensure blood type fields don't hang in the air
if( zlevels && ft.accelerated_decay ) {
support_dirty( p );
}
return true;
}
void map::remove_field( const tripoint &p, const field_id field_to_remove )
{
if( !inbounds( p ) ) {
return;
}
point l;
submap *const current_submap = get_submap_at( p, l );
if( current_submap->fld[l.x][l.y].removeField( field_to_remove ) ) {
// Only adjust the count if the field actually existed.
current_submap->field_count--;
const auto &fdata = fieldlist[ field_to_remove ];
for( bool i : fdata.transparent ) {
if( !i ) {
set_transparency_cache_dirty( p.z );
break;
}
}
for( bool danger : fdata.dangerous ) {
if( danger ) {
set_pathfinding_cache_dirty( p.z );
break;
}
}
}
}
void map::add_splatter( const field_id type, const tripoint &where, int intensity )
{
if( intensity <= 0 ) {
return;
}
if( type == fd_blood || type == fd_gibs_flesh ) { // giblets are also good for painting
if( const optional_vpart_position vp = veh_at( where ) ) {
vehicle *const veh = &vp->vehicle();
// Might be -1 if all the vehicle's parts at where are marked for removal
const int part = veh->part_displayed_at( vp->mount() );
if( part != -1 ) {
veh->parts[part].blood += 200 * std::min( intensity, 3 ) / 3;
return;
}
}
}
adjust_field_strength( where, type, intensity );
}
void map::add_splatter_trail( const field_id type, const tripoint &from, const tripoint &to )
{
if( type == fd_null ) {
return;
}
const auto trail = line_to( from, to );
int remainder = trail.size();
for( const auto &elem : trail ) {
add_splatter( type, elem );
remainder--;
if( impassable( elem ) ) { // Blood splatters stop at walls.
add_splatter( type, elem, remainder );
return;
}
}
}
void map::add_splash( const field_id type, const tripoint &center, int radius, int density )
{
if( type == fd_null ) {
return;
}
// TODO: use Bresenham here and take obstacles into account
for( const tripoint &pnt : points_in_radius( center, radius ) ) {
if( trig_dist( pnt, center ) <= radius && !one_in( density ) ) {
add_splatter( type, pnt );
}
}
}
computer *map::computer_at( const tripoint &p )
{
if( !inbounds( p ) ) {
return nullptr;
}
return get_submap_at( p )->comp.get();
}
bool map::allow_camp( const tripoint &p, const int radius )
{
return camp_at( p, radius ) == nullptr;
}
// locate the nearest camp in some radius (default CAMPSIZE)
basecamp *map::camp_at( const tripoint &p, const int radius )
{
if( !inbounds( p ) ) {
return nullptr;
}
const int sx = std::max( 0, p.x - radius );
const int sy = std::max( 0, p.y - radius );
const int ex = std::min( p.x + radius, MAPSIZE_X - 1 );
const int ey = std::min( p.y + radius, MAPSIZE_Y - 1 );
for( int ly = sy; ly < ey; ly += SEEY ) {
for( int lx = sx; lx < ex; lx += SEEX ) {
submap *const current_submap = get_submap_at( tripoint( lx, ly, p.z ) );
if( current_submap->camp.is_valid() ) {
// we only allow on camp per size radius, kinda
return &( current_submap->camp );
}
}
}
return nullptr;
}
void map::add_camp( const tripoint &p, const std::string &name )
{
if( !allow_camp( p ) ) {
dbg( D_ERROR ) << "map::add_camp: Attempting to add camp when one in local area.";
return;
}
get_submap_at( p )->camp = basecamp( name, p );
}
void map::update_visibility_cache( const int zlev )
{
visibility_variables_cache.variables_set = true; // Not used yet
visibility_variables_cache.g_light_level = static_cast<int>( g->light_level( zlev ) );
visibility_variables_cache.vision_threshold = g->u.get_vision_threshold(
get_cache_ref( g->u.posz() ).lm[g->u.posx()][g->u.posy()].max() );
visibility_variables_cache.u_clairvoyance = g->u.clairvoyance();
visibility_variables_cache.u_sight_impaired = g->u.sight_impaired();
visibility_variables_cache.u_is_boomered = g->u.has_effect( effect_boomered );
int sm_squares_seen[MAPSIZE][MAPSIZE];
std::memset( sm_squares_seen, 0, sizeof( sm_squares_seen ) );
auto &visibility_cache = get_cache( zlev ).visibility_cache;
tripoint p;
p.z = zlev;
int &x = p.x;
int &y = p.y;
for( x = 0; x < MAPSIZE_X; x++ ) {
for( y = 0; y < MAPSIZE_Y; y++ ) {
lit_level ll = apparent_light_at( p, visibility_variables_cache );
visibility_cache[x][y] = ll;
sm_squares_seen[ x / SEEX ][ y / SEEY ] += ( ll == LL_BRIGHT || ll == LL_LIT );
}
}
for( int gridx = 0; gridx < my_MAPSIZE; gridx++ ) {
for( int gridy = 0; gridy < my_MAPSIZE; gridy++ ) {
if( sm_squares_seen[gridx][gridy] > 36 ) { // 25% of the submap is visible
const tripoint sm( gridx, gridy, 0 );
const auto abs_sm = map::abs_sub + sm;
const auto abs_omt = sm_to_omt_copy( abs_sm );
overmap_buffer.set_seen( abs_omt.x, abs_omt.y, abs_omt.z, true );
}
}
}
}
const visibility_variables &map::get_visibility_variables_cache() const
{
return visibility_variables_cache;
}
visibility_type map::get_visibility( const lit_level ll, const visibility_variables &cache ) const
{
switch( ll ) {
case LL_DARK: // can't see this square at all
if( cache.u_is_boomered ) {
return VIS_BOOMER_DARK;
} else {
return VIS_DARK;
}
case LL_BRIGHT_ONLY: // can only tell that this square is bright
if( cache.u_is_boomered ) {
return VIS_BOOMER;
} else {
return VIS_LIT;
}
case LL_LOW: // low light, square visible in monochrome
case LL_LIT: // normal light
case LL_BRIGHT: // bright light
return VIS_CLEAR;
case LL_BLANK:
case LL_MEMORIZED:
return VIS_HIDDEN;
}
return VIS_HIDDEN;
}
bool map::apply_vision_effects( const catacurses::window &w, const visibility_type vis ) const
{
long symbol = ' ';
nc_color color = c_black;
switch( vis ) {
case VIS_CLEAR:
// Drew the tile, so bail out now.
return false;
case VIS_LIT: // can only tell that this square is bright
symbol = '#';
color = c_light_gray;
break;
case VIS_BOOMER:
symbol = '#';
color = c_pink;
break;
case VIS_BOOMER_DARK:
symbol = '#';
color = c_magenta;
break;
case VIS_DARK: // can't see this square at all
case VIS_HIDDEN:
symbol = ' ';
color = c_black;
break;
}
wputch( w, color, symbol );
return true;
}
bool map::draw_maptile_from_memory( const catacurses::window &w, const tripoint &p,
const tripoint &view_center, bool move_cursor ) const
{
long sym = g->u.get_memorized_symbol( getabs( p ) );
if( sym == 0 ) {
return false;
}
if( move_cursor ) {
const int k = p.x + getmaxx( w ) / 2 - view_center.x;
const int j = p.y + getmaxy( w ) / 2 - view_center.y;
mvwputch( w, j, k, c_brown, sym );
} else {
wputch( w, c_brown, sym );
}
return true;
}
void map::draw( const catacurses::window &w, const tripoint &center )
{
// We only need to draw anything if we're not in tiles mode.
if( is_draw_tiles_mode() ) {
return;
}
g->reset_light_level();
update_visibility_cache( center.z );
const visibility_variables &cache = g->m.get_visibility_variables_cache();
const auto &visibility_cache = get_cache_ref( center.z ).visibility_cache;
// X and y are in map coordinates, but might be out of range of the map.
// When they are out of range, we just draw '#'s.
tripoint p;
p.z = center.z;
int &x = p.x;
int &y = p.y;
const bool do_map_memory = g->u.should_show_map_memory();
for( y = center.y - getmaxy( w ) / 2; y <= center.y + getmaxy( w ) / 2; y++ ) {
if( y - center.y + getmaxy( w ) / 2 >= getmaxy( w ) ) {
continue;
}
wmove( w, y - center.y + getmaxy( w ) / 2, 0 );
const int maxxrender = center.x - getmaxx( w ) / 2 + getmaxx( w );
x = center.x - getmaxx( w ) / 2;
if( y < 0 || y >= MAPSIZE_Y ) {
for( ; x < maxxrender; x++ ) {
if( !do_map_memory || !draw_maptile_from_memory( w, p, center, false ) ) {
wputch( w, c_black, ' ' );
}
}
continue;
}
while( x < 0 ) {
if( !do_map_memory || !draw_maptile_from_memory( w, p, center, false ) ) {
wputch( w, c_black, ' ' );
}
x++;
}
point l;
const int maxx = std::min( MAPSIZE_X, maxxrender );
while( x < maxx ) {
submap *cur_submap = get_submap_at( p, l );
submap *sm_below = p.z > -OVERMAP_DEPTH ?
get_submap_at( {p.x, p.y, p.z - 1}, l ) : cur_submap;
while( l.x < SEEX && x < maxx ) {
const lit_level lighting = visibility_cache[x][y];
const visibility_type vis = get_visibility( lighting, cache );
if( !apply_vision_effects( w, vis ) ) {
const maptile curr_maptile = maptile( cur_submap, l );
const bool just_this_zlevel =
draw_maptile( w, g->u, p, curr_maptile,
false, true, center,
lighting == LL_LOW, lighting == LL_BRIGHT, true );
if( !just_this_zlevel ) {
p.z--;
const maptile tile_below = maptile( sm_below, l );
draw_from_above( w, g->u, p, tile_below, false, center,
lighting == LL_LOW, lighting == LL_BRIGHT, false );
p.z++;
}
} else if( do_map_memory && ( vis == VIS_HIDDEN || vis == VIS_DARK ) ) {
draw_maptile_from_memory( w, p, center );
}
l.x++;
x++;
}
}
while( x < maxxrender ) {
if( !do_map_memory || !draw_maptile_from_memory( w, p, center, false ) ) {
wputch( w, c_black, ' ' );
}
x++;
}
}
}
void map::drawsq( const catacurses::window &w, player &u, const tripoint &p,
const bool invert, const bool show_items ) const
{
drawsq( w, u, p, invert, show_items, u.pos() + u.view_offset, false, false, false );
}
void map::drawsq( const catacurses::window &w, player &u, const tripoint &p, const bool invert_arg,
const bool show_items_arg, const tripoint &view_center,
const bool low_light, const bool bright_light, const bool inorder ) const
{
// We only need to draw anything if we're not in tiles mode.
if( is_draw_tiles_mode() ) {
return;
}
if( !inbounds( p ) ) {
return;
}
const maptile tile = maptile_at( p );
const bool done = draw_maptile( w, u, p, tile, invert_arg, show_items_arg,
view_center, low_light, bright_light, inorder );
if( !done ) {
tripoint below( p.x, p.y, p.z - 1 );
const maptile tile_below = maptile_at( below );
draw_from_above( w, u, below, tile_below,
invert_arg, view_center,
low_light, bright_light, false );
}
}
// a check to see if the lower floor needs to be rendered in tiles
bool map::need_draw_lower_floor( const tripoint &p )
{
return !( !zlevels || p.z <= -OVERMAP_DEPTH || !ter( p ).obj().has_flag( TFLAG_NO_FLOOR ) );
}
bool map::draw_maptile( const catacurses::window &w, player &u, const tripoint &p,
const maptile &curr_maptile,
bool invert, bool show_items,
const tripoint &view_center,
const bool low_light, const bool bright_light, const bool inorder ) const
{
nc_color tercol;
const ter_t &curr_ter = curr_maptile.get_ter_t();
const furn_t &curr_furn = curr_maptile.get_furn_t();
const trap &curr_trap = curr_maptile.get_trap().obj();
const field &curr_field = curr_maptile.get_field();
long sym;
bool hi = false;
bool graf = false;
bool draw_item_sym = false;
long terrain_sym;
if( curr_ter.has_flag( TFLAG_AUTO_WALL_SYMBOL ) ) {
terrain_sym = determine_wall_corner( p );
} else {
terrain_sym = curr_ter.symbol();
}
if( curr_furn.id ) {
sym = curr_furn.symbol();
tercol = curr_furn.color();
} else {
sym = terrain_sym;
tercol = curr_ter.color();
}
if( curr_ter.has_flag( TFLAG_SWIMMABLE ) && curr_ter.has_flag( TFLAG_DEEP_WATER ) &&
!u.is_underwater() ) {
show_items = false; // Can only see underwater items if WE are underwater
}
// If there's a trap here, and we have sufficient perception, draw that instead
if( curr_trap.can_see( p, g->u ) ) {
tercol = curr_trap.color;
if( curr_trap.sym == '%' ) {
switch( rng( 1, 5 ) ) {
case 1:
sym = '*';
break;
case 2:
sym = '0';
break;
case 3:
sym = '8';
break;
case 4:
sym = '&';
break;
case 5:
sym = '+';
break;
}
} else {
sym = curr_trap.sym;
}
}
if( curr_field.fieldCount() > 0 ) {
const field_id &fid = curr_field.fieldSymbol();
const field_entry *fe = curr_field.findField( fid );
const field_t &f = fieldlist[fid];
if( f.sym == '&' || fe == nullptr ) {
// Do nothing, a '&' indicates invisible fields.
} else if( f.sym == '*' ) {
// A random symbol.
switch( rng( 1, 5 ) ) {
case 1:
sym = '*';
break;
case 2:
sym = '0';
break;
case 3:
sym = '8';
break;
case 4:
sym = '&';
break;
case 5:
sym = '+';
break;
}
} else {
// A field symbol '%' indicates the field should not hide
// items/terrain. When the symbol is not '%' it will
// hide items (the color is still inverted if there are items,
// but the tile symbol is not changed).
// draw_item_sym indicates that the item symbol should be used
// even if sym is not '.'.
// As we don't know at this stage if there are any items
// (that are visible to the player!), we always set the symbol.
// If there are items and the field does not hide them,
// the code handling items will override it.
draw_item_sym = ( f.sym == '%' );
// If field priority is > 1, and the field is set to hide items,
//draw the field as it obscures what's under it.
if( ( f.sym != '%' && f.priority > 1 ) || ( f.sym != '%' && sym == '.' ) ) {
// default terrain '.' and
// non-default field symbol -> field symbol overrides terrain
sym = f.sym;
}
tercol = fe->color();
}
}
// TODO: change the local variable sym to std::string and use it instead of this hack.
// Currently this are different variables because terrain/... uses long as symbol type and
// item now use string. Ideally they should all be strings.
std::string item_sym;
// If there are items here, draw those instead
if( show_items && curr_maptile.get_item_count() > 0 && sees_some_items( p, g->u ) ) {
// if there's furniture/terrain/trap/fields (sym!='.')
// and we should not override it, then only highlight the square
if( sym != '.' && sym != '%' && !draw_item_sym ) {
hi = true;
} else {
// otherwise override with the symbol of the last item
item_sym = curr_maptile.get_uppermost_item().symbol();
if( !draw_item_sym ) {
tercol = curr_maptile.get_uppermost_item().color();
}
if( curr_maptile.get_item_count() > 1 ) {
invert = !invert;
}
}
}
long memory_sym = sym;
int veh_part = 0;
const vehicle *veh = veh_at_internal( p, veh_part );
if( veh != nullptr ) {
sym = special_symbol( veh->face.dir_symbol( veh->part_sym( veh_part ) ) );
tercol = veh->part_color( veh_part );
item_sym.clear(); // clear the item symbol so `sym` is used instead.
if( !veh->forward_velocity() && !veh->player_in_control( g->u ) ) {
memory_sym = sym;
}
}
if( !check_and_set_seen_cache( p ) ) {
g->u.memorize_symbol( getabs( p ), memory_sym );
}
// If there's graffiti here, change background color
if( curr_maptile.has_graffiti() ) {
graf = true;
}
const auto u_vision = u.get_vision_modes();
if( u_vision[BOOMERED] ) {
tercol = c_magenta;
} else if( u_vision[NV_GOGGLES] ) {
tercol = ( bright_light ) ? c_white : c_light_green;
} else if( low_light ) {
tercol = c_dark_gray;
} else if( u_vision[DARKNESS] ) {
tercol = c_dark_gray;
}
if( invert ) {
tercol = invert_color( tercol );
} else if( hi ) {
tercol = hilite( tercol );
} else if( graf ) {
tercol = red_background( tercol );
}
if( inorder ) {
// Rastering the whole map, take advantage of automatically moving the cursor.
if( item_sym.empty() ) {
wputch( w, tercol, sym );
} else {
wprintz( w, tercol, item_sym );
}
} else {
// Otherwise move the cursor before drawing.
const int k = p.x + getmaxx( w ) / 2 - view_center.x;
const int j = p.y + getmaxy( w ) / 2 - view_center.y;
if( item_sym.empty() ) {
mvwputch( w, j, k, tercol, sym );
} else {
mvwprintz( w, j, k, tercol, item_sym );
}
}
return !zlevels || sym != ' ' || !item_sym.empty() || p.z <= -OVERMAP_DEPTH ||
!curr_ter.has_flag( TFLAG_NO_FLOOR );
}
void map::draw_from_above( const catacurses::window &w, player &u, const tripoint &p,
const maptile &curr_tile,
const bool invert,
const tripoint &view_center,
bool low_light, bool bright_light, bool inorder ) const
{
static const long AUTO_WALL_PLACEHOLDER = 2; // this should never appear as a real symbol!
nc_color tercol = c_dark_gray;
long sym = ' ';
const ter_t &curr_ter = curr_tile.get_ter_t();
const furn_t &curr_furn = curr_tile.get_furn_t();
int part_below;
const vehicle *veh;
if( curr_furn.has_flag( TFLAG_SEEN_FROM_ABOVE ) ) {
sym = curr_furn.symbol();
tercol = curr_furn.color();
} else if( curr_furn.movecost < 0 ) {
sym = '.';
tercol = curr_furn.color();
} else if( ( veh = veh_at_internal( p, part_below ) ) != nullptr ) {
const int roof = veh->roof_at_part( part_below );
const int displayed_part = roof >= 0 ? roof : part_below;
sym = special_symbol( veh->face.dir_symbol( veh->part_sym( displayed_part, true ) ) );
tercol = ( roof >= 0 ||
vpart_position( const_cast<vehicle &>( *veh ),
part_below ).obstacle_at_part() ) ? c_light_gray : c_light_gray_cyan;
} else if( curr_ter.has_flag( TFLAG_SEEN_FROM_ABOVE ) ) {
if( curr_ter.has_flag( TFLAG_AUTO_WALL_SYMBOL ) ) {
sym = AUTO_WALL_PLACEHOLDER;
} else if( curr_ter.has_flag( TFLAG_RAMP ) ) {
sym = '>';
} else {
sym = curr_ter.symbol();
}
tercol = curr_ter.color();
} else if( curr_ter.movecost == 0 ) {
sym = '.';
tercol = curr_ter.color();
} else if( !curr_ter.has_flag( TFLAG_NO_FLOOR ) ) {
sym = '.';
if( curr_ter.color() != c_cyan ) {
// Need a special case here, it doesn't cyanize well
tercol = cyan_background( curr_ter.color() );
} else {
tercol = c_black_cyan;
}
} else {
sym = curr_ter.symbol();
tercol = curr_ter.color();
}
if( sym == AUTO_WALL_PLACEHOLDER ) {
sym = determine_wall_corner( p );
}
const auto u_vision = u.get_vision_modes();
if( u_vision[BOOMERED] ) {
tercol = c_magenta;
} else if( u_vision[NV_GOGGLES] ) {
tercol = ( bright_light ) ? c_white : c_light_green;
} else if( low_light ) {
tercol = c_dark_gray;
} else if( u_vision[DARKNESS] ) {
tercol = c_dark_gray;
}
if( invert ) {
tercol = invert_color( tercol );
}
if( inorder ) {
wputch( w, tercol, sym );
} else {
const int k = p.x + getmaxx( w ) / 2 - view_center.x;
const int j = p.y + getmaxy( w ) / 2 - view_center.y;
mvwputch( w, j, k, tercol, sym );
}
}
bool map::sees( const tripoint &F, const tripoint &T, const int range ) const
{
int dummy = 0;
return sees( F, T, range, dummy );
}
/**
* This one is internal-only, we don't want to expose the slope tweaking ickiness outside the map class.
**/
bool map::sees( const tripoint &F, const tripoint &T, const int range, int &bresenham_slope ) const
{
if( ( range >= 0 && range < rl_dist( F, T ) ) ||
!inbounds( T ) ) {
bresenham_slope = 0;
return false; // Out of range!
}
bool visible = true;
// Ugly `if` for now
if( !fov_3d || F.z == T.z ) {
bresenham( F.x, F.y, T.x, T.y, bresenham_slope,
[this, &visible, &T]( const point & new_point ) {
// Exit before checking the last square, it's still visible even if opaque.
if( new_point.x == T.x && new_point.y == T.y ) {
return false;
}
if( !this->trans( tripoint( new_point, T.z ) ) ) {
visible = false;
return false;
}
return true;
} );
return visible;
}
tripoint last_point = F;
bresenham( F, T, bresenham_slope, 0,
[this, &visible, &T, &last_point]( const tripoint & new_point ) {
// Exit before checking the last square, it's still visible even if opaque.
if( new_point == T ) {
return false;
}
// TODO: Allow transparent floors (and cache them!)
if( new_point.z == last_point.z ) {
if( !this->trans( new_point ) ) {
visible = false;
return false;
}
} else {
const int max_z = std::max( new_point.z, last_point.z );
if( ( has_floor_or_support( {new_point.x, new_point.y, max_z} ) ||
!trans( {new_point.x, new_point.y, last_point.z} ) ) &&
( has_floor_or_support( {last_point.x, last_point.y, max_z} ) ||
!trans( {last_point.x, last_point.y, new_point.z} ) ) ) {
visible = false;
return false;
}
}
last_point = new_point;
return true;
} );
return visible;
}
// This method tries a bunch of initial offsets for the line to try and find a clear one.
// Basically it does, "Find a line from any point in the source that ends up in the target square".
std::vector<tripoint> map::find_clear_path( const tripoint &source,
const tripoint &destination ) const
{
// TODO: Push this junk down into the Bresenham method, it's already doing it.
const int dx = destination.x - source.x;
const int dy = destination.y - source.y;
const int ax = std::abs( dx ) * 2;
const int ay = std::abs( dy ) * 2;
const int dominant = std::max( ax, ay );
const int minor = std::min( ax, ay );
// This seems to be the method for finding the ideal start value for the error value.
const int ideal_start_offset = minor - ( dominant / 2 );
const int start_sign = ( ideal_start_offset > 0 ) - ( ideal_start_offset < 0 );
// Not totally sure of the derivation.
const int max_start_offset = std::abs( ideal_start_offset ) * 2 + 1;
for( int horizontal_offset = -1; horizontal_offset <= max_start_offset; ++horizontal_offset ) {
int candidate_offset = horizontal_offset * start_sign;
if( sees( source, destination, rl_dist( source, destination ), candidate_offset ) ) {
return line_to( source, destination, candidate_offset, 0 );
}
}
// If we couldn't find a clear LoS, just return the ideal one.
return line_to( source, destination, ideal_start_offset, 0 );
}
bool map::clear_path( const tripoint &f, const tripoint &t, const int range,
const int cost_min, const int cost_max ) const
{
// Ugly `if` for now
if( !fov_3d && f.z != t.z ) {
return false;
}
if( f.z == t.z ) {
if( ( range >= 0 && range < rl_dist( f.x, f.y, t.x, t.y ) ) ||
!inbounds( t ) ) {
return false; // Out of range!
}
bool is_clear = true;
bresenham( f.x, f.y, t.x, t.y, 0,
[this, &is_clear, cost_min, cost_max, &t]( const point & new_point ) {
// Exit before checking the last square, it's still reachable even if it is an obstacle.
if( new_point.x == t.x && new_point.y == t.y ) {
return false;
}
const int cost = this->move_cost( new_point.x, new_point.y );
if( cost < cost_min || cost > cost_max ) {
is_clear = false;
return false;
}
return true;
} );
return is_clear;
}
if( ( range >= 0 && range < rl_dist( f, t ) ) ||
!inbounds( t ) ) {
return false; // Out of range!
}
bool is_clear = true;
tripoint last_point = f;
bresenham( f, t, 0, 0,
[this, &is_clear, cost_min, cost_max, t, &last_point]( const tripoint & new_point ) {
// Exit before checking the last square, it's still reachable even if it is an obstacle.
if( new_point == t ) {
return false;
}
// We have to check a weird case where the move is both vertical and horizontal
if( new_point.z == last_point.z ) {
const int cost = move_cost( new_point );
if( cost < cost_min || cost > cost_max ) {
is_clear = false;
return false;
}
} else {
bool this_clear = false;
const int max_z = std::max( new_point.z, last_point.z );
if( !has_floor_or_support( {new_point.x, new_point.y, max_z} ) ) {
const int cost = move_cost( {new_point.x, new_point.y, last_point.z} );
if( cost > cost_min && cost < cost_max ) {
this_clear = true;
}
}
if( !this_clear && has_floor_or_support( {last_point.x, last_point.y, max_z} ) ) {
const int cost = move_cost( {last_point.x, last_point.y, new_point.z} );
if( cost > cost_min && cost < cost_max ) {
this_clear = true;
}
}
if( !this_clear ) {
is_clear = false;
return false;
}
}
last_point = new_point;
return true;
} );
return is_clear;
}
bool map::accessible_items( const tripoint &t ) const
{
return !has_flag( "SEALED", t ) || has_flag( "LIQUIDCONT", t );
}
std::vector<tripoint> map::get_dir_circle( const tripoint &f, const tripoint &t ) const
{
std::vector<tripoint> circle;
circle.resize( 8 );
// The line below can be crazy expensive - we only take the FIRST point of it
const std::vector<tripoint> line = line_to( f, t, 0, 0 );
const std::vector<tripoint> spiral = closest_tripoints_first( 1, f );
const std::vector<int> pos_index {1, 2, 4, 6, 8, 7, 5, 3};
// All possible constellations (closest_points_first goes clockwise)
// 753 531 312 124 246 468 687 875
// 8 1 7 2 5 4 3 6 1 8 2 7 4 5 6 3
// 642 864 786 578 357 135 213 421
size_t pos_offset = 0;
for( unsigned int i = 1; i < spiral.size(); i++ ) {
if( spiral[i] == line[0] ) {
pos_offset = i - 1;
break;
}
}
for( unsigned int i = 1; i < spiral.size(); i++ ) {
if( pos_offset >= pos_index.size() ) {
pos_offset = 0;
}
circle[pos_index[pos_offset++] - 1] = spiral[i];
}
return circle;
}
void map::save()
{
for( int gridx = 0; gridx < my_MAPSIZE; gridx++ ) {
for( int gridy = 0; gridy < my_MAPSIZE; gridy++ ) {
if( zlevels ) {
for( int gridz = -OVERMAP_DEPTH; gridz <= OVERMAP_HEIGHT; gridz++ ) {
saven( gridx, gridy, gridz );
}
} else {
saven( gridx, gridy, abs_sub.z );
}
}
}
}
void map::load( const int wx, const int wy, const int wz, const bool update_vehicle )
{
for( auto &traps : traplocs ) {
traps.clear();
}
set_abs_sub( wx, wy, wz );
for( int gridx = 0; gridx < my_MAPSIZE; gridx++ ) {
for( int gridy = 0; gridy < my_MAPSIZE; gridy++ ) {
loadn( gridx, gridy, update_vehicle );
}
}
}
void map::shift_traps( const tripoint &shift )
{
// Offset needs to have sign opposite to shift direction
const tripoint offset( -shift.x * SEEX, -shift.y * SEEY, -shift.z );
for( auto &traps : traplocs ) {
for( auto iter = traps.begin(); iter != traps.end(); ) {
tripoint &pos = *iter;
pos += offset;
if( inbounds( pos ) ) {
++iter;
} else {
// Theoretical enhancement: if this is not the last entry of the vector,
// move the last entry into pos and remove the last entry instead of iter.
// This would avoid moving all the remaining entries.
iter = traps.erase( iter );
}
}
}
}
void shift_map_memory_seen_cache(
std::bitset<MAPSIZE_X *MAPSIZE_Y> &map_memory_seen_cache,
const int sx, const int sy )
{
// sx shifts by SEEX rows, sy shifts by SEEX columns.
int shift_amount = ( sx * SEEX ) + ( sy * MAPSIZE_Y * SEEX );
if( shift_amount > 0 ) {
map_memory_seen_cache >>= static_cast<size_t>( shift_amount );
} else if( shift_amount < 0 ) {
map_memory_seen_cache <<= static_cast<size_t>( -shift_amount );
}
// Shifting in the y direction shifted in 0 values, no no additional clearing is necessary, but
// a shift in the x direction makes values "wrap" to the next row, and they need to be zeroed.
if( sx == 0 ) {
return;
}
const size_t x_offset = ( sx > 0 ) ? MAPSIZE_X - SEEX : 0;
for( size_t y = 0; y < MAPSIZE_X; ++y ) {
size_t y_offset = y * MAPSIZE_X;
for( size_t x = 0; x < SEEX; ++x ) {
map_memory_seen_cache.reset( y_offset + x_offset + x );
}
}
}
void map::shift( const int sx, const int sy )
{
// Special case of 0-shift; refresh the map
if( sx == 0 && sy == 0 ) {
return; // Skip this?
}
const int absx = get_abs_sub().x;
const int absy = get_abs_sub().y;
const int wz = get_abs_sub().z;
set_abs_sub( absx + sx, absy + sy, wz );
// if player is in vehicle, (s)he must be shifted with vehicle too
if( g->u.in_vehicle ) {
g->u.setx( g->u.posx() - sx * SEEX );
g->u.sety( g->u.posy() - sy * SEEY );
}
shift_traps( tripoint( sx, sy, 0 ) );
vehicle *remoteveh = g->remoteveh();
const int zmin = zlevels ? -OVERMAP_DEPTH : wz;
const int zmax = zlevels ? OVERMAP_HEIGHT : wz;
for( int gridz = zmin; gridz <= zmax; gridz++ ) {
for( vehicle *veh : get_cache( gridz ).vehicle_list ) {
veh->smx += sx;
veh->smy += sy;
veh->zones_dirty = true;
}
}
// Shift the map sx submaps to the right and sy submaps down.
// sx and sy should never be bigger than +/-1.
// absx and absy are our position in the world, for saving/loading purposes.
for( int gridz = zmin; gridz <= zmax; gridz++ ) {
// Clear vehicle list and rebuild after shift
clear_vehicle_cache( gridz );
clear_vehicle_list( gridz );
shift_map_memory_seen_cache( get_cache( gridz ).map_memory_seen_cache, sx, sy );
if( sx >= 0 ) {
for( int gridx = 0; gridx < my_MAPSIZE; gridx++ ) {
if( sy >= 0 ) {
for( int gridy = 0; gridy < my_MAPSIZE; gridy++ ) {
if( gridx + sx < my_MAPSIZE && gridy + sy < my_MAPSIZE ) {
copy_grid( tripoint( gridx, gridy, gridz ),
tripoint( gridx + sx, gridy + sy, gridz ) );
update_vehicle_list( get_submap_at_grid( {gridx, gridy, gridz} ), gridz );
} else {
loadn( gridx, gridy, gridz, true );
}
}
} else { // sy < 0; work through it backwards
for( int gridy = my_MAPSIZE - 1; gridy >= 0; gridy-- ) {
if( gridx + sx < my_MAPSIZE && gridy + sy >= 0 ) {
copy_grid( tripoint( gridx, gridy, gridz ),
tripoint( gridx + sx, gridy + sy, gridz ) );
update_vehicle_list( get_submap_at_grid( { gridx, gridy, gridz } ), gridz );
} else {
loadn( gridx, gridy, gridz, true );
}
}
}
}
} else { // sx < 0; work through it backwards
for( int gridx = my_MAPSIZE - 1; gridx >= 0; gridx-- ) {
if( sy >= 0 ) {
for( int gridy = 0; gridy < my_MAPSIZE; gridy++ ) {
if( gridx + sx >= 0 && gridy + sy < my_MAPSIZE ) {
copy_grid( tripoint( gridx, gridy, gridz ),
tripoint( gridx + sx, gridy + sy, gridz ) );
update_vehicle_list( get_submap_at_grid( { gridx, gridy, gridz } ), gridz );
} else {
loadn( gridx, gridy, gridz, true );
}
}
} else { // sy < 0; work through it backwards
for( int gridy = my_MAPSIZE - 1; gridy >= 0; gridy-- ) {
if( gridx + sx >= 0 && gridy + sy >= 0 ) {
copy_grid( tripoint( gridx, gridy, gridz ),
tripoint( gridx + sx, gridy + sy, gridz ) );
update_vehicle_list( get_submap_at_grid( { gridx, gridy, gridz } ), gridz );
} else {
loadn( gridx, gridy, gridz, true );
}
}
}
}
}
reset_vehicle_cache( gridz );
}
g->setremoteveh( remoteveh );
if( !support_cache_dirty.empty() ) {
std::set<tripoint> old_cache = std::move( support_cache_dirty );
support_cache_dirty.clear();
for( const auto &pt : old_cache ) {
support_cache_dirty.insert( tripoint( pt.x - sx * SEEX, pt.y - sy * SEEY, pt.z ) );
}
}
}
void map::vertical_shift( const int newz )
{
if( !zlevels ) {
debugmsg( "Called map::vertical_shift in a non-z-level world" );
return;
}
if( newz < -OVERMAP_DEPTH || newz > OVERMAP_HEIGHT ) {
debugmsg( "Tried to get z-level %d outside allowed range of %d-%d",
newz, -OVERMAP_DEPTH, OVERMAP_HEIGHT );
return;
}
tripoint trp = get_abs_sub();
set_abs_sub( trp.x, trp.y, newz );
// TODO: Remove the function when it's safe
return;
}
// saven saves a single nonant. worldx and worldy are used for the file
// name and specifies where in the world this nonant is. gridx and gridy are
// the offset from the top left nonant:
// 0,0 1,0 2,0
// 0,1 1,1 2,1
// 0,2 1,2 2,2
// (worldx,worldy,worldz) denotes the absolute coordinate of the submap
// in grid[0].
void map::saven( const int gridx, const int gridy, const int gridz )
{
dbg( D_INFO ) << "map::saven(worldx[" << abs_sub.x << "], worldy[" << abs_sub.y << "], worldz[" <<
abs_sub.z
<< "], gridx[" << gridx << "], gridy[" << gridy << "], gridz[" << gridz << "])";
const int gridn = get_nonant( { gridx, gridy, gridz } );
submap *submap_to_save = getsubmap( gridn );
if( submap_to_save == nullptr || submap_to_save->get_ter( point_zero ) == t_null ) {
// This is a serious error and should be signaled as soon as possible
debugmsg( "map::saven grid (%d,%d,%d) %s!", gridx, gridy, gridz,
submap_to_save == nullptr ? "null" : "uninitialized" );
return;
}
const int abs_x = abs_sub.x + gridx;
const int abs_y = abs_sub.y + gridy;
const int abs_z = gridz;
if( !zlevels && gridz != abs_sub.z ) {
debugmsg( "Tried to save submap (%d,%d,%d) as (%d,%d,%d), which isn't supported in non-z-level builds",
abs_x, abs_y, abs_sub.z, abs_x, abs_y, gridz );
}
dbg( D_INFO ) << "map::saven abs_x: " << abs_x << " abs_y: " << abs_y << " abs_z: " << abs_z
<< " gridn: " << gridn;
submap_to_save->last_touched = calendar::turn;
MAPBUFFER.add_submap( abs_x, abs_y, abs_z, submap_to_save );
}
// worldx & worldy specify where in the world this is;
// gridx & gridy specify which nonant:
// 0,0 1,0 2,0
// 0,1 1,1 2,1
// 0,2 1,2 2,2 etc
// (worldx,worldy,worldz) denotes the absolute coordinate of the submap
// in grid[0].
void map::loadn( const int gridx, const int gridy, const bool update_vehicles )
{
if( zlevels ) {
for( int gridz = -OVERMAP_DEPTH; gridz <= OVERMAP_HEIGHT; gridz++ ) {
loadn( gridx, gridy, gridz, update_vehicles );
}
// Note: we want it in a separate loop! It is a post-load cleanup
// Since we're adding roofs, we want it to go up (from lowest to highest)
for( int gridz = -OVERMAP_DEPTH; gridz <= OVERMAP_HEIGHT; gridz++ ) {
add_roofs( gridx, gridy, gridz );
}
} else {
loadn( gridx, gridy, abs_sub.z, update_vehicles );
}
}
// Optimized mapgen function that only works properly for very simple overmap types
// Does not create or require a temporary map and does its own saving
static void generate_uniform( const int x, const int y, const int z, const ter_id &terrain_type )
{
dbg( D_INFO ) << "generate_uniform x: " << x << " y: " << y << " abs_z: " << z
<< " terrain_type: " << terrain_type.id().str();
constexpr size_t block_size = SEEX * SEEY;
for( int xd = 0; xd <= 1; xd++ ) {
for( int yd = 0; yd <= 1; yd++ ) {
submap *sm = new submap();
sm->is_uniform = true;
std::uninitialized_fill_n( &sm->ter[0][0], block_size, terrain_type );
sm->last_touched = calendar::turn;
MAPBUFFER.add_submap( x + xd, y + yd, z, sm );
}
}
}
void map::loadn( const int gridx, const int gridy, const int gridz, const bool update_vehicles )
{
// Cache empty overmap types
static const oter_id rock( "empty_rock" );
static const oter_id air( "open_air" );
dbg( D_INFO ) << "map::loadn(game[" << g.get() << "], worldx[" << abs_sub.x
<< "], worldy[" << abs_sub.y << "], gridx["
<< gridx << "], gridy[" << gridy << "], gridz[" << gridz << "])";
const int absx = abs_sub.x + gridx,
absy = abs_sub.y + gridy;
const size_t gridn = get_nonant( { gridx, gridy, gridz } );
dbg( D_INFO ) << "map::loadn absx: " << absx << " absy: " << absy
<< " gridn: " << gridn;
const int old_abs_z = abs_sub.z; // Ugly, but necessary at the moment
abs_sub.z = gridz;
submap *tmpsub = MAPBUFFER.lookup_submap( absx, absy, gridz );
if( tmpsub == nullptr ) {
// It doesn't exist; we must generate it!
dbg( D_INFO | D_WARNING ) << "map::loadn: Missing mapbuffer data. Regenerating.";
// Each overmap square is two nonants; to prevent overlap, generate only at
// squares divisible by 2.
const int newmapx = absx - ( abs( absx ) % 2 );
const int newmapy = absy - ( abs( absy ) % 2 );
// Short-circuit if the map tile is uniform
int overx = newmapx;
int overy = newmapy;
sm_to_omt( overx, overy );
const oter_id terrain_type = overmap_buffer.ter( overx, overy, gridz );
// @todo: Replace with json mapgen functions.
if( terrain_type == air ) {
generate_uniform( newmapx, newmapy, gridz, t_open_air );
} else if( terrain_type == rock ) {
generate_uniform( newmapx, newmapy, gridz, t_rock );
} else {
tinymap tmp_map;
tmp_map.generate( newmapx, newmapy, gridz, calendar::turn );
}
// This is the same call to MAPBUFFER as above!
tmpsub = MAPBUFFER.lookup_submap( absx, absy, gridz );
if( tmpsub == nullptr ) {
dbg( D_ERROR ) << "failed to generate a submap at " << absx << absy << abs_sub.z;
debugmsg( "failed to generate a submap at %d,%d,%d", absx, absy, abs_sub.z );
return;
}
}
// New submap changes the content of the map and all caches must be recalculated
set_transparency_cache_dirty( gridz );
set_outside_cache_dirty( gridz );
set_floor_cache_dirty( gridz );
set_pathfinding_cache_dirty( gridz );
setsubmap( gridn, tmpsub );
// Destroy bugged no-part vehicles
auto &veh_vec = tmpsub->vehicles;
for( auto iter = veh_vec.begin(); iter != veh_vec.end(); ) {
vehicle *veh = iter->get();
if( !veh->parts.empty() ) {
// Always fix submap coordinates for easier Z-level-related operations
veh->smx = gridx;
veh->smy = gridy;
veh->smz = gridz;
iter++;
} else {
reset_vehicle_cache( gridz );
if( veh->tracking_on ) {
overmap_buffer.remove_vehicle( veh );
}
dirty_vehicle_list.erase( veh );
iter = veh_vec.erase( iter );
}
}
// Update vehicle data
if( update_vehicles ) {
auto &map_cache = get_cache( gridz );
for( const auto &veh : tmpsub->vehicles ) {
// Only add if not tracking already.
if( map_cache.vehicle_list.find( veh.get() ) == map_cache.vehicle_list.end() ) {
map_cache.vehicle_list.insert( veh.get() );
if( !veh->loot_zones.empty() ) {
map_cache.zone_vehicles.insert( veh.get() );
}
add_vehicle_to_cache( veh.get() );
}
}
}
actualize( gridx, gridy, gridz );
abs_sub.z = old_abs_z;
}
bool map::has_rotten_away( item &itm, const tripoint &pnt ) const
{
if( itm.is_corpse() ) {
itm.calc_rot( pnt );
return itm.get_rot() > 10_days && !itm.can_revive();
} else if( itm.goes_bad() ) {
itm.calc_rot( pnt );
return itm.has_rotten_away();
} else if( itm.type->container && itm.type->container->preserves ) {
// Containers like tin cans preserves all items inside, they do not rot at all.
return false;
} else if( itm.type->container && itm.type->container->seals ) {
// Items inside rot but do not vanish as the container seals them in.
for( auto &c : itm.contents ) {
c.calc_rot( pnt );
}
return false;
} else {
// Check and remove rotten contents, but always keep the container.
for( auto it = itm.contents.begin(); it != itm.contents.end(); ) {
if( has_rotten_away( *it, pnt ) ) {
it = itm.contents.erase( it );
} else {
++it;
}
}
return false;
}
}
template <typename Container>
void map::remove_rotten_items( Container &items, const tripoint &pnt )
{
const tripoint abs_pnt = getabs( pnt );
for( auto it = items.begin(); it != items.end(); ) {
if( has_rotten_away( *it, abs_pnt ) ) {
if( it->is_comestible() ) {
rotten_item_spawn( *it, pnt );
}
it = i_rem( pnt, it );
} else {
++it;
}
}
}
void map::rotten_item_spawn( const item &item, const tripoint &pnt )
{
if( g->critter_at( pnt ) != nullptr ) {
return;
}
auto &comest = item.type->comestible;
mongroup_id mgroup = comest->rot_spawn;
if( mgroup == "GROUP_NULL" ) {
return;
}
const int chance = ( comest->rot_spawn_chance * get_option<int>( "CARRION_SPAWNRATE" ) ) / 100;
if( rng( 0, 100 ) < chance ) {
MonsterGroupResult spawn_details = MonsterGroupManager::GetResultFromGroup( mgroup );
add_spawn( spawn_details.name, 1, pnt.x, pnt.y, false );
if( g->u.sees( pnt ) ) {
if( item.is_seed() ) {
add_msg( m_warning, _( "Something has crawled out of the %s plants!" ), item.get_plant_name() );
} else {
add_msg( m_warning, _( "Something has crawled out of the %s!" ), item.tname().c_str() );
}
}
}
}
void map::fill_funnels( const tripoint &p, const time_point &since )
{
const auto &tr = tr_at( p );
if( !tr.is_funnel() ) {
return;
}
// Note: the inside/outside cache might not be correct at this time
if( has_flag_ter_or_furn( TFLAG_INDOORS, p ) ) {
return;
}
auto items = i_at( p );
units::volume maxvolume = 0_ml;
auto biggest_container = items.end();
for( auto candidate = items.begin(); candidate != items.end(); ++candidate ) {
if( candidate->is_funnel_container( maxvolume ) ) {
biggest_container = candidate;
}
}
if( biggest_container != items.end() ) {
retroactively_fill_from_funnel( *biggest_container, tr, since, calendar::turn, getabs( p ) );
}
}
void map::grow_plant( const tripoint &p )
{
const auto &furn = this->furn( p ).obj();
if( !furn.has_flag( "PLANT" ) ) {
return;
}
auto items = i_at( p );
if( items.empty() ) {
// No seed there anymore, we don't know what kind of plant it was.
dbg( D_ERROR ) << "a seed item has vanished at " << p.x << "," << p.y << "," << p.z;
furn_set( p, f_null );
return;
}
auto seed = items.front();
if( !seed.is_seed() ) {
// No seed there anymore, we don't know what kind of plant it was.
dbg( D_ERROR ) << "a planted item at " << p.x << "," << p.y << "," << p.z << " has no seed data";
furn_set( p, f_null );
return;
}
const time_duration plantEpoch = seed.get_plant_epoch();
furn_id cur_furn = this->furn( p ).id();
if( seed.age() >= plantEpoch && cur_furn != furn_str_id( "f_plant_harvest" ) ) {
if( seed.age() < plantEpoch * 2 ) {
if( cur_furn == furn_str_id( "f_plant_seedling" ) ) {
return;
}
i_rem( p, 1 );
rotten_item_spawn( seed, p );
furn_set( p, furn_str_id( "f_plant_seedling" ) );
} else if( seed.age() < plantEpoch * 3 ) {
if( cur_furn == furn_str_id( "f_plant_mature" ) ) {
return;
}
i_rem( p, 1 );
rotten_item_spawn( seed, p );
//You've skipped the seedling stage so roll monsters twice
if( cur_furn != furn_str_id( "f_plant_seedling" ) ) {
rotten_item_spawn( seed, p );
}
furn_set( p, furn_str_id( "f_plant_mature" ) );
} else {
//You've skipped two stages so roll monsters two times
if( cur_furn == furn_str_id( "f_plant_seedling" ) ) {
rotten_item_spawn( seed, p );
rotten_item_spawn( seed, p );
//One stage change
} else if( cur_furn == furn_str_id( "f_plant_mature" ) ) {
rotten_item_spawn( seed, p );
//Goes from seed to harvest in one check
} else {
rotten_item_spawn( seed, p );
rotten_item_spawn( seed, p );
rotten_item_spawn( seed, p );
}
furn_set( p, furn_str_id( "f_plant_harvest" ) );
}
}
}
void map::restock_fruits( const tripoint &p, const time_duration &time_since_last_actualize )
{
const auto &ter = this->ter( p ).obj();
if( !ter.has_flag( TFLAG_HARVESTED ) ) {
return; // Already harvestable. Do nothing.
}
// Make it harvestable again if the last actualization was during a different season or year.
const time_point last_touched = calendar::turn - time_since_last_actualize;
if( season_of_year( calendar::turn ) != season_of_year( last_touched ) ||
time_since_last_actualize >= calendar::season_length() ) {
ter_set( p, ter.transforms_into );
}
}
void map::produce_sap( const tripoint &p, const time_duration &time_since_last_actualize )
{
if( time_since_last_actualize <= 0_turns ) {
return;
}
if( t_tree_maple_tapped != ter( p ) ) {
return;
}
// Amount of maple sap liters produced per season per tap
static const int maple_sap_per_season = 56;
// How many turns to produce 1 charge (250 ml) of sap?
const time_duration producing_length = 0.75 * calendar::season_length();
const time_duration turns_to_produce = producing_length / ( maple_sap_per_season * 4 );
// How long of this time_since_last_actualize have we been in the producing period (late winter, early spring)?
time_duration time_producing = 0_turns;
if( time_since_last_actualize >= calendar::year_length() ) {
time_producing = producing_length;
} else {
// We are only producing sap on the intersection with the sap producing season.
const time_duration early_spring_end = 0.5f * calendar::season_length();
const time_duration late_winter_start = 3.75f * calendar::season_length();
const time_point last_actualize = calendar::turn - time_since_last_actualize;
const time_duration last_actualize_tof = time_past_new_year( last_actualize );
bool last_producing = (
last_actualize_tof >= late_winter_start ||
last_actualize_tof < early_spring_end
);
const time_duration current_tof = time_past_new_year( calendar::turn );
bool current_producing = (
current_tof >= late_winter_start ||
current_tof < early_spring_end
);
const time_duration non_producing_length = 3.25 * calendar::season_length();
if( last_producing && current_producing ) {
if( time_since_last_actualize < non_producing_length ) {
time_producing = time_since_last_actualize;
} else {
time_producing = time_since_last_actualize - non_producing_length;
}
} else if( !last_producing && !current_producing ) {
if( time_since_last_actualize > non_producing_length ) {
time_producing = time_since_last_actualize - non_producing_length;
}
} else if( last_producing && !current_producing ) {
// We hit the end of early spring
if( last_actualize_tof < early_spring_end ) {
time_producing = early_spring_end - last_actualize_tof;
} else {
time_producing = calendar::year_length() - last_actualize_tof + early_spring_end;
}
} else if( !last_producing && current_producing ) {
// We hit the start of late winter
if( current_tof >= late_winter_start ) {
time_producing = current_tof - late_winter_start;
} else {
time_producing = 0.25f * calendar::season_length() + current_tof;
}
}
}
long new_charges = roll_remainder( time_producing / turns_to_produce );
// Not enough time to produce 1 charge of sap
if( new_charges <= 0 ) {
return;
}
item sap( "maple_sap", calendar::turn );
// Is there a proper container?
auto items = i_at( p );
for( auto &it : items ) {
if( it.is_bucket() || it.is_watertight_container() ) {
const long capacity = it.get_remaining_capacity_for_liquid( sap, true );
if( capacity > 0 ) {
new_charges = std::min<long>( new_charges, capacity );
// The environment might have poisoned the sap with animals passing by, insects, leaves or contaminants in the ground
sap.poison = one_in( 10 ) ? 1 : 0;
sap.charges = new_charges;
it.fill_with( sap );
}
// Only fill up the first container.
break;
}
}
}
void map::rad_scorch( const tripoint &p, const time_duration &time_since_last_actualize )
{
const int rads = get_radiation( p );
if( rads == 0 ) {
return;
}
// TODO: More interesting rad scorch chance - base on season length?
if( !x_in_y( 1.0 * rads * rads * time_since_last_actualize, 91_days ) ) {
return;
}
// First destroy the farmable plants (those are furniture)
// TODO: Rad-resistant mutant plants (that produce radioactive fruit)
const furn_t &fid = furn( p ).obj();
if( fid.has_flag( "PLANT" ) ) {
i_clear( p );
furn_set( p, f_null );
}
const ter_id tid = ter( p );
// TODO: De-hardcode this
static const std::map<ter_id, ter_str_id> dies_into {{
{t_grass, ter_str_id( "t_dirt" )},
{t_tree_young, ter_str_id( "t_dirt" )},
{t_tree_pine, ter_str_id( "t_tree_deadpine" )},
{t_tree_birch, ter_str_id( "t_tree_birch_harvested" )},
{t_tree_willow, ter_str_id( "t_tree_willow_harvested" )},
{t_tree_hickory, ter_str_id( "t_tree_hickory_dead" )},
{t_tree_hickory_harvested, ter_str_id( "t_tree_hickory_dead" )},
}};
const auto iter = dies_into.find( tid );
if( iter != dies_into.end() ) {
ter_set( p, iter->second );
return;
}
const ter_t &tr = tid.obj();
if( tr.has_flag( "SHRUB" ) ) {
ter_set( p, t_dirt );
} else if( tr.has_flag( "TREE" ) ) {
ter_set( p, ter_str_id( "t_tree_dead" ) );
}
}
void map::decay_cosmetic_fields( const tripoint &p, const time_duration &time_since_last_actualize )
{
for( auto &pr : field_at( p ) ) {
auto &fd = pr.second;
if( !fd.decays_on_actualize() ) {
continue;
}
const time_duration added_age = 2 * time_since_last_actualize / rng( 2, 4 );
fd.mod_age( added_age );
const time_duration hl = fieldlist[ fd.getFieldType() ].halflife;
const int density_drop = fd.getFieldAge() / hl;
if( density_drop > 0 ) {
fd.setFieldDensity( fd.getFieldDensity() - density_drop );
fd.mod_age( -hl * density_drop );
}
}
}
void map::actualize( const int gridx, const int gridy, const int gridz )
{
submap *const tmpsub = get_submap_at_grid( {gridx, gridy, gridz} );
if( tmpsub == nullptr ) {
debugmsg( "Actualize called on null submap (%d,%d,%d)", gridx, gridy, gridz );
return;
}
const time_duration time_since_last_actualize = calendar::turn - tmpsub->last_touched;
const bool do_funnels = ( gridz >= 0 );
// check spoiled stuff, and fill up funnels while we're at it
for( int x = 0; x < SEEX; x++ ) {
for( int y = 0; y < SEEY; y++ ) {
const tripoint pnt( gridx * SEEX + x, gridy * SEEY + y, gridz );
const point p( x, y );
const auto &furn = this->furn( pnt ).obj();
// plants contain a seed item which must not be removed under any circumstances
if( !furn.has_flag( "DONT_REMOVE_ROTTEN" ) ) {
remove_rotten_items( tmpsub->itm[x][y], pnt );
}
const auto trap_here = tmpsub->get_trap( p );
if( trap_here != tr_null ) {
traplocs[trap_here].push_back( pnt );
}
const ter_t &ter = tmpsub->get_ter( p ).obj();
if( ter.trap != tr_null && ter.trap != tr_ledge ) {
traplocs[trap_here].push_back( pnt );
}
if( do_funnels ) {
fill_funnels( pnt, tmpsub->last_touched );
}
grow_plant( pnt );
restock_fruits( pnt, time_since_last_actualize );
produce_sap( pnt, time_since_last_actualize );
rad_scorch( pnt, time_since_last_actualize );
decay_cosmetic_fields( pnt, time_since_last_actualize );
}
}
//Check for Merchants to restock
for( npc &guy : g->all_npcs() ) {
if( guy.restock != calendar::before_time_starts && calendar::turn > guy.restock ) {
guy.shop_restock();
}
}
// the last time we touched the submap, is right now.
tmpsub->last_touched = calendar::turn;
}
void map::add_roofs( const int gridx, const int gridy, const int gridz )
{
if( !zlevels ) {
// No roofs required!
// Why not? Because submaps below and above don't exist yet
return;
}
submap *const sub_here = get_submap_at_grid( {gridx, gridy, gridz} );
if( sub_here == nullptr ) {
debugmsg( "Tried to add roofs/floors on null submap on %d,%d,%d",
gridx, gridy, gridz );
return;
}
bool check_roof = gridz > -OVERMAP_DEPTH;
submap *const sub_below = check_roof ? get_submap_at_grid( { gridx, gridy, gridz - 1 } ) : nullptr;
if( check_roof && sub_below == nullptr ) {
debugmsg( "Tried to add roofs to sm at %d,%d,%d, but sm below doesn't exist",
gridx, gridy, gridz );
return;
}
for( int x = 0; x < SEEX; x++ ) {
for( int y = 0; y < SEEY; y++ ) {
const ter_id ter_here = sub_here->ter[x][y];
if( ter_here != t_open_air ) {
continue;
}
if( !check_roof ) {
// Make sure we don't have open air at lowest z-level
sub_here->ter[x][y] = t_rock_floor;
continue;
}
const ter_t &ter_below = sub_below->ter[x][y].obj();
if( ter_below.roof ) {
// TODO: Make roof variable a ter_id to speed this up
sub_here->ter[x][y] = ter_below.roof.id();
}
}
}
}
void map::copy_grid( const tripoint &to, const tripoint &from )
{
const auto smap = get_submap_at_grid( from );
setsubmap( get_nonant( to ), smap );
for( auto &it : smap->vehicles ) {
it->smx = to.x;
it->smy = to.y;
}
}
void map::spawn_monsters_submap_group( const tripoint &gp, mongroup &group, bool ignore_sight )
{
const int s_range = std::min( HALF_MAPSIZE_X,
g->u.sight_range( g->light_level( g->u.posz() ) ) );
int pop = group.population;
std::vector<tripoint> locations;
if( !ignore_sight ) {
// If the submap is one of the outermost submaps, assume that monsters are
// invisible there.
if( gp.x == 0 || gp.y == 0 || gp.x + 1 == MAPSIZE || gp.y + 1 == MAPSIZE ) {
ignore_sight = true;
}
}
if( gp.z != g->u.posz() ) {
// Note: this is only OK because 3D vision isn't a thing yet
ignore_sight = true;
}
// If the submap is uniform, we can skip many checks
const submap *current_submap = get_submap_at_grid( gp );
bool ignore_terrain_checks = false;
bool ignore_inside_checks = gp.z < 0;
if( current_submap->is_uniform ) {
const tripoint upper_left{ SEEX * gp.x, SEEY * gp.y, gp.z };
if( impassable( upper_left ) ||
( !ignore_inside_checks && has_flag_ter_or_furn( TFLAG_INDOORS, upper_left ) ) ) {
const tripoint glp = getabs( gp );
dbg( D_WARNING ) << "Empty locations for group " << group.type.str() <<
" at uniform submap " << gp.x << "," << gp.y << "," << gp.z <<
" global " << glp.x << "," << glp.y << "," << glp.z;
return;
}
ignore_terrain_checks = true;
ignore_inside_checks = true;
}
for( int x = 0; x < SEEX; ++x ) {
for( int y = 0; y < SEEY; ++y ) {
int fx = x + SEEX * gp.x;
int fy = y + SEEY * gp.y;
tripoint fp{ fx, fy, gp.z };
if( g->critter_at( fp ) != nullptr ) {
continue; // there is already some creature
}
if( !ignore_terrain_checks && impassable( fp ) ) {
continue; // solid area, impassable
}
if( !ignore_sight && sees( g->u.pos(), fp, s_range ) ) {
continue; // monster must spawn outside the viewing range of the player
}
if( !ignore_inside_checks && has_flag_ter_or_furn( TFLAG_INDOORS, fp ) ) {
continue; // monster must spawn outside.
}
locations.push_back( fp );
}
}
if( locations.empty() ) {
// TODO: what now? there is no possible place to spawn monsters, most
// likely because the player can see all the places.
const tripoint glp = getabs( gp );
dbg( D_WARNING ) << "Empty locations for group " << group.type.str() <<
" at " << gp.x << "," << gp.y << "," << gp.z <<
" global " << glp.x << "," << glp.y << "," << glp.z;
// Just kill the group. It's not like we're removing existing monsters
// Unless it's a horde - then don't kill it and let it spawn behind a tree or smoke cloud
if( !group.horde ) {
group.clear();
}
return;
}
if( pop ) {
// Populate the group from its population variable.
for( int m = 0; m < pop; m++ ) {
MonsterGroupResult spawn_details = MonsterGroupManager::GetResultFromGroup( group.type, &pop );
if( !spawn_details.name ) {
continue;
}
monster tmp( spawn_details.name );
// If a monster came from a horde population, configure them to always be willing to rejoin a horde.
if( group.horde ) {
tmp.set_horde_attraction( MHA_ALWAYS );
}
for( int i = 0; i < spawn_details.pack_size; i++ ) {
group.monsters.push_back( tmp );
}
}
}
// Find horde's target submap
tripoint horde_target( group.target.x - abs_sub.x,
group.target.y - abs_sub.y, abs_sub.z );
sm_to_ms( horde_target );
for( auto &tmp : group.monsters ) {
for( int tries = 0; tries < 10 && !locations.empty(); tries++ ) {
const tripoint p = random_entry_removed( locations );
if( !tmp.can_move_to( p ) ) {
continue; // target can not contain the monster
}
tmp.spawn( p );
if( group.horde ) {
// Give monster a random point near horde's expected destination
const tripoint rand_dest = horde_target +
point( rng( 0, SEEX ), rng( 0, SEEY ) );
const int turns = rl_dist( p, rand_dest ) + group.interest;
tmp.wander_to( rand_dest, turns );
add_msg( m_debug, "%s targeting %d,%d,%d", tmp.disp_name().c_str(),
tmp.wander_pos.x, tmp.wander_pos.y, tmp.wander_pos.z );
}
g->add_zombie( tmp );
break;
}
}
// indicates the group is empty, and can be removed later
group.clear();
}
void map::spawn_monsters_submap( const tripoint &gp, bool ignore_sight )
{
// Load unloaded monsters
overmap_buffer.spawn_monster( abs_sub.x + gp.x, abs_sub.y + gp.y, gp.z );
// Only spawn new monsters after existing monsters are loaded.
auto groups = overmap_buffer.groups_at( abs_sub.x + gp.x, abs_sub.y + gp.y, gp.z );
for( auto &mgp : groups ) {
spawn_monsters_submap_group( gp, *mgp, ignore_sight );
}
submap *const current_submap = get_submap_at_grid( gp );
for( auto &i : current_submap->spawns ) {
for( int j = 0; j < i.count; j++ ) {
int tries = 0;
int mx = i.pos.x;
int my = i.pos.y;
monster tmp( i.type );
tmp.mission_id = i.mission_id;
if( i.name != "NONE" ) {
tmp.unique_name = i.name;
}
if( i.friendly ) {
tmp.friendly = -1;
}
int fx = mx + gp.x * SEEX;
int fy = my + gp.y * SEEY;
tripoint pos( fx, fy, gp.z );
while( ( !g->is_empty( pos ) || !tmp.can_move_to( pos ) ) && tries < 10 ) {
mx = ( i.pos.x + rng( -3, 3 ) ) % SEEX;
my = ( i.pos.y + rng( -3, 3 ) ) % SEEY;
if( mx < 0 ) {
mx += SEEX;
}
if( my < 0 ) {
my += SEEY;
}
fx = mx + gp.x * SEEX;
fy = my + gp.y * SEEY;
tries++;
pos = tripoint( fx, fy, gp.z );
}
if( tries != 10 ) {
tmp.spawn( pos );
g->add_zombie( tmp );
}
}
}
current_submap->spawns.clear();
}
void map::spawn_monsters( bool ignore_sight )
{
const int zmin = zlevels ? -OVERMAP_DEPTH : abs_sub.z;
const int zmax = zlevels ? OVERMAP_HEIGHT : abs_sub.z;
tripoint gp;
int &gx = gp.x;
int &gy = gp.y;
int &gz = gp.z;
for( gz = zmin; gz <= zmax; gz++ ) {
for( gx = 0; gx < my_MAPSIZE; gx++ ) {
for( gy = 0; gy < my_MAPSIZE; gy++ ) {
spawn_monsters_submap( gp, ignore_sight );
}
}
}
}
void map::clear_spawns()
{
for( auto &smap : grid ) {
smap->spawns.clear();
}
}
void map::clear_traps()
{
for( auto &smap : grid ) {
for( int x = 0; x < SEEX; x++ ) {
for( int y = 0; y < SEEY; y++ ) {
const point p( x, y );
smap->set_trap( p, tr_null );
}
}
}
// Forget about all trap locations.
for( auto &i : traplocs ) {
i.clear();
}
}
const std::vector<tripoint> &map::trap_locations( const trap_id type ) const
{
return traplocs[type];
}
bool map::inbounds( const tripoint &p ) const
{
constexpr tripoint map_boundary_min( 0, 0, -OVERMAP_DEPTH );
constexpr tripoint map_boundary_max( MAPSIZE_Y, MAPSIZE_X, OVERMAP_HEIGHT );
constexpr tripoint map_clearance_min( tripoint_zero );
constexpr tripoint map_clearance_max( 1, 1, 0 );
constexpr box map_boundaries( map_boundary_min, map_boundary_max );
constexpr box map_clearance( map_clearance_min, map_clearance_max );
return generic_inbounds( p, map_boundaries, map_clearance );
}
bool tinymap::inbounds( const tripoint &p ) const
{
constexpr tripoint map_boundary_min( 0, 0, -OVERMAP_DEPTH );
constexpr tripoint map_boundary_max( SEEY * 2, SEEX * 2, OVERMAP_HEIGHT );
constexpr tripoint map_clearance_min( tripoint_zero );
constexpr tripoint map_clearance_max( 1, 1, 0 );
constexpr box map_boundaries( map_boundary_min, map_boundary_max );
constexpr box map_clearance( map_clearance_min, map_clearance_max );
return generic_inbounds( p, map_boundaries, map_clearance );
}
void map::set_graffiti( const tripoint &p, const std::string &contents )
{
if( !inbounds( p ) ) {
return;
}
point l;
submap *const current_submap = get_submap_at( p, l );
current_submap->set_graffiti( l, contents );
}
void map::delete_graffiti( const tripoint &p )
{
if( !inbounds( p ) ) {
return;
}
point l;
submap *const current_submap = get_submap_at( p, l );
current_submap->delete_graffiti( l );
}
const std::string &map::graffiti_at( const tripoint &p ) const
{
if( !inbounds( p ) ) {
static const std::string empty_string;
return empty_string;
}
point l;
submap *const current_submap = get_submap_at( p, l );
return current_submap->get_graffiti( l );
}
bool map::has_graffiti_at( const tripoint &p ) const
{
if( !inbounds( p ) ) {
return false;
}
point l;
submap *const current_submap = get_submap_at( p, l );
return current_submap->has_graffiti( l );
}
long map::determine_wall_corner( const tripoint &p ) const
{
int test_connect_group = ter( tripoint( p.x, p.y, p.z ) ).obj().connect_group;
uint8_t connections = get_known_connections( p, test_connect_group );
// The bits in connections are SEWN, whereas the characters in LINE_
// constants are NESW, so we want values in 8 | 2 | 1 | 4 order.
switch( connections ) {
case 8 | 2 | 1 | 4:
return LINE_XXXX;
case 0 | 2 | 1 | 4:
return LINE_OXXX;
case 8 | 0 | 1 | 4:
return LINE_XOXX;
case 0 | 0 | 1 | 4:
return LINE_OOXX;
case 8 | 2 | 0 | 4:
return LINE_XXOX;
case 0 | 2 | 0 | 4:
return LINE_OXOX;
case 8 | 0 | 0 | 4:
return LINE_XOOX;
case 0 | 0 | 0 | 4:
return LINE_OXOX; // LINE_OOOX would be better
case 8 | 2 | 1 | 0:
return LINE_XXXO;
case 0 | 2 | 1 | 0:
return LINE_OXXO;
case 8 | 0 | 1 | 0:
return LINE_XOXO;
case 0 | 0 | 1 | 0:
return LINE_XOXO; // LINE_OOXO would be better
case 8 | 2 | 0 | 0:
return LINE_XXOO;
case 0 | 2 | 0 | 0:
return LINE_OXOX; // LINE_OXOO would be better
case 8 | 0 | 0 | 0:
return LINE_XOXO; // LINE_XOOO would be better
case 0 | 0 | 0 | 0:
return ter( p ).obj().symbol(); // technically just a column
default:
// assert( false );
// this shall not happen
return '?';
}
}
void map::build_outside_cache( const int zlev )
{
auto &ch = get_cache( zlev );
if( !ch.outside_cache_dirty ) {
return;
}
// Make a bigger cache to avoid bounds checking
// We will later copy it to our regular cache
const size_t padded_w = ( MAPSIZE_X ) + 2;
const size_t padded_h = ( MAPSIZE_Y ) + 2;
bool padded_cache[padded_w][padded_h];
auto &outside_cache = ch.outside_cache;
if( zlev < 0 ) {
std::uninitialized_fill_n(
&outside_cache[0][0], ( MAPSIZE_X ) * ( MAPSIZE_Y ), false );
return;
}
std::uninitialized_fill_n(
&padded_cache[0][0], padded_w * padded_h, true );
for( int smx = 0; smx < my_MAPSIZE; ++smx ) {
for( int smy = 0; smy < my_MAPSIZE; ++smy ) {
const auto cur_submap = get_submap_at_grid( { smx, smy, zlev } );
for( int sx = 0; sx < SEEX; ++sx ) {
for( int sy = 0; sy < SEEY; ++sy ) {
point sp( sx, sy );
if( cur_submap->get_ter( sp ).obj().has_flag( TFLAG_INDOORS ) ||
cur_submap->get_furn( sp ).obj().has_flag( TFLAG_INDOORS ) ) {
const int x = sx + ( smx * SEEX );
const int y = sy + ( smy * SEEY );
// Add 1 to both coordinates, because we're operating on the padded cache
for( int dx = 0; dx <= 2; dx++ ) {
for( int dy = 0; dy <= 2; dy++ ) {
padded_cache[x + dx][y + dy] = false;
}
}
}
}
}
}
}
// Copy the padded cache back to the proper one, but with no padding
for( int x = 0; x < SEEX * my_MAPSIZE; x++ ) {
std::copy_n( &padded_cache[x + 1][1], SEEX * my_MAPSIZE, &outside_cache[x][0] );
}
ch.outside_cache_dirty = false;
}
void map::build_obstacle_cache( const tripoint &start, const tripoint &end,
fragment_cloud( &obstacle_cache )[MAPSIZE_X][MAPSIZE_Y] )
{
const point min_submap{ std::max( 0, start.x / SEEX ), std::max( 0, start.y / SEEY ) };
const point max_submap{
std::min( my_MAPSIZE - 1, end.x / SEEX ), std::min( my_MAPSIZE - 1, end.y / SEEY ) };
// Find and cache all the map obstacles.
// For now setting obstacles to be extremely dense and fill their squares.
// In future, scale effective obstacle density by the thickness of the obstacle.
// Also consider modelling partial obstacles.
// TODO: Support z-levels.
for( int smx = min_submap.x; smx <= max_submap.x; ++smx ) {
for( int smy = min_submap.y; smy <= max_submap.y; ++smy ) {
const auto cur_submap = get_submap_at_grid( { smx, smy, start.z } );
// TODO: Init indices to prevent iterating over unused submap sections.
for( int sx = 0; sx < SEEX; ++sx ) {
for( int sy = 0; sy < SEEY; ++sy ) {
const point sp( sx, sy );
int ter_move = cur_submap->get_ter( sp ).obj().movecost;
int furn_move = cur_submap->get_furn( sp ).obj().movecost;
const int x = sx + ( smx * SEEX );
const int y = sy + ( smy * SEEY );
if( ter_move == 0 || furn_move < 0 || ter_move + furn_move == 0 ) {
obstacle_cache[x][y].velocity = 1000.0f;
obstacle_cache[x][y].density = 0.0f;
} else {
// Magic number warning, this is the density of air at sea level at
// some nominal temp and humidity.
// TODO: figure out if our temp/altitude/humidity variation is
// sufficient to bother setting this differently.
obstacle_cache[x][y].velocity = 1.2f;
obstacle_cache[x][y].density = 1.0f;
}
}
}
}
}
VehicleList vehs = get_vehicles( start, end );
// Cache all the vehicle stuff in one loop
for( auto &v : vehs ) {
for( const vpart_reference &vp : v.v->get_all_parts() ) {
int px = v.x + vp.part().precalc[0].x;
int py = v.y + vp.part().precalc[0].y;
if( v.z != start.z ) {
break;
}
if( px < start.x || py < start.y || v.z < start.z ||
px > end.x || py > end.y || v.z > end.z ) {
continue;
}
if( vp.obstacle_at_part() ) {
obstacle_cache[px][py].velocity = 1000.0f;
obstacle_cache[px][py].density = 0.0f;
}
}
}
// Iterate over creatures and set them to block their squares relative to their size.
for( Creature &critter : g->all_creatures() ) {
const tripoint &loc = critter.pos();
if( loc.z != start.z ) {
continue;
}
// TODO: scale this with expected creature "thickness".
obstacle_cache[loc.x][loc.y].velocity = 1000.0f;
// ranged_target_size is "proportion of square that is blocked", and density needs to be
// "transmissivity of square", so we need the reciprocal.
obstacle_cache[loc.x][loc.y].density = 1.0 - critter.ranged_target_size();
}
}
void map::build_floor_cache( const int zlev )
{
auto &ch = get_cache( zlev );
if( !ch.floor_cache_dirty ) {
return;
}
auto &floor_cache = ch.floor_cache;
std::uninitialized_fill_n(
&floor_cache[0][0], ( MAPSIZE_X ) * ( MAPSIZE_Y ), true );
for( int smx = 0; smx < my_MAPSIZE; ++smx ) {
for( int smy = 0; smy < my_MAPSIZE; ++smy ) {
const auto cur_submap = get_submap_at_grid( { smx, smy, zlev } );
for( int sx = 0; sx < SEEX; ++sx ) {
for( int sy = 0; sy < SEEY; ++sy ) {
// Note: furniture currently can't affect existence of floor
if( cur_submap->get_ter( { sx, sy } ).obj().has_flag( TFLAG_NO_FLOOR ) ) {
const int x = sx + ( smx * SEEX );
const int y = sy + ( smy * SEEY );
floor_cache[x][y] = false;
}
}
}
}
}
ch.floor_cache_dirty = false;
}
void map::build_floor_caches()
{
const int minz = zlevels ? -OVERMAP_DEPTH : abs_sub.z;
const int maxz = zlevels ? OVERMAP_HEIGHT : abs_sub.z;
for( int z = minz; z <= maxz; z++ ) {
build_floor_cache( z );
}
}
void map::build_map_cache( const int zlev, bool skip_lightmap )
{
const int minz = zlevels ? -OVERMAP_DEPTH : zlev;
const int maxz = zlevels ? OVERMAP_HEIGHT : zlev;
for( int z = minz; z <= maxz; z++ ) {
build_outside_cache( z );
build_transparency_cache( z );
build_floor_cache( z );
}
tripoint start( 0, 0, minz );
tripoint end( SEEX * my_MAPSIZE, SEEY * my_MAPSIZE, maxz );
VehicleList vehs = get_vehicles( start, end );
// Cache all the vehicle stuff in one loop
for( auto &v : vehs ) {
auto &ch = get_cache( v.z );
auto &outside_cache = ch.outside_cache;
auto &transparency_cache = ch.transparency_cache;
auto &floor_cache = ch.floor_cache;
for( const vpart_reference &vp : v.v->get_all_parts() ) {
const size_t part = vp.part_index();
int px = v.x + vp.part().precalc[0].x;
int py = v.y + vp.part().precalc[0].y;
const point p( px, py );
if( !inbounds( p ) ) {
continue;
}
bool vehicle_is_opaque =
vp.has_feature( VPFLAG_OPAQUE ) && !vp.part().is_broken();
if( vehicle_is_opaque ) {
int dpart = v.v->part_with_feature( part, VPFLAG_OPENABLE, true );
if( dpart < 0 || !v.v->parts[dpart].open ) {
transparency_cache[px][py] = LIGHT_TRANSPARENCY_SOLID;
} else {
vehicle_is_opaque = false;
}
}
if( vehicle_is_opaque || vp.is_inside() ) {
outside_cache[px][py] = false;
}
if( vp.has_feature( VPFLAG_BOARDABLE ) && !vp.part().is_broken() ) {
floor_cache[px][py] = true;
}
}
}
build_seen_cache( g->u.pos(), zlev );
if( !skip_lightmap ) {
generate_lightmap( zlev );
}
}
std::vector<point> closest_points_first( int radius, point p )
{
return closest_points_first( radius, p.x, p.y );
}
//this returns points in a spiral pattern starting at center_x/center_y until it hits the radius. clockwise fashion
//credit to Tom J Nowell; http://stackoverflow.com/a/1555236/1269969
std::vector<point> closest_points_first( int radius, int center_x, int center_y )
{
std::vector<point> points;
int X = ( radius * 2 ) + 1;
int Y = ( radius * 2 ) + 1;
int x = 0;
int y = 0;
int dx = 0;
int dy = -1;
int t = std::max( X, Y );
int maxI = t * t;
for( int i = 0; i < maxI; i++ ) {
if( ( -X / 2 <= x ) && ( x <= X / 2 ) && ( -Y / 2 <= y ) && ( y <= Y / 2 ) ) {
points.push_back( point( x + center_x, y + center_y ) );
}
if( ( x == y ) || ( ( x < 0 ) && ( x == -y ) ) || ( ( x > 0 ) && ( x == 1 - y ) ) ) {
t = dx;
dx = -dy;
dy = t;
}
x += dx;
y += dy;
}
return points;
}
std::vector<tripoint> closest_tripoints_first( int radius, const tripoint &center )
{
std::vector<tripoint> points;
int X = ( radius * 2 ) + 1;
int Y = ( radius * 2 ) + 1;
int x = 0;
int y = 0;
int dx = 0;
int dy = -1;
int t = std::max( X, Y );
int maxI = t * t;
for( int i = 0; i < maxI; i++ ) {
if( ( -X / 2 <= x ) && ( x <= X / 2 ) && ( -Y / 2 <= y ) && ( y <= Y / 2 ) ) {
points.push_back( tripoint( x + center.x, y + center.y, center.z ) );
}
if( ( x == y ) || ( ( x < 0 ) && ( x == -y ) ) || ( ( x > 0 ) && ( x == 1 - y ) ) ) {
t = dx;
dx = -dy;
dy = t;
}
x += dx;
y += dy;
}
return points;
}
//////////
///// coordinate helpers
point map::getabs( const int x, const int y ) const
{
return point( x + abs_sub.x * SEEX, y + abs_sub.y * SEEY );
}
tripoint map::getabs( const tripoint &p ) const
{
return tripoint( p.x + abs_sub.x * SEEX, p.y + abs_sub.y * SEEY, p.z );
}
point map::getlocal( const int x, const int y ) const
{
return point( x - abs_sub.x * SEEX, y - abs_sub.y * SEEY );
}
tripoint map::getlocal( const tripoint &p ) const
{
return tripoint( p.x - abs_sub.x * SEEX, p.y - abs_sub.y * SEEY, p.z );
}
void map::set_abs_sub( const int x, const int y, const int z )
{
abs_sub = tripoint( x, y, z );
}
tripoint map::get_abs_sub() const
{
return abs_sub;
}
submap *map::getsubmap( const size_t grididx ) const
{
if( grididx >= grid.size() ) {
debugmsg( "Tried to access invalid grid index %d. Grid size: %d", grididx, grid.size() );
return nullptr;
}
return grid[grididx];
}
void map::setsubmap( const size_t grididx, submap *const smap )
{
if( grididx >= grid.size() ) {
debugmsg( "Tried to access invalid grid index %d", grididx );
return;
} else if( smap == nullptr ) {
debugmsg( "Tried to set NULL submap pointer at index %d", grididx );
return;
}
grid[grididx] = smap;
}
submap *map::get_submap_at( const point &p ) const
{
if( !inbounds( p ) ) {
debugmsg( "Tried to access invalid map position (%d, %d, %d)", p.x, p.y, abs_sub.z );
return nullptr;
}
return get_submap_at_grid( { p.x / SEEX, p.y / SEEY, abs_sub.z } );
}
submap *map::get_submap_at( const tripoint &p ) const
{
if( !inbounds( p ) ) {
debugmsg( "Tried to access invalid map position (%d, %d, %d)", p.x, p.y, p.z );
return nullptr;
}
return get_submap_at_grid( { p.x / SEEX, p.y / SEEY, p.z } );
}
submap *map::get_submap_at( const point &p, point &offset_p ) const
{
return get_submap_at( { p.x, p.y, abs_sub.z }, offset_p );
}
submap *map::get_submap_at( const tripoint &p, point &offset_p ) const
{
offset_p.x = p.x % SEEX;
offset_p.y = p.y % SEEY;
return get_submap_at( p );
}
submap *map::get_submap_at_grid( const point &gridp ) const
{
return getsubmap( get_nonant( gridp ) );
}
submap *map::get_submap_at_grid( const tripoint &gridp ) const
{
return getsubmap( get_nonant( gridp ) );
}
size_t map::get_nonant( const point &gridp ) const
{
return get_nonant( { gridp.x, gridp.y, abs_sub.z } );
}
size_t map::get_nonant( const tripoint &gridp ) const
{
if( gridp.x < 0 || gridp.x >= my_MAPSIZE ||
gridp.y < 0 || gridp.y >= my_MAPSIZE ||
gridp.z < -OVERMAP_DEPTH || gridp.z > OVERMAP_HEIGHT ) {
debugmsg( "Tried to access invalid map position at grid (%d,%d,%d)", gridp.x, gridp.y, gridp.z );
return 0;
}
if( zlevels ) {
const int indexz = gridp.z + OVERMAP_HEIGHT; // Can't be lower than 0
return indexz + ( gridp.x + gridp.y * my_MAPSIZE ) * OVERMAP_LAYERS;
} else {
return gridp.x + gridp.y * my_MAPSIZE;
}
}
tinymap::tinymap( int mapsize, bool zlevels )
: map( mapsize, zlevels )
{
}
void map::draw_line_ter( const ter_id type, int x1, int y1, int x2, int y2 )
{
draw_line( [this, type]( int x, int y ) {
this->ter_set( x, y, type );
}, x1, y1, x2, y2 );
}
void map::draw_line_furn( const furn_id type, int x1, int y1, int x2, int y2 )
{
draw_line( [this, type]( int x, int y ) {
this->furn_set( x, y, type );
}, x1, y1, x2, y2 );
}
void map::draw_fill_background( const ter_id type )
{
// Need to explicitly set caches dirty - set_ter would do it before
set_transparency_cache_dirty( abs_sub.z );
set_outside_cache_dirty( abs_sub.z );
set_pathfinding_cache_dirty( abs_sub.z );
// Fill each submap rather than each tile
constexpr size_t block_size = SEEX * SEEY;
for( int gridx = 0; gridx < my_MAPSIZE; gridx++ ) {
for( int gridy = 0; gridy < my_MAPSIZE; gridy++ ) {
auto sm = get_submap_at_grid( {gridx, gridy} );
sm->is_uniform = true;
std::uninitialized_fill_n( &sm->ter[0][0], block_size, type );
}
}
}
void map::draw_fill_background( ter_id( *f )() )
{
draw_square_ter( f, 0, 0, SEEX * my_MAPSIZE - 1, SEEY * my_MAPSIZE - 1 );
}
void map::draw_fill_background( const weighted_int_list<ter_id> &f )
{
draw_square_ter( f, 0, 0, SEEX * my_MAPSIZE - 1, SEEY * my_MAPSIZE - 1 );
}
void map::draw_square_ter( const ter_id type, int x1, int y1, int x2, int y2 )
{
draw_square( [this, type]( int x, int y ) {
this->ter_set( x, y, type );
}, x1, y1, x2, y2 );
}
void map::draw_square_furn( const furn_id type, int x1, int y1, int x2, int y2 )
{
draw_square( [this, type]( int x, int y ) {
this->furn_set( x, y, type );
}, x1, y1, x2, y2 );
}
void map::draw_square_ter( ter_id( *f )(), int x1, int y1, int x2, int y2 )
{
draw_square( [this, f]( int x, int y ) {
this->ter_set( x, y, f() );
}, x1, y1, x2, y2 );
}
void map::draw_square_ter( const weighted_int_list<ter_id> &f, int x1, int y1, int x2, int y2 )
{
draw_square( [this, f]( int x, int y ) {
const ter_id *tid = f.pick();
this->ter_set( x, y, tid != nullptr ? *tid : t_null );
}, x1, y1, x2, y2 );
}
void map::draw_rough_circle_ter( const ter_id type, int x, int y, int rad )
{
draw_rough_circle( [this, type]( int x, int y ) {
this->ter_set( x, y, type );
}, x, y, rad );
}
void map::draw_rough_circle_furn( const furn_id type, int x, int y, int rad )
{
draw_rough_circle( [this, type]( int x, int y ) {
this->furn_set( x, y, type );
}, x, y, rad );
}
void map::draw_circle_ter( const ter_id type, double x, double y, double rad )
{
draw_circle( [this, type]( int x, int y ) {
this->ter_set( x, y, type );
}, x, y, rad );
}
void map::draw_circle_ter( const ter_id type, int x, int y, int rad )
{
draw_circle( [this, type]( int x, int y ) {
this->ter_set( x, y, type );
}, x, y, rad );
}
void map::draw_circle_furn( const furn_id type, int x, int y, int rad )
{
draw_circle( [this, type]( int x, int y ) {
this->furn_set( x, y, type );
}, x, y, rad );
}
void map::add_corpse( const tripoint &p )
{
item body;
const bool isReviveSpecial = one_in( 10 );
if( !isReviveSpecial ) {
body = item::make_corpse();
} else {
body = item::make_corpse( mon_zombie );
body.item_tags.insert( "REVIVE_SPECIAL" );
body.active = true;
}
add_item_or_charges( p, body );
put_items_from_loc( "shoes", p, 0 );
put_items_from_loc( "pants", p, 0 );
put_items_from_loc( "shirts", p, 0 );
if( one_in( 6 ) ) {
put_items_from_loc( "jackets", p, 0 );
}
if( one_in( 15 ) ) {
put_items_from_loc( "bags", p, 0 );
}
}
field &map::get_field( const tripoint &p )
{
return field_at( p );
}
void map::creature_on_trap( Creature &c, const bool may_avoid )
{
const auto &tr = tr_at( c.pos() );
if( tr.is_null() ) {
return;
}
// boarded in a vehicle means the player is above the trap, like a flying monster and can
// never trigger the trap.
const player *const p = dynamic_cast<const player *>( &c );
if( p != nullptr && p->in_vehicle ) {
return;
}
if( may_avoid && c.avoid_trap( c.pos(), tr ) ) {
return;
}
tr.trigger( c.pos(), &c );
}
template<typename Functor>
void map::function_over( const tripoint &start, const tripoint &end, Functor fun ) const
{
function_over( start.x, start.y, start.z, end.x, end.y, end.z, fun );
}
template<typename Functor>
void map::function_over( const int stx, const int sty, const int stz,
const int enx, const int eny, const int enz, Functor fun ) const
{
// start and end are just two points, end can be "before" start
// Also clip the area to map area
const int minx = std::max( std::min( stx, enx ), 0 );
const int miny = std::max( std::min( sty, eny ), 0 );
const int minz = std::max( std::min( stz, enz ), -OVERMAP_DEPTH );
const int maxx = std::min( std::max( stx, enx ), SEEX * my_MAPSIZE - 1 );
const int maxy = std::min( std::max( sty, eny ), SEEY * my_MAPSIZE - 1 );
const int maxz = std::min( std::max( stz, enz ), OVERMAP_HEIGHT );
// Submaps that contain the bounding points
const int min_smx = minx / SEEX;
const int min_smy = miny / SEEY;
const int max_smx = maxx / SEEX;
const int max_smy = maxy / SEEY;
// Z outermost, because submaps are flat
tripoint gp;
int &z = gp.z;
int &smx = gp.x;
int &smy = gp.y;
for( z = minz; z <= maxz; z++ ) {
for( smx = min_smx; smx <= max_smx; smx++ ) {
for( smy = min_smy; smy <= max_smy; smy++ ) {
submap const *cur_submap = get_submap_at_grid( { smx, smy, z } );
// Bounds on the submap coordinates
const int sm_minx = smx > min_smx ? 0 : minx % SEEX;
const int sm_miny = smy > min_smy ? 0 : miny % SEEY;
const int sm_maxx = smx < max_smx ? ( SEEX - 1 ) : maxx % SEEX;
const int sm_maxy = smy < max_smy ? ( SEEY - 1 ) : maxy % SEEY;
point lp;
int &sx = lp.x;
int &sy = lp.y;
for( sx = sm_minx; sx <= sm_maxx; ++sx ) {
for( sy = sm_miny; sy <= sm_maxy; ++sy ) {
const iteration_state rval = fun( gp, cur_submap, lp );
if( rval != ITER_CONTINUE ) {
switch( rval ) {
case ITER_SKIP_ZLEVEL:
smx = my_MAPSIZE + 1;
smy = my_MAPSIZE + 1;
// Fall through
case ITER_SKIP_SUBMAP:
sx = SEEX;
sy = SEEY;
break;
default:
return;
}
}
}
}
}
}
}
}
void map::scent_blockers( std::array<std::array<bool, MAPSIZE_X>, MAPSIZE_Y> &blocks_scent,
std::array<std::array<bool, MAPSIZE_X>, MAPSIZE_Y> &reduces_scent,
const int minx, const int miny, const int maxx, const int maxy )
{
auto reduce = TFLAG_REDUCE_SCENT;
auto block = TFLAG_WALL;
auto fill_values = [&]( const tripoint & gp, const submap * sm, const point & lp ) {
// We need to generate the x/y coordinates, because we can't get them "for free"
const int x = gp.x * SEEX + lp.x;
const int y = gp.y * SEEY + lp.y;
if( sm->get_ter( lp ).obj().has_flag( block ) ) {
blocks_scent[x][y] = true;
reduces_scent[x][y] = false;
} else if( sm->get_ter( lp ).obj().has_flag( reduce ) ||
sm->get_furn( lp ).obj().has_flag( reduce ) ) {
blocks_scent[x][y] = false;
reduces_scent[x][y] = true;
} else {
blocks_scent[x][y] = false;
reduces_scent[x][y] = false;
}
return ITER_CONTINUE;
};
function_over( minx, miny, abs_sub.z, maxx, maxy, abs_sub.z, fill_values );
// Now vehicles
// Currently the scentmap is limited to an area around the player rather than entire map
auto local_bounds = [ = ]( const tripoint & coord ) {
return coord.x >= minx && coord.x <= maxx && coord.y >= miny && coord.y <= maxy;
};
auto vehs = get_vehicles();
for( auto &wrapped_veh : vehs ) {
vehicle &veh = *( wrapped_veh.v );
for( const vpart_reference &vp : veh.get_any_parts( VPFLAG_OBSTACLE ) ) {
const tripoint part_pos = vp.pos();
if( local_bounds( part_pos ) ) {
reduces_scent[part_pos.x][part_pos.y] = true;
}
}
// Doors, but only the closed ones
for( const vpart_reference &vp : veh.get_any_parts( VPFLAG_OPENABLE ) ) {
if( vp.part().open ) {
continue;
}
const tripoint part_pos = vp.pos();
if( local_bounds( part_pos ) ) {
reduces_scent[part_pos.x][part_pos.y] = true;
}
}
}
}
tripoint_range map::points_in_rectangle( const tripoint &from, const tripoint &to ) const
{
const int minx = std::max( 0, std::min( from.x, to.x ) );
const int miny = std::max( 0, std::min( from.y, to.y ) );
const int minz = std::max( -OVERMAP_DEPTH, std::min( from.z, to.z ) );
const int maxx = std::min( SEEX * my_MAPSIZE - 1, std::max( from.x, to.x ) );
const int maxy = std::min( SEEX * my_MAPSIZE - 1, std::max( from.y, to.y ) );
const int maxz = std::min( OVERMAP_HEIGHT, std::max( from.z, to.z ) );
return tripoint_range( tripoint( minx, miny, minz ), tripoint( maxx, maxy, maxz ) );
}
tripoint_range map::points_in_radius( const tripoint &center, size_t radius, size_t radiusz ) const
{
const int minx = std::max<int>( 0, center.x - radius );
const int miny = std::max<int>( 0, center.y - radius );
const int minz = std::max<int>( -OVERMAP_DEPTH, center.z - radiusz );
const int maxx = std::min<int>( SEEX * my_MAPSIZE - 1, center.x + radius );
const int maxy = std::min<int>( SEEX * my_MAPSIZE - 1, center.y + radius );
const int maxz = std::min<int>( OVERMAP_HEIGHT, center.z + radiusz );
return tripoint_range( tripoint( minx, miny, minz ), tripoint( maxx, maxy, maxz ) );
}
std::list<item_location> map::get_active_items_in_radius( const tripoint &center, int radius ) const
{
std::list<item_location> result;
const point minp( center.x - radius, center.y - radius );
const point maxp( center.x + radius, center.y + radius );
const point ming( std::max( minp.x / SEEX, 0 ),
std::max( minp.y / SEEY, 0 ) );
const point maxg( std::min( maxp.x / SEEX, my_MAPSIZE - 1 ),
std::min( maxp.y / SEEY, my_MAPSIZE - 1 ) );
for( int gx = ming.x; gx <= maxg.x; ++gx ) {
for( int gy = ming.y; gy <= maxg.y; ++gy ) {
const point sm_offset( gx * SEEX, gy * SEEY );
for( const auto &elem : get_submap_at_grid( { gx, gy, center.z } )->active_items.get() ) {
const tripoint pos( sm_offset + elem.location, center.z );
if( rl_dist( pos, center ) > radius ) {
continue;
}
result.emplace_back( map_cursor( pos ), &*elem.item_iterator );
}
}
}
return result;
}
level_cache &map::access_cache( int zlev )
{
if( zlev >= -OVERMAP_DEPTH && zlev <= OVERMAP_HEIGHT ) {
return *caches[zlev + OVERMAP_DEPTH];
}
debugmsg( "access_cache called with invalid z-level: %d", zlev );
return nullcache;
}
const level_cache &map::access_cache( int zlev ) const
{
if( zlev >= -OVERMAP_DEPTH && zlev <= OVERMAP_HEIGHT ) {
return *caches[zlev + OVERMAP_DEPTH];
}
debugmsg( "access_cache called with invalid z-level: %d", zlev );
return nullcache;
}
level_cache::level_cache()
{
const int map_dimensions = MAPSIZE_X * MAPSIZE_Y;
transparency_cache_dirty = true;
outside_cache_dirty = true;
floor_cache_dirty = false;
constexpr four_quadrants four_zeros( 0.0f );
std::fill_n( &lm[0][0], map_dimensions, four_zeros );
std::fill_n( &sm[0][0], map_dimensions, 0.0f );
std::fill_n( &light_source_buffer[0][0], map_dimensions, 0.0f );
std::fill_n( &outside_cache[0][0], map_dimensions, false );
std::fill_n( &floor_cache[0][0], map_dimensions, false );
std::fill_n( &transparency_cache[0][0], map_dimensions, 0.0f );
std::fill_n( &seen_cache[0][0], map_dimensions, 0.0f );
std::fill_n( &camera_cache[0][0], map_dimensions, 0.0f );
std::fill_n( &visibility_cache[0][0], map_dimensions, LL_DARK );
veh_in_active_range = false;
std::fill_n( &veh_exists_at[0][0], map_dimensions, false );
}
pathfinding_cache::pathfinding_cache()
{
dirty = true;
}
pathfinding_cache::~pathfinding_cache() = default;
pathfinding_cache &map::get_pathfinding_cache( int zlev ) const
{
return *pathfinding_caches[zlev + OVERMAP_DEPTH];
}
void map::set_pathfinding_cache_dirty( const int zlev )
{
if( inbounds_z( zlev ) ) {
get_pathfinding_cache( zlev ).dirty = true;
}
}
const pathfinding_cache &map::get_pathfinding_cache_ref( int zlev ) const
{
if( !inbounds_z( zlev ) ) {
debugmsg( "Tried to get pathfinding cache for out of bounds z-level %d", zlev );
return *pathfinding_caches[ OVERMAP_DEPTH ];
}
auto &cache = get_pathfinding_cache( zlev );
if( cache.dirty ) {
update_pathfinding_cache( zlev );
}
return cache;
}
void map::update_pathfinding_cache( int zlev ) const
{
auto &cache = get_pathfinding_cache( zlev );
if( !cache.dirty ) {
return;
}
std::uninitialized_fill_n( &cache.special[0][0], MAPSIZE_X * MAPSIZE_Y, PF_NORMAL );
for( int smx = 0; smx < my_MAPSIZE; ++smx ) {
for( int smy = 0; smy < my_MAPSIZE; ++smy ) {
const auto cur_submap = get_submap_at_grid( { smx, smy, zlev } );
tripoint p( 0, 0, zlev );
for( int sx = 0; sx < SEEX; ++sx ) {
p.x = sx + smx * SEEX;
for( int sy = 0; sy < SEEY; ++sy ) {
p.y = sy + smy * SEEY;
pf_special cur_value = PF_NORMAL;
maptile tile( cur_submap, sx, sy );
const auto &terrain = tile.get_ter_t();
const auto &furniture = tile.get_furn_t();
int part;
const vehicle *veh = veh_at_internal( p, part );
const int cost = move_cost_internal( furniture, terrain, veh, part );
if( cost > 2 ) {
cur_value |= PF_SLOW;
} else if( cost <= 0 ) {
cur_value |= PF_WALL;
if( terrain.has_flag( TFLAG_CLIMBABLE ) ) {
cur_value |= PF_CLIMBABLE;
}
}
if( veh != nullptr ) {
cur_value |= PF_VEHICLE;
}
for( const auto &fld : tile.get_field() ) {
const field_entry &cur = fld.second;
const field_id type = cur.getFieldType();
const int density = cur.getFieldDensity();
if( fieldlist[type].dangerous[density - 1] ) {
cur_value |= PF_FIELD;
}
}
if( !tile.get_trap_t().is_benign() || !terrain.trap.obj().is_benign() ) {
cur_value |= PF_TRAP;
}
if( terrain.has_flag( TFLAG_GOES_DOWN ) || terrain.has_flag( TFLAG_GOES_UP ) ||
terrain.has_flag( TFLAG_RAMP ) ) {
cur_value |= PF_UPDOWN;
}
cache.special[p.x][p.y] = cur_value;
}
}
}
}
cache.dirty = false;
}
void map::clip_to_bounds( tripoint &p ) const
{
clip_to_bounds( p.x, p.y, p.z );
}
void map::clip_to_bounds( int &x, int &y ) const
{
if( x < 0 ) {
x = 0;
} else if( x >= SEEX * my_MAPSIZE ) {
x = SEEX * my_MAPSIZE - 1;
}
if( y < 0 ) {
y = 0;
} else if( y >= SEEY * my_MAPSIZE ) {
y = SEEY * my_MAPSIZE - 1;
}
}
void map::clip_to_bounds( int &x, int &y, int &z ) const
{
clip_to_bounds( x, y );
if( z < -OVERMAP_DEPTH ) {
z = -OVERMAP_DEPTH;
} else if( z > OVERMAP_HEIGHT ) {
z = OVERMAP_HEIGHT;
}
}
You can’t perform that action at this time.