1 #include <algorithm>
2 #include <array>
3 #include <bitset>
4 #include <cmath>
5 #include <cstddef>
6 #include <functional>
7 #include <iosfwd>
8 #include <list>
9 #include <map>
10 #include <memory>
11 #include <new>
12 #include <queue>
13 #include <set>
14 #include <string>
15 #include <tuple>
16 #include <utility>
17 #include <vector>
18
19 #include "bodypart.h"
20 #include "calendar.h"
21 #include "cata_utility.h"
22 #include "character.h"
23 #include "colony.h"
24 #include "coordinate_conversions.h"
25 #include "coordinates.h"
26 #include "creature.h"
27 #include "damage.h"
28 #include "debug.h"
29 #include "effect.h"
30 #include "emit.h"
31 #include "enums.h"
32 #include "field.h"
33 #include "field_type.h"
34 #include "fire.h"
35 #include "fungal_effects.h"
36 #include "game.h"
37 #include "game_constants.h"
38 #include "item.h"
39 #include "item_contents.h"
40 #include "itype.h"
41 #include "level_cache.h"
42 #include "line.h"
43 #include "make_static.h"
44 #include "map.h"
45 #include "map_field.h"
46 #include "map_iterator.h"
47 #include "mapdata.h"
48 #include "material.h"
49 #include "messages.h"
50 #include "mongroup.h"
51 #include "monster.h"
52 #include "mtype.h"
53 #include "npc.h"
54 #include "optional.h"
55 #include "overmapbuffer.h"
56 #include "player.h"
57 #include "point.h"
58 #include "rng.h"
59 #include "scent_block.h"
60 #include "scent_map.h"
61 #include "submap.h"
62 #include "teleport.h"
63 #include "translations.h"
64 #include "type_id.h"
65 #include "units.h"
66 #include "vehicle.h"
67 #include "vpart_position.h"
68 #include "weather.h"
69
70 static const itype_id itype_rm13_armor_on( "rm13_armor_on" );
71 static const itype_id itype_rock( "rock" );
72
73 static const species_id species_FUNGUS( "FUNGUS" );
74
75 static const bionic_id bio_heatsink( "bio_heatsink" );
76
77 static const efftype_id effect_badpoison( "badpoison" );
78 static const efftype_id effect_blind( "blind" );
79 static const efftype_id effect_corroding( "corroding" );
80 static const efftype_id effect_fungus( "fungus" );
81 static const efftype_id effect_onfire( "onfire" );
82 static const efftype_id effect_poison( "poison" );
83 static const efftype_id effect_stung( "stung" );
84 static const efftype_id effect_stunned( "stunned" );
85 static const efftype_id effect_teargas( "teargas" );
86 static const efftype_id effect_webbed( "webbed" );
87
88 static const trait_id trait_ACIDPROOF( "ACIDPROOF" );
89 static const trait_id trait_ELECTRORECEPTORS( "ELECTRORECEPTORS" );
90 static const trait_id trait_M_IMMUNE( "M_IMMUNE" );
91 static const trait_id trait_M_SKIN2( "M_SKIN2" );
92 static const trait_id trait_M_SKIN3( "M_SKIN3" );
93 static const trait_id trait_THRESH_MARLOSS( "THRESH_MARLOSS" );
94 static const trait_id trait_THRESH_MYCUS( "THRESH_MYCUS" );
95
96 using namespace map_field_processing;
97
create_burnproducts(const tripoint & p,const item & fuel,const units::mass & burned_mass)98 void map::create_burnproducts( const tripoint &p, const item &fuel, const units::mass &burned_mass )
99 {
100 std::vector<material_id> all_mats = fuel.made_of();
101 if( all_mats.empty() ) {
102 return;
103 }
104 // Items that are multiple materials are assumed to be equal parts each.
105 const units::mass by_weight = burned_mass / all_mats.size();
106 for( material_id &mat : all_mats ) {
107 for( const auto &bp : mat->burn_products() ) {
108 itype_id id = bp.first;
109 // Spawning the same item as the one that was just burned is pointless
110 // and leads to infinite recursion.
111 if( fuel.typeId() == id ) {
112 continue;
113 }
114 const float eff = bp.second;
115 const int n = std::floor( eff * ( by_weight / item::find_type( id )->weight ) );
116
117 if( n <= 0 ) {
118 continue;
119 }
120 spawn_item( p, id, n, 1, calendar::turn );
121 }
122 }
123 }
124
125 // Use a helper for a bit less boilerplate
burn_body_part(player & u,field_entry & cur,const bodypart_id & bp,const int scale)126 int map::burn_body_part( player &u, field_entry &cur, const bodypart_id &bp, const int scale )
127 {
128 int total_damage = 0;
129 const int intensity = cur.get_field_intensity();
130 const int damage = rng( 1, ( scale + intensity ) / 2 );
131 // A bit ugly, but better than being annoyed by acid when in hazmat
132 if( u.get_armor_type( damage_type::ACID, bp ) < damage ) {
133 const dealt_damage_instance ddi = u.deal_damage( nullptr, bp, damage_instance( damage_type::ACID,
134 damage ) );
135 total_damage += ddi.total_damage();
136 }
137 // Represents acid seeping in rather than being splashed on
138 u.add_env_effect( effect_corroding, bp, 2 + intensity, time_duration::from_turns( rng( 2,
139 1 + intensity ) ), bp, false, 0 );
140 return total_damage;
141 }
142
process_fields()143 void map::process_fields()
144 {
145 const int minz = zlevels ? -OVERMAP_DEPTH : abs_sub.z;
146 const int maxz = zlevels ? OVERMAP_HEIGHT : abs_sub.z;
147 for( int z = minz; z <= maxz; z++ ) {
148 auto &field_cache = get_cache( z ).field_cache;
149 for( int x = 0; x < my_MAPSIZE; x++ ) {
150 for( int y = 0; y < my_MAPSIZE; y++ ) {
151 if( field_cache[ x + y * MAPSIZE ] ) {
152 submap *const current_submap = get_submap_at_grid( { x, y, z } );
153 if( current_submap == nullptr ) {
154 debugmsg( "Tried to process field at (%d,%d,%d) but the submap is not loaded", x, y, z );
155 continue;
156 }
157 process_fields_in_submap( current_submap, tripoint( x, y, z ) );
158 if( current_submap->field_count == 0 ) {
159 field_cache[ x + y * MAPSIZE ] = false;
160 }
161 }
162 }
163 }
164 }
165 }
166
ter_furn_has_flag(const ter_t & ter,const furn_t & furn,const ter_bitflags flag)167 bool ter_furn_has_flag( const ter_t &ter, const furn_t &furn, const ter_bitflags flag )
168 {
169 return ter.has_flag( flag ) || furn.has_flag( flag );
170 }
171
ter_furn_movecost(const ter_t & ter,const furn_t & furn)172 static int ter_furn_movecost( const ter_t &ter, const furn_t &furn )
173 {
174 if( ter.movecost == 0 ) {
175 return 0;
176 }
177
178 if( furn.movecost < 0 ) {
179 return 0;
180 }
181
182 return ter.movecost + furn.movecost;
183 }
184
185 // Wrapper to allow skipping bound checks except at the edges of the map
maptile_has_bounds(const tripoint & p,const bool bounds_checked)186 std::pair<tripoint, maptile> map::maptile_has_bounds( const tripoint &p, const bool bounds_checked )
187 {
188 if( bounds_checked ) {
189 // We know that the point is in bounds
190 return {p, maptile_at_internal( p )};
191 }
192
193 return {p, maptile_at( p )};
194 }
195
get_neighbors(const tripoint & p)196 std::array<std::pair<tripoint, maptile>, 8> map::get_neighbors( const tripoint &p )
197 {
198 // Find out which edges are in the bubble
199 // Where possible, do just one bounds check for all the neighbors
200 const bool west = p.x > 0;
201 const bool north = p.y > 0;
202 const bool east = p.x < SEEX * my_MAPSIZE - 1;
203 const bool south = p.y < SEEY * my_MAPSIZE - 1;
204 return std::array< std::pair<tripoint, maptile>, 8 > { {
205 maptile_has_bounds( p + eight_horizontal_neighbors[0], west &&north ),
206 maptile_has_bounds( p + eight_horizontal_neighbors[1], north ),
207 maptile_has_bounds( p + eight_horizontal_neighbors[2], east &&north ),
208 maptile_has_bounds( p + eight_horizontal_neighbors[3], west ),
209 maptile_has_bounds( p + eight_horizontal_neighbors[4], east ),
210 maptile_has_bounds( p + eight_horizontal_neighbors[5], west &&south ),
211 maptile_has_bounds( p + eight_horizontal_neighbors[6], south ),
212 maptile_has_bounds( p + eight_horizontal_neighbors[7], east &&south ),
213 }
214 };
215 }
216
gas_can_spread_to(field_entry & cur,const maptile & dst)217 bool map::gas_can_spread_to( field_entry &cur, const maptile &dst )
218 {
219 const field_entry *tmpfld = dst.get_field().find_field( cur.get_field_type() );
220 // Candidates are existing weaker fields or navigable/flagged tiles with no field.
221 if( tmpfld == nullptr || tmpfld->get_field_intensity() < cur.get_field_intensity() ) {
222 const ter_t &ter = dst.get_ter_t();
223 const furn_t &frn = dst.get_furn_t();
224 return ter_furn_movecost( ter, frn ) > 0 || ter_furn_has_flag( ter, frn, TFLAG_PERMEABLE );
225 }
226 return false;
227 }
228
gas_spread_to(field_entry & cur,maptile & dst,const tripoint & p)229 void map::gas_spread_to( field_entry &cur, maptile &dst, const tripoint &p )
230 {
231 const field_type_id current_type = cur.get_field_type();
232 const time_duration current_age = cur.get_field_age();
233 const int current_intensity = cur.get_field_intensity();
234 field_entry *f = dst.find_field( current_type );
235 // Nearby gas grows thicker, and ages are shared.
236 const time_duration age_fraction = current_age / current_intensity;
237 if( f != nullptr ) {
238 f->set_field_intensity( f->get_field_intensity() + 1 );
239 cur.set_field_intensity( current_intensity - 1 );
240 f->set_field_age( f->get_field_age() + age_fraction );
241 cur.set_field_age( current_age - age_fraction );
242 // Or, just create a new field.
243 } else if( add_field( p, current_type, 1, 0_turns ) ) {
244 f = dst.find_field( current_type );
245 if( f != nullptr ) {
246 f->set_field_age( age_fraction );
247 } else {
248 debugmsg( "While spreading the gas, field was added but doesn't exist." );
249 }
250 cur.set_field_intensity( current_intensity - 1 );
251 cur.set_field_age( current_age - age_fraction );
252 }
253 }
254
spread_gas(field_entry & cur,const tripoint & p,int percent_spread,const time_duration & outdoor_age_speedup,scent_block & sblk,const oter_id & om_ter)255 void map::spread_gas( field_entry &cur, const tripoint &p, int percent_spread,
256 const time_duration &outdoor_age_speedup, scent_block &sblk, const oter_id &om_ter )
257 {
258 // TODO: fix point types
259 const bool sheltered = g->is_sheltered( p );
260 const int winddirection = g->weather.winddirection;
261 const int windpower = get_local_windpower( g->weather.windspeed, om_ter, p, winddirection,
262 sheltered );
263
264 const int current_intensity = cur.get_field_intensity();
265 const field_type_id ft_id = cur.get_field_type();
266
267 const int scent_neutralize = ft_id->get_intensity_level( current_intensity -
268 1 ).scent_neutralization;
269
270 if( scent_neutralize > 0 ) {
271 // modify scents by neutralization value (minus)
272 for( const tripoint &tmp : points_in_radius( p, 1 ) ) {
273 sblk.apply_gas( tmp, scent_neutralize );
274 }
275 }
276
277 // Dissipate faster outdoors.
278 if( is_outside( p ) ) {
279 const time_duration current_age = cur.get_field_age();
280 cur.set_field_age( current_age + outdoor_age_speedup );
281 }
282
283 // Bail out if we don't meet the spread chance or required intensity.
284 if( current_intensity <= 1 || rng( 1, 100 - windpower ) > percent_spread ) {
285 return;
286 }
287
288 // First check if we can fall
289 // TODO: Make fall and rise chances parameters to enable heavy/light gas
290 if( zlevels && p.z > -OVERMAP_DEPTH ) {
291 const tripoint down{ p.xy(), p.z - 1 };
292 maptile down_tile = maptile_at_internal( down );
293 if( gas_can_spread_to( cur, down_tile ) && valid_move( p, down, true, true ) ) {
294 gas_spread_to( cur, down_tile, down );
295 return;
296 }
297 }
298
299 auto neighs = get_neighbors( p );
300 size_t end_it = static_cast<size_t>( rng( 0, neighs.size() - 1 ) );
301 std::vector<size_t> spread;
302 std::vector<size_t> neighbour_vec;
303 // Then, spread to a nearby point.
304 // If not possible (or randomly), try to spread up
305 // Wind direction will block the field spreading into the wind.
306 // Start at end_it + 1, then wrap around until all elements have been processed.
307 for( size_t i = ( end_it + 1 ) % neighs.size(), count = 0;
308 count != neighs.size();
309 i = ( i + 1 ) % neighs.size(), count++ ) {
310 const auto &neigh = neighs[i];
311 if( gas_can_spread_to( cur, neigh.second ) ) {
312 spread.push_back( i );
313 }
314 }
315 auto maptiles = get_wind_blockers( winddirection, p );
316 // Three map tiles that are facing the wind direction.
317 const maptile remove_tile = std::get<0>( maptiles );
318 const maptile remove_tile2 = std::get<1>( maptiles );
319 const maptile remove_tile3 = std::get<2>( maptiles );
320 if( !spread.empty() && ( !zlevels || one_in( spread.size() ) ) ) {
321 // Construct the destination from offset and p
322 if( sheltered || windpower < 5 ) {
323 std::pair<tripoint, maptile> &n = neighs[ random_entry( spread ) ];
324 gas_spread_to( cur, n.second, n.first );
325 } else {
326 end_it = static_cast<size_t>( rng( 0, neighs.size() - 1 ) );
327 // Start at end_it + 1, then wrap around until all elements have been processed.
328 for( size_t i = ( end_it + 1 ) % neighs.size(), count = 0;
329 count != neighs.size();
330 i = ( i + 1 ) % neighs.size(), count++ ) {
331 const auto &neigh = neighs[i].second;
332 if( ( neigh.pos_.x != remove_tile.pos_.x && neigh.pos_.y != remove_tile.pos_.y ) ||
333 ( neigh.pos_.x != remove_tile2.pos_.x && neigh.pos_.y != remove_tile2.pos_.y ) ||
334 ( neigh.pos_.x != remove_tile3.pos_.x && neigh.pos_.y != remove_tile3.pos_.y ) ) {
335 neighbour_vec.push_back( i );
336 } else if( x_in_y( 1, std::max( 2, windpower ) ) ) {
337 neighbour_vec.push_back( i );
338 }
339 }
340 if( !neighbour_vec.empty() ) {
341 std::pair<tripoint, maptile> &n = neighs[neighbour_vec[rng( 0, neighbour_vec.size() - 1 )]];
342 gas_spread_to( cur, n.second, n.first );
343 }
344 }
345 } else if( zlevels && p.z < OVERMAP_HEIGHT ) {
346 const tripoint up{ p.xy(), p.z + 1 };
347 maptile up_tile = maptile_at_internal( up );
348 if( gas_can_spread_to( cur, up_tile ) && valid_move( p, up, true, true ) ) {
349 gas_spread_to( cur, up_tile, up );
350 }
351 }
352 }
353
354 /*
355 Helper function that encapsulates the logic involved in creating hot air.
356 */
create_hot_air(const tripoint & p,int intensity)357 void map::create_hot_air( const tripoint &p, int intensity )
358 {
359 field_type_id hot_air;
360 switch( intensity ) {
361 case 1:
362 hot_air = fd_hot_air1;
363 break;
364 case 2:
365 hot_air = fd_hot_air2;
366 break;
367 case 3:
368 hot_air = fd_hot_air3;
369 break;
370 case 4:
371 hot_air = fd_hot_air4;
372 break;
373 default:
374 debugmsg( "Tried to spread hot air with intensity %d", intensity );
375 return;
376 }
377
378 for( int counter = 0; counter < 5; counter++ ) {
379 tripoint dst( p + point( rng( -1, 1 ), rng( -1, 1 ) ) );
380 add_field( dst, hot_air, 1 );
381 }
382 }
383
384 /**
385 * Common field processing data: shared per submap and field_type.
386 * Note: maptile here is mutable and is modified for each point in the submap scanning loop.
387 */
388 struct field_proc_data {
389 scent_block &sblk;
390 Character &player_character;
391 const oter_id &om_ter;
392 map &here;
393 maptile &map_tile;
394 field_type_id cur_fd_type_id;
395 field_type const *cur_fd_type;
396 };
397
398 /*
399 Function: process_fields_in_submap
400 Iterates over every field on every tile of the given submap given as parameter.
401 This is the general update function for field effects. This should only be called once per game turn.
402 If you need to insert a new field behavior per unit time add a case statement in the switch below.
403 */
process_fields_in_submap(submap * const current_submap,const tripoint & submap)404 void map::process_fields_in_submap( submap *const current_submap,
405 const tripoint &submap )
406 {
407 const oter_id &om_ter = overmap_buffer.ter( tripoint_abs_omt( sm_to_omt_copy( submap ) ) );
408 Character &player_character = get_player_character();
409 scent_block sblk( submap, get_scent() );
410
411 // Initialize the map tile wrapper
412 maptile map_tile( current_submap, point_zero );
413 int &locx = map_tile.pos_.x;
414 int &locy = map_tile.pos_.y;
415 const point sm_offset = sm_to_ms_copy( submap.xy() );
416
417 field_proc_data pd{
418 sblk,
419 player_character,
420 om_ter,
421 *this,
422 map_tile,
423 fd_null,
424 &( *fd_null )
425 };
426
427 // Loop through all tiles in this submap indicated by current_submap
428 for( locx = 0; locx < SEEX; locx++ ) {
429 for( locy = 0; locy < SEEY; locy++ ) {
430 // Get a reference to the field variable from the submap;
431 // contains all the pointers to the real field effects.
432 field &curfield = current_submap->get_field( {static_cast<int>( locx ), static_cast<int>( locy )} );
433
434 // when displayed_field_type == fd_null it means that `curfield` has no fields inside
435 // avoids instantiating (relatively) expensive map iterator
436 if( !curfield.displayed_field_type() ) {
437 continue;
438 }
439
440 // This is a translation from local coordinates to submap coordinates.
441 const tripoint p = tripoint( map_tile.pos() + sm_offset, submap.z );
442
443 for( auto it = curfield.begin(); it != curfield.end(); ) {
444 // Iterating through all field effects in the submap's field.
445 field_entry &cur = it->second;
446 const int prev_intensity = cur.is_field_alive() ? cur.get_field_intensity() : 0;
447
448 pd.cur_fd_type_id = cur.get_field_type();
449 pd.cur_fd_type = &( *pd.cur_fd_type_id );
450
451 // The field might have been killed by processing a neighbor field
452 if( prev_intensity == 0 ) {
453 on_field_modified( p, *pd.cur_fd_type );
454 --current_submap->field_count;
455 curfield.remove_field( it++ );
456 continue;
457 }
458
459 // Don't process "newborn" fields. This gives the player time to run if they need to.
460 if( cur.get_field_age() == 0_turns ) {
461 cur.do_decay();
462 if( !cur.is_field_alive() || cur.get_field_intensity() != prev_intensity ) {
463 on_field_modified( p, *pd.cur_fd_type );
464 }
465 it++;
466 continue;
467 }
468
469 for( const FieldProcessorPtr &proc : pd.cur_fd_type->get_processors() ) {
470 proc( p, cur, pd );
471 }
472
473 cur.do_decay();
474 if( !cur.is_field_alive() || cur.get_field_intensity() != prev_intensity ) {
475 on_field_modified( p, *pd.cur_fd_type );
476 }
477 it++;
478 }
479 }
480 }
481 sblk.commit_modifications();
482 }
483
field_processor_upgrade_intensity(const tripoint &,field_entry & cur,field_proc_data &)484 static void field_processor_upgrade_intensity( const tripoint &, field_entry &cur,
485 field_proc_data & )
486 {
487 // Upgrade field intensity
488 const field_intensity_level &int_level = cur.get_intensity_level();
489 if( int_level.intensity_upgrade_chance > 0 &&
490 int_level.intensity_upgrade_duration > 0_turns &&
491 calendar::once_every( int_level.intensity_upgrade_duration ) &&
492 one_in( int_level.intensity_upgrade_chance ) ) {
493 cur.mod_field_intensity( 1 );
494 }
495 }
496
field_processor_underwater_dissipation(const tripoint &,field_entry & cur,field_proc_data & pd)497 static void field_processor_underwater_dissipation( const tripoint &, field_entry &cur,
498 field_proc_data &pd )
499 {
500 // Dissipate faster in water
501 if( pd.map_tile.get_ter_t().has_flag( TFLAG_SWIMMABLE ) ) {
502 cur.mod_field_age( pd.cur_fd_type->underwater_age_speedup );
503 }
504 }
505
field_processor_fd_acid(const tripoint & p,field_entry & cur,field_proc_data & pd)506 static void field_processor_fd_acid( const tripoint &p, field_entry &cur, field_proc_data &pd )
507 {
508 //cur_fd_type_id == fd_acid
509 if( !pd.here.has_zlevels() || p.z <= -OVERMAP_DEPTH ) {
510 return;
511 }
512
513 // Try to fall by a z-level
514 tripoint dst{ p.xy(), p.z - 1 };
515 if( pd.here.valid_move( p, dst, true, true ) ) {
516 field_entry *acid_there = pd.here.get_field( dst, fd_acid );
517 if( !acid_there ) {
518 pd.here.add_field( dst, fd_acid, cur.get_field_intensity(), cur.get_field_age() );
519 } else {
520 // Math can be a bit off,
521 // but "boiling" falling acid can be allowed to be stronger
522 // than acid that just lies there
523 const int sum_intensity = cur.get_field_intensity() + acid_there->get_field_intensity();
524 const int new_intensity = std::min( 3, sum_intensity );
525 // No way to get precise elapsed time, let's always reset
526 // Allow falling acid to last longer than regular acid to show it off
527 const time_duration new_age = -1_minutes * ( sum_intensity - new_intensity );
528 acid_there->set_field_intensity( new_intensity );
529 acid_there->set_field_age( new_age );
530 }
531
532 // Set ourselves up for removal
533 cur.set_field_intensity( 0 );
534 }
535
536 // TODO: Allow spreading to the sides if age < 0 && intensity == 3
537 }
538
field_processor_fd_extinguisher(const tripoint & p,field_entry & cur,field_proc_data & pd)539 static void field_processor_fd_extinguisher( const tripoint &p, field_entry &cur,
540 field_proc_data &pd )
541 {
542 // if( cur_fd_type_id == fd_extinguisher )
543 if( field_entry *fire_here = pd.map_tile.find_field( fd_fire ) ) {
544 // extinguisher fights fire in 1:1 ratio
545 const int min_int = std::min( fire_here->get_field_intensity(), cur.get_field_intensity() );
546 pd.here.mod_field_intensity( p, fd_fire, -min_int );
547 cur.mod_field_intensity( -min_int );
548 }
549 }
550
field_processor_apply_slime(const tripoint & p,field_entry & cur,field_proc_data & pd)551 static void field_processor_apply_slime( const tripoint &p, field_entry &cur, field_proc_data &pd )
552 {
553 // if( cur_fd_type.apply_slime_factor > 0 )
554 pd.sblk.apply_slime( p, cur.get_field_intensity() * pd.cur_fd_type->apply_slime_factor );
555 }
556
557 // Spread gaseous fields
field_processor_spread_gas(const tripoint & p,field_entry & cur,field_proc_data & pd)558 void field_processor_spread_gas( const tripoint &p, field_entry &cur, field_proc_data &pd )
559 {
560 // if( cur.gas_can_spread() )
561 pd.here.spread_gas( cur, p, pd.cur_fd_type->percent_spread, pd.cur_fd_type->outdoor_age_speedup,
562 pd.sblk, pd.om_ter );
563 }
564
field_processor_fd_fungal_haze(const tripoint & p,field_entry & cur,field_proc_data & pd)565 static void field_processor_fd_fungal_haze( const tripoint &p, field_entry &cur,
566 field_proc_data &pd )
567 {
568 // if( cur_fd_type_id == fd_fungal_haze ) {
569 if( one_in( 10 - 2 * cur.get_field_intensity() ) ) {
570 // Haze'd terrain
571 fungal_effects( *g, pd.here ).spread_fungus( p );
572 }
573 }
574
575 // Process npc complaints moved to player_in_field
576
field_processor_extra_radiation(const tripoint & p,field_entry & cur,field_proc_data & pd)577 static void field_processor_extra_radiation( const tripoint &p, field_entry &cur,
578 field_proc_data &pd )
579 {
580 // Apply radiation
581 const field_intensity_level &ilevel = cur.get_intensity_level();
582 if( ilevel.extra_radiation_max > 0 ) {
583 int extra_radiation = rng( ilevel.extra_radiation_min, ilevel.extra_radiation_max );
584 pd.here.adjust_radiation( p, extra_radiation );
585 }
586 }
587
field_processor_wandering_field(const tripoint & p,field_entry & cur,field_proc_data & pd)588 void field_processor_wandering_field( const tripoint &p, field_entry &cur, field_proc_data &pd )
589 {
590 // Apply wandering fields from vents
591 const field_type_id wandering_field_type_id = pd.cur_fd_type->wandering_field;
592 for( const tripoint &pnt : points_in_radius( p, cur.get_field_intensity() - 1 ) ) {
593 field &wandering_field = pd.here.get_field( pnt );
594 field_entry *tmpfld = wandering_field.find_field( wandering_field_type_id );
595 if( tmpfld && tmpfld->get_field_intensity() < cur.get_field_intensity() ) {
596 pd.here.mod_field_intensity( pnt, wandering_field_type_id, 1 );
597 } else {
598 pd.here.add_field( pnt, wandering_field_type_id, cur.get_field_intensity() );
599 }
600 }
601 }
602
field_processor_fd_fire_vent(const tripoint & p,field_entry & cur,field_proc_data & pd)603 void field_processor_fd_fire_vent( const tripoint &p, field_entry &cur, field_proc_data &pd )
604 {
605 if( cur.get_field_intensity() > 1 ) {
606 if( one_in( 3 ) ) {
607 cur.set_field_intensity( cur.get_field_intensity() - 1 );
608 }
609 pd.here.create_hot_air( p, cur.get_field_intensity() );
610 } else {
611 pd.here.add_field( p, fd_flame_burst, 3, cur.get_field_age() );
612 cur.set_field_intensity( 0 );
613 }
614 }
615
616 //TODO extract common logic from this and field_processor_fd_fire_vent
field_processor_fd_flame_burst(const tripoint & p,field_entry & cur,field_proc_data & pd)617 void field_processor_fd_flame_burst( const tripoint &p, field_entry &cur, field_proc_data &pd )
618 {
619 if( cur.get_field_intensity() > 1 ) {
620 cur.set_field_intensity( cur.get_field_intensity() - 1 );
621 pd.here.create_hot_air( p, cur.get_field_intensity() );
622 } else {
623 pd.here.add_field( p, fd_fire_vent, 3, cur.get_field_age() );
624 cur.set_field_intensity( 0 );
625 }
626 }
627
field_processor_fd_electricity(const tripoint & p,field_entry & cur,field_proc_data & pd)628 static void field_processor_fd_electricity( const tripoint &p, field_entry &cur,
629 field_proc_data &pd )
630 {
631 // 4 in 5 chance to spread
632 if( !one_in( 5 ) ) {
633 std::vector<tripoint> valid;
634 // We're grounded
635 if( pd.here.impassable( p ) && cur.get_field_intensity() > 1 ) {
636 int tries = 0;
637 tripoint pnt;
638 pnt.z = p.z;
639 while( tries < 10 && cur.get_field_age() < 5_minutes && cur.get_field_intensity() > 1 ) {
640 pnt.x = p.x + rng( -1, 1 );
641 pnt.y = p.y + rng( -1, 1 );
642 if( pd.here.passable( pnt ) ) {
643 pd.here.add_field( pnt, fd_electricity, 1, cur.get_field_age() + 1_turns );
644 cur.set_field_intensity( cur.get_field_intensity() - 1 );
645 tries = 0;
646 } else {
647 tries++;
648 }
649 }
650 // We're not grounded; attempt to ground
651 } else {
652 for( const tripoint &dst : points_in_radius( p, 1 ) ) {
653 // Grounded tiles first
654 if( pd.here.impassable( dst ) ) {
655 valid.push_back( dst );
656 }
657 }
658 // Spread to adjacent space, then
659 if( valid.empty() ) {
660 tripoint dst( p + point( rng( -1, 1 ), rng( -1, 1 ) ) );
661 field_entry *elec = pd.here.get_field( dst, fd_electricity );
662 if( pd.here.passable( dst ) && elec != nullptr &&
663 elec->get_field_intensity() < 3 ) {
664 pd.here.mod_field_intensity( dst, fd_electricity, 1 );
665 cur.set_field_intensity( cur.get_field_intensity() - 1 );
666 } else if( pd.here.passable( dst ) ) {
667 pd.here.add_field( dst, fd_electricity, 1, cur.get_field_age() + 1_turns );
668 }
669 cur.set_field_intensity( cur.get_field_intensity() - 1 );
670 }
671 while( !valid.empty() && cur.get_field_intensity() > 1 ) {
672 const tripoint target = random_entry_removed( valid );
673 pd.here.add_field( target, fd_electricity, 1, cur.get_field_age() + 1_turns );
674 cur.set_field_intensity( cur.get_field_intensity() - 1 );
675 }
676 }
677 }
678 }
679
field_processor_monster_spawn(const tripoint & p,field_entry & cur,field_proc_data & pd)680 static void field_processor_monster_spawn( const tripoint &p, field_entry &cur,
681 field_proc_data &pd )
682 {
683 const field_intensity_level &int_level = cur.get_intensity_level();
684 int monster_spawn_chance = int_level.monster_spawn_chance;
685 int monster_spawn_count = int_level.monster_spawn_count;
686 if( monster_spawn_count > 0 && monster_spawn_chance > 0 && one_in( monster_spawn_chance ) ) {
687 for( ; monster_spawn_count > 0; monster_spawn_count-- ) {
688 MonsterGroupResult spawn_details = MonsterGroupManager::GetResultFromGroup(
689 int_level.monster_spawn_group, &monster_spawn_count );
690 if( !spawn_details.name ) {
691 continue;
692 }
693 if( const cata::optional<tripoint> spawn_point = random_point(
694 points_in_radius( p, int_level.monster_spawn_radius ),
695 [&pd]( const tripoint & n ) {
696 return pd.here.passable( n );
697 } ) ) {
698 pd.here.add_spawn( spawn_details, *spawn_point );
699 }
700 }
701 }
702 }
703
field_processor_fd_push_items(const tripoint & p,field_entry &,field_proc_data & pd)704 static void field_processor_fd_push_items( const tripoint &p, field_entry &, field_proc_data &pd )
705 {
706 map_stack items = pd.here.i_at( p );
707 for( auto pushee = items.begin(); pushee != items.end(); ) {
708 if( pushee->typeId() != itype_rock ||
709 pushee->age() < 1_turns ) {
710 pushee++;
711 } else {
712 std::vector<tripoint> valid;
713 for( const tripoint &dst : points_in_radius( p, 1 ) ) {
714 if( dst != p && pd.here.get_field( dst, fd_push_items ) ) {
715 valid.push_back( dst );
716 }
717 }
718 if( !valid.empty() ) {
719 item tmp = *pushee;
720 tmp.set_age( 0_turns );
721 pushee = items.erase( pushee );
722 tripoint newp = random_entry( valid );
723 pd.here.add_item_or_charges( newp, tmp );
724 if( pd.player_character.pos() == newp ) {
725 add_msg( m_bad, _( "A %s hits you!" ), tmp.tname() );
726 const bodypart_id hit = pd.player_character.get_random_body_part();
727 pd.player_character.deal_damage( nullptr, hit, damage_instance( damage_type::BASH, 6 ) );
728 pd.player_character.check_dead_state();
729 }
730
731 if( npc *const n = g->critter_at<npc>( newp ) ) {
732 // TODO: combine with player character code above
733 const bodypart_id hit = pd.player_character.get_random_body_part();
734 n->deal_damage( nullptr, hit, damage_instance( damage_type::BASH, 6 ) );
735 add_msg_if_player_sees( newp, _( "A %1$s hits %2$s!" ), tmp.tname(), n->name );
736 n->check_dead_state();
737 } else if( monster *const mon = g->critter_at<monster>( newp ) ) {
738 mon->apply_damage( nullptr, bodypart_id( "torso" ),
739 6 - mon->get_armor_bash( bodypart_id( "torso" ) ) );
740 add_msg_if_player_sees( newp, _( "A %1$s hits the %2$s!" ), tmp.tname(), mon->name() );
741 mon->check_dead_state();
742 }
743 } else {
744 pushee++;
745 }
746 }
747 }
748 }
749
field_processor_fd_shock_vent(const tripoint & p,field_entry & cur,field_proc_data & pd)750 static void field_processor_fd_shock_vent( const tripoint &p, field_entry &cur,
751 field_proc_data &pd )
752 {
753 if( cur.get_field_intensity() > 1 ) {
754 if( one_in( 5 ) ) {
755 cur.set_field_intensity( cur.get_field_intensity() - 1 );
756 }
757 } else {
758 cur.set_field_intensity( 3 );
759 int num_bolts = rng( 3, 6 );
760 for( int i = 0; i < num_bolts; i++ ) {
761 point dir;
762 while( dir == point_zero ) {
763 dir = { rng( -1, 1 ), rng( -1, 1 ) };
764 }
765 int dist = rng( 4, 12 );
766 point bolt = p.xy();
767 for( int n = 0; n < dist; n++ ) {
768 bolt += dir;
769 pd.here.add_field( tripoint( bolt, p.z ), fd_electricity, rng( 2, 3 ) );
770 if( one_in( 4 ) ) {
771 if( dir.x == 0 ) {
772 dir.x = rng( 0, 1 ) * 2 - 1;
773 } else {
774 dir.x = 0;
775 }
776 }
777 if( one_in( 4 ) ) {
778 if( dir.y == 0 ) {
779 dir.y = rng( 0, 1 ) * 2 - 1;
780 } else {
781 dir.y = 0;
782 }
783 }
784 }
785 }
786 }
787 }
788
field_processor_fd_acid_vent(const tripoint & p,field_entry & cur,field_proc_data & pd)789 static void field_processor_fd_acid_vent( const tripoint &p, field_entry &cur, field_proc_data &pd )
790 {
791 if( cur.get_field_intensity() > 1 ) {
792 if( cur.get_field_age() >= 1_minutes ) {
793 cur.set_field_intensity( cur.get_field_intensity() - 1 );
794 cur.set_field_age( 0_turns );
795 }
796 } else {
797 cur.set_field_intensity( 3 );
798 for( const tripoint &t : points_in_radius( p, 5 ) ) {
799 const field_entry *acid = pd.here.get_field( t, fd_acid );
800 if( acid && acid->get_field_intensity() == 0 ) {
801 int new_intensity = 3 - rl_dist( p, t ) / 2 + ( one_in( 3 ) ? 1 : 0 );
802 if( new_intensity > 3 ) {
803 new_intensity = 3;
804 }
805 if( new_intensity > 0 ) {
806 pd.here.add_field( t, fd_acid, new_intensity );
807 }
808 }
809 }
810 }
811 }
812
field_processor_fd_bees(const tripoint & p,field_entry & cur,field_proc_data & pd)813 void field_processor_fd_bees( const tripoint &p, field_entry &cur, field_proc_data &pd )
814 {
815 // Poor bees are vulnerable to so many other fields.
816 // TODO: maybe adjust effects based on different fields.
817 // FIXME replace this insanity with a bool flag in the field_type
818 const field &curfield = pd.here.field_at( p );
819 if( curfield.find_field( fd_web ) ||
820 curfield.find_field( fd_fire ) ||
821 curfield.find_field( fd_smoke ) ||
822 curfield.find_field( fd_toxic_gas ) ||
823 curfield.find_field( fd_tear_gas ) ||
824 curfield.find_field( fd_relax_gas ) ||
825 curfield.find_field( fd_nuke_gas ) ||
826 curfield.find_field( fd_gas_vent ) ||
827 curfield.find_field( fd_smoke_vent ) ||
828 curfield.find_field( fd_fungicidal_gas ) ||
829 curfield.find_field( fd_insecticidal_gas ) ||
830 curfield.find_field( fd_fire_vent ) ||
831 curfield.find_field( fd_flame_burst ) ||
832 curfield.find_field( fd_electricity ) ||
833 curfield.find_field( fd_fatigue ) ||
834 curfield.find_field( fd_shock_vent ) ||
835 curfield.find_field( fd_plasma ) ||
836 curfield.find_field( fd_laser ) ||
837 curfield.find_field( fd_dazzling ) ||
838 curfield.find_field( fd_incendiary ) ) {
839 // Kill them at the end of processing.
840 cur.set_field_intensity( 0 );
841 } else {
842 // Bees chase the player if in range, wander randomly otherwise.
843 if( !pd.player_character.is_underwater() &&
844 rl_dist( p, pd.player_character.pos() ) < 10 &&
845 pd.here.clear_path( p, pd.player_character.pos(), 10, 1, 100 ) ) {
846
847 std::vector<point> candidate_positions =
848 squares_in_direction( p.xy(), pd.player_character.pos().xy() );
849 for( const point &candidate_position : candidate_positions ) {
850 field &target_field = pd.here.get_field( tripoint( candidate_position, p.z ) );
851 // Only shift if there are no bees already there.
852 // TODO: Figure out a way to merge bee fields without allowing
853 // Them to effectively move several times in a turn depending
854 // on iteration direction.
855 if( !target_field.find_field( fd_bees ) ) {
856 pd.here.add_field( tripoint( candidate_position, p.z ), fd_bees,
857 cur.get_field_intensity(), cur.get_field_age() );
858 cur.set_field_intensity( 0 );
859 break;
860 }
861 }
862 } else {
863 pd.here.spread_gas( cur, p, 5, 0_turns, pd.sblk, pd.om_ter );
864 }
865 }
866 }
867
field_processor_fd_incendiary(const tripoint & p,field_entry & cur,field_proc_data & pd)868 void field_processor_fd_incendiary( const tripoint &p, field_entry &cur, field_proc_data &pd )
869 {
870 // Needed for variable scope
871 tripoint dst( p + point( rng( -1, 1 ), rng( -1, 1 ) ) );
872
873 if( pd.here.has_flag( TFLAG_FLAMMABLE, dst ) ||
874 pd.here.has_flag( TFLAG_FLAMMABLE_ASH, dst ) ||
875 pd.here.has_flag( TFLAG_FLAMMABLE_HARD, dst ) ) {
876 pd.here.add_field( dst, fd_fire, 1 );
877 }
878
879 // Check piles for flammable items and set those on fire
880 if( pd.here.flammable_items_at( dst ) ) {
881 pd.here.add_field( dst, fd_fire, 1 );
882 }
883
884 pd.here.create_hot_air( p, cur.get_field_intensity() );
885 }
886
field_processor_make_rubble(const tripoint & p,field_entry &,field_proc_data & pd)887 static void field_processor_make_rubble( const tripoint &p, field_entry &, field_proc_data &pd )
888 {
889 // if( cur_fd_type.legacy_make_rubble )
890 // Legacy Stuff
891 pd.here.make_rubble( p );
892 }
893
field_processor_fd_fungicidal_gas(const tripoint & p,field_entry & cur,field_proc_data & pd)894 static void field_processor_fd_fungicidal_gas( const tripoint &p, field_entry &cur,
895 field_proc_data &pd )
896 {
897 // Check the terrain and replace it accordingly to simulate the fungus dieing off
898 const ter_t &ter = pd.map_tile.get_ter_t();
899 const furn_t &frn = pd.map_tile.get_furn_t();
900 const int intensity = cur.get_field_intensity();
901 if( ter.has_flag( TFLAG_FUNGUS ) && one_in( 10 / intensity ) ) {
902 pd.here.ter_set( p, t_dirt );
903 }
904 if( frn.has_flag( TFLAG_FUNGUS ) && one_in( 10 / intensity ) ) {
905 pd.here.furn_set( p, f_null );
906 }
907 }
908
field_processor_fd_fire(const tripoint & p,field_entry & cur,field_proc_data & pd)909 void field_processor_fd_fire( const tripoint &p, field_entry &cur, field_proc_data &pd )
910 {
911 const field_type_id fd_fire = ::fd_fire;
912 map &here = pd.here;
913 maptile &map_tile = pd.map_tile;
914 const oter_id om_ter = pd.om_ter;
915
916 cur.set_field_age( std::max( -24_hours, cur.get_field_age() ) );
917 // Entire objects for ter/frn for flags
918 bool sheltered = g->is_sheltered( p );
919 int winddirection = g->weather.winddirection;
920 int windpower = get_local_windpower( g->weather.windspeed, om_ter, p, winddirection,
921 sheltered );
922 const ter_t &ter = map_tile.get_ter_t();
923 const furn_t &frn = map_tile.get_furn_t();
924
925 // We've got ter/furn cached, so let's use that
926 const bool is_sealed = ter_furn_has_flag( ter, frn, TFLAG_SEALED ) &&
927 !ter_furn_has_flag( ter, frn, TFLAG_ALLOW_FIELD_EFFECT );
928 // Smoke generation probability, consumed items count
929 int smoke = 0;
930 int consumed = 0;
931 // How much time to add to the fire's life due to burned items/terrain/furniture
932 time_duration time_added = 0_turns;
933 // Checks if the fire can spread
934 const bool can_spread = !ter_furn_has_flag( ter, frn, TFLAG_FIRE_CONTAINER );
935 // If the flames are in furniture with fire_container flag like brazier or oven,
936 // they're fully contained, so skip consuming terrain
937 const bool can_burn = ( ter.is_flammable() || frn.is_flammable() ) &&
938 !ter_furn_has_flag( ter, frn, TFLAG_FIRE_CONTAINER );
939 // The huge indent below should probably be somehow moved away from here
940 // without forcing the function to use i_at( p ) for fires without items
941 if( !is_sealed && map_tile.get_item_count() > 0 ) {
942 map_stack items_here = here.i_at( p );
943 std::vector<item> new_content;
944 for( auto it = items_here.begin(); it != items_here.end(); ) {
945 if( it->will_explode_in_fire() ) {
946 // We need to make a copy because the iterator validity is not predictable
947 item copy = *it;
948 it = items_here.erase( it );
949 if( copy.detonate( p, new_content ) ) {
950 // Need to restart, iterators may not be valid
951 it = items_here.begin();
952 }
953 } else {
954 ++it;
955 }
956 }
957
958 fire_data frd( cur.get_field_intensity(), !can_spread );
959 // The highest # of items this fire can remove in one turn
960 int max_consume = cur.get_field_intensity() * 2;
961
962 for( auto fuel = items_here.begin(); fuel != items_here.end() && consumed < max_consume; ) {
963 // `item::burn` modifies the charges in order to simulate some of them getting
964 // destroyed by the fire, this changes the item weight, but may not actually
965 // destroy it. We need to spawn products anyway.
966 const units::mass old_weight = fuel->weight( false );
967 bool destroyed = fuel->burn( frd );
968 // If the item is considered destroyed, it may have negative charge count,
969 // see `item::burn?. This in turn means `item::weight` returns a negative value,
970 // which we can not use, so only call `weight` when it's still an existing item.
971 const units::mass new_weight = destroyed ? 0_gram : fuel->weight( false );
972 if( old_weight != new_weight ) {
973 here.create_burnproducts( p, *fuel, old_weight - new_weight );
974 }
975
976 if( destroyed ) {
977 // If we decided the item was destroyed by fire, remove it.
978 // But remember its contents, except for irremovable mods, if any
979 const std::list<item *> content_list = fuel->contents.all_items_top();
980 for( item *it : content_list ) {
981 if( !it->is_irremovable() ) {
982 new_content.push_back( item( *it ) );
983 }
984 }
985 fuel = items_here.erase( fuel );
986 consumed++;
987 } else {
988 ++fuel;
989 }
990 }
991
992 here.spawn_items( p, new_content );
993 smoke = roll_remainder( frd.smoke_produced );
994 time_added = 1_turns * roll_remainder( frd.fuel_produced );
995 }
996
997 int part;
998 // Get the part of the vehicle in the fire (_internal skips the boundary check)
999 vehicle *veh = here.veh_at_internal( p, part );
1000 if( veh != nullptr ) {
1001 veh->damage( part, cur.get_field_intensity() * 10, damage_type::HEAT, true );
1002 // Damage the vehicle in the fire.
1003 }
1004 if( can_burn ) {
1005 if( ter.has_flag( TFLAG_SWIMMABLE ) ) {
1006 // Flames die quickly on water
1007 cur.set_field_age( cur.get_field_age() + 4_minutes );
1008 }
1009
1010 // Consume the terrain we're on
1011 if( ter_furn_has_flag( ter, frn, TFLAG_FLAMMABLE ) ) {
1012 // The fire feeds on the ground itself until max intensity.
1013 time_added += 1_turns * ( 5 - cur.get_field_intensity() );
1014 smoke += 2;
1015 smoke += static_cast<int>( windpower / 5 );
1016 if( cur.get_field_intensity() > 1 &&
1017 one_in( 200 - cur.get_field_intensity() * 50 ) ) {
1018 here.destroy( p, false );
1019 }
1020
1021 } else if( ter_furn_has_flag( ter, frn, TFLAG_FLAMMABLE_HARD ) &&
1022 one_in( 3 ) ) {
1023 // The fire feeds on the ground itself until max intensity.
1024 time_added += 1_turns * ( 4 - cur.get_field_intensity() );
1025 smoke += 2;
1026 smoke += static_cast<int>( windpower / 5 );
1027 if( cur.get_field_intensity() > 1 &&
1028 one_in( 200 - cur.get_field_intensity() * 50 ) ) {
1029 here.destroy( p, false );
1030 }
1031
1032 } else if( ter.has_flag( TFLAG_FLAMMABLE_ASH ) ) {
1033 // The fire feeds on the ground itself until max intensity.
1034 time_added += 1_turns * ( 5 - cur.get_field_intensity() );
1035 smoke += 2;
1036 smoke += static_cast<int>( windpower / 5 );
1037 if( cur.get_field_intensity() > 1 &&
1038 one_in( 200 - cur.get_field_intensity() * 50 ) ) {
1039 if( p.z > 0 ) {
1040 // We're in the air
1041 here.ter_set( p, t_open_air );
1042 } else {
1043 here.ter_set( p, t_dirt );
1044 }
1045 }
1046
1047 } else if( frn.has_flag( TFLAG_FLAMMABLE_ASH ) ) {
1048 // The fire feeds on the ground itself until max intensity.
1049 time_added += 1_turns * ( 5 - cur.get_field_intensity() );
1050 smoke += 2;
1051 smoke += static_cast<int>( windpower / 5 );
1052 if( cur.get_field_intensity() > 1 &&
1053 one_in( 200 - cur.get_field_intensity() * 50 ) ) {
1054 here.furn_set( p, f_ash );
1055 here.add_item_or_charges( p, item( "ash" ) );
1056 }
1057
1058 } else if( ter.has_flag( TFLAG_NO_FLOOR ) && here.has_zlevels() && p.z > -OVERMAP_DEPTH ) {
1059 // We're hanging in the air - let's fall down
1060 tripoint dst{ p.xy(), p.z - 1 };
1061 if( here.valid_move( p, dst, true, true ) ) {
1062 maptile dst_tile = here.maptile_at_internal( dst );
1063 field_entry *fire_there = dst_tile.find_field( fd_fire );
1064 if( !fire_there ) {
1065 here.add_field( dst, fd_fire, 1, 0_turns, false );
1066 cur.mod_field_intensity( -1 );
1067 } else {
1068 // Don't fuel raging fires or they'll burn forever
1069 // as they can produce small fires above themselves
1070 int new_intensity = std::max( cur.get_field_intensity(),
1071 fire_there->get_field_intensity() );
1072 // Allow smaller fires to combine
1073 if( new_intensity < 3 &&
1074 cur.get_field_intensity() == fire_there->get_field_intensity() ) {
1075 new_intensity++;
1076 }
1077 // A raging fire below us can support us for a while
1078 // Otherwise decay and decay fast
1079 if( fire_there->get_field_intensity() < 3 || one_in( 10 ) ) {
1080 cur.set_field_intensity( cur.get_field_intensity() - 1 );
1081 }
1082 fire_there->set_field_intensity( new_intensity );
1083 }
1084 return;
1085 }
1086 }
1087 }
1088 // Lower age is a longer lasting fire
1089 if( time_added != 0_turns ) {
1090 cur.set_field_age( cur.get_field_age() - time_added );
1091 } else if( can_burn ) {
1092 // Nothing to burn = fire should be dying out faster
1093 // Drain more power from big fires, so that they stop raging over nothing
1094 // Except for fires on stoves and fireplaces, those are made to keep the fire alive
1095 cur.mod_field_age( 10_seconds * cur.get_field_intensity() );
1096 }
1097
1098 // Below we will access our nearest 8 neighbors, so let's cache them now
1099 // This should probably be done more globally, because large fires will re-do it a lot
1100 auto neighs = here.get_neighbors( p );
1101 // Get the neighbours that are allowed due to wind direction
1102 auto maptiles = here.get_wind_blockers( winddirection, p );
1103 maptile remove_tile = std::get<0>( maptiles );
1104 maptile remove_tile2 = std::get<1>( maptiles );
1105 maptile remove_tile3 = std::get<2>( maptiles );
1106 std::vector<size_t> neighbour_vec;
1107 neighbour_vec.reserve( neighs.size() );
1108 size_t end_it = static_cast<size_t>( rng( 0, neighs.size() - 1 ) );
1109 // Start at end_it + 1, then wrap around until all elements have been processed
1110 for( size_t i = ( end_it + 1 ) % neighs.size(), count = 0;
1111 count != neighs.size();
1112 i = ( i + 1 ) % neighs.size(), count++ ) {
1113 const auto &neigh = neighs[i].second;
1114 if( ( neigh.pos().x != remove_tile.pos().x && neigh.pos().y != remove_tile.pos().y ) ||
1115 ( neigh.pos().x != remove_tile2.pos().x && neigh.pos().y != remove_tile2.pos().y ) ||
1116 ( neigh.pos().x != remove_tile3.pos().x && neigh.pos().y != remove_tile3.pos().y ) ) {
1117 neighbour_vec.push_back( i );
1118 } else if( x_in_y( 1, std::max( 2, windpower ) ) ) {
1119 neighbour_vec.push_back( i );
1120 }
1121 }
1122 // If the flames are in a pit, it can't spread to non-pit
1123 const bool in_pit = ter.id.id() == t_pit;
1124
1125 // Count adjacent fires, to optimize out needless smoke and hot air
1126 int adjacent_fires = 0;
1127
1128 // If the flames are big, they contribute to adjacent flames
1129 if( can_spread ) {
1130 if( cur.get_field_intensity() > 1 && one_in( 3 ) ) {
1131 // Basically: Scan around for a spot,
1132 // if there is more fire there, make it bigger and give it some fuel.
1133 // This is how big fires spend their excess age:
1134 // making other fires bigger. Flashpoint.
1135 if( sheltered || windpower < 5 ) {
1136 end_it = static_cast<size_t>( rng( 0, neighs.size() - 1 ) );
1137 for( size_t i = ( end_it + 1 ) % neighs.size(), count = 0;
1138 count != neighs.size() && cur.get_field_age() < 0_turns;
1139 i = ( i + 1 ) % neighs.size(), count++ ) {
1140 maptile &dst = neighs[i].second;
1141 field_entry *dstfld = dst.find_field( fd_fire );
1142 // If the fire exists and is weaker than ours, boost it
1143 if( dstfld &&
1144 ( dstfld->get_field_intensity() <= cur.get_field_intensity() ||
1145 dstfld->get_field_age() > cur.get_field_age() ) &&
1146 ( in_pit == ( dst.get_ter() == t_pit ) ) ) {
1147 if( dstfld->get_field_intensity() < 2 ) {
1148 // HACK: ignoring all map field caches, since field already exists
1149 // and intensity is increased, not decreased
1150 dstfld->mod_field_intensity( 1 );
1151 }
1152
1153 dstfld->mod_field_age( -5_minutes );
1154 cur.set_field_age( cur.get_field_age() + 5_minutes );
1155 }
1156 if( dstfld ) {
1157 adjacent_fires++;
1158 }
1159 }
1160 } else {
1161 end_it = static_cast<size_t>( rng( 0, neighbour_vec.size() - 1 ) );
1162 for( size_t i = ( end_it + 1 ) % neighbour_vec.size(), count = 0;
1163 count != neighbour_vec.size() && cur.get_field_age() < 0_turns;
1164 i = ( i + 1 ) % neighbour_vec.size(), count++ ) {
1165 maptile &dst = neighs[neighbour_vec[i]].second;
1166 field_entry *dstfld = dst.find_field( fd_fire );
1167 // If the fire exists and is weaker than ours, boost it
1168 if( dstfld &&
1169 ( dstfld->get_field_intensity() <= cur.get_field_intensity() ||
1170 dstfld->get_field_age() > cur.get_field_age() ) &&
1171 ( in_pit == ( dst.get_ter() == t_pit ) ) ) {
1172 if( dstfld->get_field_intensity() < 2 ) {
1173 // HACK: ignoring all map field caches, since field already exists
1174 // and intensity is increased, not decreased
1175 dstfld->mod_field_intensity( 1 );
1176 }
1177
1178 dstfld->set_field_age( dstfld->get_field_age() - 5_minutes );
1179 cur.set_field_age( cur.get_field_age() + 5_minutes );
1180 }
1181
1182 if( dstfld ) {
1183 adjacent_fires++;
1184 }
1185 }
1186 }
1187 } else if( cur.get_field_age() < 0_turns && cur.get_field_intensity() < 3 ) {
1188 // See if we can grow into a stage 2/3 fire, for this
1189 // burning neighbors are necessary in addition to
1190 // field age < 0, or alternatively, a LOT of fuel.
1191
1192 // The maximum fire intensity is 1 for a lone fire, 2 for at least 1 neighbor,
1193 // 3 for at least 2 neighbors.
1194 int maximum_intensity = 1;
1195
1196 // The following logic looks a bit complex due to optimization concerns, so here are the semantics:
1197 // 1. Calculate maximum field intensity based on fuel, -50 minutes is 2(medium), -500 minutes is 3(raging)
1198 // 2. Calculate maximum field intensity based on neighbors, 3 neighbors is 2(medium), 7 or more neighbors is 3(raging)
1199 // 3. Pick the higher maximum between 1. and 2.
1200 if( cur.get_field_age() < -500_minutes ) {
1201 maximum_intensity = 3;
1202 } else {
1203 for( auto &neigh : neighs ) {
1204 if( neigh.second.get_field().find_field( fd_fire ) ) {
1205 adjacent_fires++;
1206 }
1207 }
1208 maximum_intensity = 1 + ( adjacent_fires >= 3 ) + ( adjacent_fires >= 7 );
1209
1210 if( maximum_intensity < 2 && cur.get_field_age() < -50_minutes ) {
1211 maximum_intensity = 2;
1212 }
1213 }
1214
1215 // If we consumed a lot, the flames grow higher
1216 if( cur.get_field_intensity() < maximum_intensity && cur.get_field_age() < 0_turns ) {
1217 // Fires under 0 age grow in size. Level 3 fires under 0 spread later on.
1218 // Weaken the newly-grown fire
1219 cur.set_field_intensity( cur.get_field_intensity() + 1 );
1220 cur.set_field_age( cur.get_field_age() + 10_minutes * cur.get_field_intensity() );
1221 }
1222 }
1223 }
1224 // Consume adjacent fuel / terrain / webs to spread.
1225 // Allow raging fires (and only raging fires) to spread up
1226 // Spreading down is achieved by wrecking the walls/floor and then falling
1227 if( here.has_zlevels() && cur.get_field_intensity() == 3 && p.z < OVERMAP_HEIGHT ) {
1228 const tripoint dst_p = tripoint( p.xy(), p.z + 1 );
1229 // Let it burn through the floor
1230 maptile dst = here.maptile_at_internal( dst_p );
1231 const auto &dst_ter = dst.get_ter_t();
1232 if( dst_ter.has_flag( TFLAG_NO_FLOOR ) ||
1233 dst_ter.has_flag( TFLAG_FLAMMABLE ) ||
1234 dst_ter.has_flag( TFLAG_FLAMMABLE_ASH ) ||
1235 dst_ter.has_flag( TFLAG_FLAMMABLE_HARD ) ) {
1236 field_entry *nearfire = dst.find_field( fd_fire );
1237 if( nearfire != nullptr ) {
1238 nearfire->mod_field_age( -2_turns );
1239 } else {
1240 here.add_field( dst_p, fd_fire, 1, 0_turns, false );
1241 }
1242 // Fueling fires above doesn't cost fuel
1243 }
1244 }
1245 // Our iterator will start at end_i + 1 and increment from there and then wrap around.
1246 // This guarantees it will check all neighbors, starting from a random one
1247 if( sheltered || windpower < 5 ) {
1248 const size_t end_i = static_cast<size_t>( rng( 0, neighs.size() - 1 ) );
1249 for( size_t i = ( end_i + 1 ) % neighs.size(), count = 0;
1250 count != neighs.size();
1251 i = ( i + 1 ) % neighs.size(), count++ ) {
1252 if( one_in( cur.get_field_intensity() * 2 ) ) {
1253 // Skip some processing to save on CPU
1254 continue;
1255 }
1256
1257 tripoint &dst_p = neighs[i].first;
1258 maptile &dst = neighs[i].second;
1259 // No bounds checking here: we'll treat the invalid neighbors as valid.
1260 // We're using the map tile wrapper, so we can treat invalid tiles as sentinels.
1261 // This will create small oddities on map edges, but nothing more noticeable than
1262 // "cut-off" that happens with bounds checks.
1263
1264 if( dst.find_field( fd_fire ) ) {
1265 // We handled supporting fires in the section above, no need to do it here
1266 continue;
1267 }
1268
1269 field_entry *nearwebfld = dst.find_field( fd_web );
1270 int spread_chance = 25 * ( cur.get_field_intensity() - 1 );
1271 if( nearwebfld ) {
1272 spread_chance = 50 + spread_chance / 2;
1273 }
1274
1275 const ter_t &dster = dst.get_ter_t();
1276 const furn_t &dsfrn = dst.get_furn_t();
1277 // Allow weaker fires to spread occasionally
1278 const int power = cur.get_field_intensity() + one_in( 5 );
1279 if( can_spread && rng( 1, 100 ) < spread_chance &&
1280 ( in_pit == ( dster.id.id() == t_pit ) ) &&
1281 (
1282 ( power >= 2 && ( ter_furn_has_flag( dster, dsfrn, TFLAG_FLAMMABLE ) && one_in( 2 ) ) ) ||
1283 ( power >= 2 && ( ter_furn_has_flag( dster, dsfrn, TFLAG_FLAMMABLE_ASH ) && one_in( 2 ) ) ) ||
1284 ( power >= 3 && ( ter_furn_has_flag( dster, dsfrn, TFLAG_FLAMMABLE_HARD ) && one_in( 5 ) ) ) ||
1285 nearwebfld ||
1286 ( one_in( 5 ) && dst.get_item_count() > 0 &&
1287 here.flammable_items_at( p + eight_horizontal_neighbors[i] ) )
1288 ) ) {
1289 // Nearby open flammable ground? Set it on fire.
1290 // Make the new fire quite weak, so that it doesn't start jumping around instantly
1291 if( here.add_field( dst_p, fd_fire, 1, 2_minutes, false ) ) {
1292 // Consume a bit of our fuel
1293 cur.set_field_age( cur.get_field_age() + 1_minutes );
1294 }
1295 if( nearwebfld ) {
1296 nearwebfld->set_field_intensity( 0 );
1297 }
1298 }
1299 }
1300 } else {
1301 const size_t end_i = static_cast<size_t>( rng( 0, neighbour_vec.size() - 1 ) );
1302 for( size_t i = ( end_i + 1 ) % neighbour_vec.size(), count = 0;
1303 count != neighbour_vec.size();
1304 i = ( i + 1 ) % neighbour_vec.size(), count++ ) {
1305 if( one_in( cur.get_field_intensity() * 2 ) ) {
1306 // Skip some processing to save on CPU
1307 continue;
1308 }
1309
1310 if( neighbour_vec.empty() ) {
1311 continue;
1312 }
1313
1314 tripoint &dst_p = neighs[neighbour_vec[i]].first;
1315 maptile &dst = neighs[neighbour_vec[i]].second;
1316 // No bounds checking here: we'll treat the invalid neighbors as valid.
1317 // We're using the map tile wrapper, so we can treat invalid tiles as sentinels.
1318 // This will create small oddities on map edges, but nothing more noticeable than
1319 // "cut-off" that happens with bounds checks.
1320
1321 if( dst.find_field( fd_fire ) ) {
1322 // We handled supporting fires in the section above, no need to do it here
1323 continue;
1324 }
1325
1326 field_entry *nearwebfld = dst.find_field( fd_web );
1327 int spread_chance = 25 * ( cur.get_field_intensity() - 1 );
1328 if( nearwebfld ) {
1329 spread_chance = 50 + spread_chance / 2;
1330 }
1331
1332 const ter_t &dster = dst.get_ter_t();
1333 const furn_t &dsfrn = dst.get_furn_t();
1334 // Allow weaker fires to spread occasionally
1335 const int power = cur.get_field_intensity() + one_in( 5 );
1336 if( can_spread && rng( 1, 100 ) < spread_chance &&
1337 ( in_pit == ( dster.id.id() == t_pit ) ) &&
1338 (
1339 ( power >= 2 && ( ter_furn_has_flag( dster, dsfrn, TFLAG_FLAMMABLE ) && one_in( 2 ) ) ) ||
1340 ( power >= 2 && ( ter_furn_has_flag( dster, dsfrn, TFLAG_FLAMMABLE_ASH ) && one_in( 2 ) ) ) ||
1341 ( power >= 3 && ( ter_furn_has_flag( dster, dsfrn, TFLAG_FLAMMABLE_HARD ) && one_in( 5 ) ) ) ||
1342 nearwebfld ||
1343 ( one_in( 5 ) && dst.get_item_count() > 0 &&
1344 here.flammable_items_at( p + eight_horizontal_neighbors[i] ) )
1345 ) ) {
1346 // Nearby open flammable ground? Set it on fire.
1347 // Make the new fire quite weak, so that it doesn't start jumping around instantly
1348 if( here.add_field( dst_p, fd_fire, 1, 2_minutes, false ) ) {
1349 // Consume a bit of our fuel
1350 cur.set_field_age( cur.get_field_age() + 1_minutes );
1351 }
1352 if( nearwebfld ) {
1353 nearwebfld->set_field_intensity( 0 );
1354 }
1355 }
1356 }
1357 }
1358 // Create smoke once - above us if possible, at us otherwise
1359 if( !ter_furn_has_flag( ter, frn, TFLAG_SUPPRESS_SMOKE ) &&
1360 rng( 0, 100 - windpower ) <= smoke &&
1361 rng( 3, 35 ) < cur.get_field_intensity() * 10 ) {
1362 bool smoke_up = here.has_zlevels() && p.z < OVERMAP_HEIGHT;
1363 if( smoke_up ) {
1364 tripoint up{p.xy(), p.z + 1};
1365 if( here.has_flag_ter( TFLAG_NO_FLOOR, up ) ) {
1366 here.add_field( up, fd_smoke, rng( 1, cur.get_field_intensity() ), 0_turns, false );
1367 } else {
1368 // Can't create smoke above
1369 smoke_up = false;
1370 }
1371 }
1372
1373 if( !smoke_up ) {
1374 // Create thicker smoke
1375 here.add_field( p, fd_smoke, cur.get_field_intensity(), 0_turns, false );
1376 }
1377 }
1378
1379 // Hot air is a load on the CPU
1380 // Don't produce too much of it if we have a lot fires nearby, they produce
1381 // radiant heat which does what hot air would do anyway
1382 if( adjacent_fires < 5 && rng( 0, 4 - adjacent_fires ) ) {
1383 here.create_hot_air( p, cur.get_field_intensity() );
1384 }
1385 }
1386
1387 // This entire function makes very little sense. Why are the rules the way they are? Why does walking into some things destroy them but not others?
1388
1389 /*
1390 Function: step_in_field
1391 Triggers any active abilities a field effect would have. Fire burns you, acid melts you, etc.
1392 If you add a field effect that interacts with the player place a case statement in the switch here.
1393 If you wish for a field effect to do something over time (propagate, interact with terrain, etc) place it in process_subfields
1394 */
player_in_field(player & u)1395 void map::player_in_field( player &u )
1396 {
1397 // A copy of the current field for reference. Do not add fields to it, use map::add_field
1398 field &curfield = get_field( u.pos() );
1399 // Are we inside?
1400 bool inside = false;
1401 // If we are in a vehicle figure out if we are inside (reduces effects usually)
1402 // and what part of the vehicle we need to deal with.
1403 if( u.in_vehicle ) {
1404 if( const optional_vpart_position vp = veh_at( u.pos() ) ) {
1405 inside = vp->is_inside();
1406 }
1407 }
1408
1409 // Iterate through all field effects on this tile.
1410 // Do not remove the field with remove_field, instead set it's intensity to 0. It will be removed
1411 // later by the field processing, which will also adjust field_count accordingly.
1412 for( auto &field_list_it : curfield ) {
1413 field_entry &cur = field_list_it.second;
1414 if( !cur.is_field_alive() ) {
1415 continue;
1416 }
1417
1418 // Do things based on what field effect we are currently in.
1419 const field_type_id ft = cur.get_field_type();
1420 if( ft == fd_acid ) {
1421 // Assume vehicles block acid damage entirely,
1422 // you're certainly not standing in it.
1423 if( !u.in_vehicle && !u.has_trait( trait_ACIDPROOF ) ) {
1424 int total_damage = 0;
1425 total_damage += burn_body_part( u, cur, bodypart_id( "foot_l" ), 2 );
1426 total_damage += burn_body_part( u, cur, bodypart_id( "foot_r" ), 2 );
1427 const bool on_ground = u.is_on_ground();
1428 if( on_ground ) {
1429 // Apply the effect to the remaining body parts
1430 total_damage += burn_body_part( u, cur, bodypart_id( "leg_l" ), 2 );
1431 total_damage += burn_body_part( u, cur, bodypart_id( "leg_r" ), 2 );
1432 total_damage += burn_body_part( u, cur, bodypart_id( "hand_l" ), 2 );
1433 total_damage += burn_body_part( u, cur, bodypart_id( "hand_r" ), 2 );
1434 total_damage += burn_body_part( u, cur, bodypart_id( "torso" ), 2 );
1435 // Less arms = less ability to keep upright
1436 if( ( !u.has_two_arms() && one_in( 4 ) ) || one_in( 2 ) ) {
1437 total_damage += burn_body_part( u, cur, bodypart_id( "arm_l" ), 1 );
1438 total_damage += burn_body_part( u, cur, bodypart_id( "arm_r" ), 1 );
1439 total_damage += burn_body_part( u, cur, bodypart_id( "head" ), 1 );
1440 }
1441 }
1442
1443 if( on_ground && total_damage > 0 ) {
1444 u.add_msg_player_or_npc( m_bad, _( "The acid burns your body!" ),
1445 _( "The acid burns <npcname>s body!" ) );
1446 } else if( total_damage > 0 ) {
1447 u.add_msg_player_or_npc( m_bad, _( "The acid burns your legs and feet!" ),
1448 _( "The acid burns <npcname>s legs and feet!" ) );
1449 } else if( on_ground ) {
1450 u.add_msg_if_player( m_warning, _( "You're lying in a pool of acid" ) );
1451 } else {
1452 u.add_msg_if_player( m_warning, _( "You're standing in a pool of acid" ) );
1453 }
1454
1455 u.check_dead_state();
1456 }
1457 }
1458 if( ft == fd_sap ) {
1459 // Sap does nothing to cars.
1460 if( !u.in_vehicle ) {
1461 // Use up sap.
1462 mod_field_intensity( u.pos(), ft, -1 );
1463 }
1464 }
1465 if( ft == fd_sludge ) {
1466 // Sludge is on the ground, but you are above the ground when boarded on a vehicle
1467 if( !u.in_vehicle ) {
1468 u.add_msg_if_player( m_bad, _( "The sludge is thick and sticky. You struggle to pull free." ) );
1469 u.moves -= cur.get_field_intensity() * 300;
1470 cur.set_field_intensity( 0 );
1471 }
1472 }
1473 if( ft == fd_fire ) {
1474 // Heatsink or suit prevents ALL fire damage.
1475 if( !u.has_active_bionic( bio_heatsink ) && !u.is_wearing( itype_rm13_armor_on ) ) {
1476
1477 // To modify power of a field based on... whatever is relevant for the effect.
1478 int adjusted_intensity = cur.get_field_intensity();
1479 // Burn the player. Less so if you are in a car or ON a car.
1480 if( u.in_vehicle ) {
1481 if( inside ) {
1482 adjusted_intensity -= 2;
1483 } else {
1484 adjusted_intensity -= 1;
1485 }
1486 }
1487
1488 if( adjusted_intensity >= 1 ) {
1489 // Burn message by intensity
1490 static const std::array<std::string, 4> player_burn_msg = { {
1491 translate_marker( "You burn your legs and feet!" ),
1492 translate_marker( "You're burning up!" ),
1493 translate_marker( "You're set ablaze!" ),
1494 translate_marker( "Your whole body is burning!" )
1495 }
1496 };
1497 static const std::array<std::string, 4> npc_burn_msg = { {
1498 translate_marker( "<npcname> burns their legs and feet!" ),
1499 translate_marker( "<npcname> is burning up!" ),
1500 translate_marker( "<npcname> is set ablaze!" ),
1501 translate_marker( "<npcname>s whole body is burning!" )
1502 }
1503 };
1504 static const std::array<std::string, 4> player_warn_msg = { {
1505 translate_marker( "You're standing in a fire!" ),
1506 translate_marker( "You're waist-deep in a fire!" ),
1507 translate_marker( "You're surrounded by raging fire!" ),
1508 translate_marker( "You're lying in fire!" )
1509 }
1510 };
1511
1512 const int burn_min = adjusted_intensity;
1513 const int burn_max = 3 * adjusted_intensity + 3;
1514 std::list<bodypart_id> parts_burned;
1515 int msg_num = adjusted_intensity - 1;
1516 if( !u.is_on_ground() ) {
1517 switch( adjusted_intensity ) {
1518 case 3:
1519 parts_burned.push_back( bodypart_id( "hand_l" ) );
1520 parts_burned.push_back( bodypart_id( "hand_r" ) );
1521 parts_burned.push_back( bodypart_id( "arm_l" ) );
1522 parts_burned.push_back( bodypart_id( "arm_r" ) );
1523 /* fallthrough */
1524 case 2:
1525 parts_burned.push_back( bodypart_id( "torso" ) );
1526 /* fallthrough */
1527 case 1:
1528 parts_burned.push_back( bodypart_id( "foot_l" ) );
1529 parts_burned.push_back( bodypart_id( "foot_r" ) );
1530 parts_burned.push_back( bodypart_id( "leg_l" ) );
1531 parts_burned.push_back( bodypart_id( "leg_r" ) );
1532 }
1533 } else {
1534 // Lying in the fire is BAAAD news, hits every body part.
1535 msg_num = 3;
1536 const std::vector<bodypart_id> all_parts = u.get_all_body_parts();
1537 parts_burned.assign( all_parts.begin(), all_parts.end() );
1538 }
1539
1540 int total_damage = 0;
1541 for( const bodypart_id &part_burned : parts_burned ) {
1542 const dealt_damage_instance dealt = u.deal_damage( nullptr, part_burned,
1543 damage_instance( damage_type::HEAT, rng( burn_min, burn_max ) ) );
1544 total_damage += dealt.type_damage( damage_type::HEAT );
1545 }
1546 if( total_damage > 0 ) {
1547 u.add_msg_player_or_npc( m_bad, _( player_burn_msg[msg_num] ), _( npc_burn_msg[msg_num] ) );
1548 } else {
1549 u.add_msg_if_player( m_warning, _( player_warn_msg[msg_num] ) );
1550 }
1551 u.check_dead_state();
1552 }
1553 }
1554
1555 }
1556 if( ft == fd_tear_gas ) {
1557 // Tear gas will both give you teargas disease and/or blind you.
1558 if( ( cur.get_field_intensity() > 1 || !one_in( 3 ) ) && ( !inside || one_in( 3 ) ) ) {
1559 u.add_env_effect( effect_teargas, bodypart_id( "mouth" ), 5, 20_seconds );
1560 }
1561 if( cur.get_field_intensity() > 1 && ( !inside || one_in( 3 ) ) ) {
1562 u.add_env_effect( effect_blind, bodypart_id( "eyes" ), cur.get_field_intensity() * 2, 10_seconds );
1563 }
1564 }
1565 if( ft == fd_fungal_haze ) {
1566 if( !u.has_trait( trait_M_IMMUNE ) && ( !inside || one_in( 4 ) ) ) {
1567 u.add_env_effect( effect_fungus, bodypart_id( "mouth" ), 4, 10_minutes, true );
1568 u.add_env_effect( effect_fungus, bodypart_id( "eyes" ), 4, 10_minutes, true );
1569 }
1570 }
1571
1572 if( cur.get_intensity_level().extra_radiation_min > 0 ) {
1573 const field_intensity_level &int_level = cur.get_intensity_level();
1574 // Get irradiated by the nuclear fallout.
1575 const float rads = rng( int_level.extra_radiation_min + 1,
1576 int_level.extra_radiation_max * ( int_level.extra_radiation_max + 1 ) );
1577 const bool rad_proof = !u.irradiate( rads );
1578 // TODO: Reduce damage for rad resistant?
1579 if( int_level.extra_radiation_min > 0 && !rad_proof ) {
1580 u.add_msg_if_player( m_bad, int_level.radiation_hurt_message.translated() );
1581 u.hurtall( rng( int_level.radiation_hurt_damage_min, int_level.radiation_hurt_damage_max ),
1582 nullptr );
1583 }
1584 }
1585 if( ft == fd_flame_burst ) {
1586 // A burst of flame? Only hits the legs and torso.
1587 if( !inside ) {
1588 // Fireballs can't touch you inside a car.
1589 // Heatsink or suit stops fire.
1590 if( !u.has_active_bionic( bio_heatsink ) &&
1591 !u.is_wearing( itype_rm13_armor_on ) ) {
1592 u.add_msg_player_or_npc( m_bad, _( "You're torched by flames!" ),
1593 _( "<npcname> is torched by flames!" ) );
1594 u.deal_damage( nullptr, bodypart_id( "leg_l" ), damage_instance( damage_type::HEAT, rng( 2, 6 ) ) );
1595 u.deal_damage( nullptr, bodypart_id( "leg_r" ), damage_instance( damage_type::HEAT, rng( 2, 6 ) ) );
1596 u.deal_damage( nullptr, bodypart_id( "torso" ), damage_instance( damage_type::HEAT, rng( 4, 9 ) ) );
1597 u.check_dead_state();
1598 } else {
1599 u.add_msg_player_or_npc( _( "These flames do not burn you." ),
1600 _( "Those flames do not burn <npcname>." ) );
1601 }
1602 }
1603 }
1604 if( ft == fd_electricity ) {
1605 // Small universal damage based on intensity, only if not electroproofed.
1606 if( !u.is_elec_immune() ) {
1607 int total_damage = 0;
1608 for( const bodypart_id &bp :
1609 u.get_all_body_parts( get_body_part_flags::only_main ) ) {
1610 const int dmg = rng( 1, cur.get_field_intensity() );
1611 total_damage += u.deal_damage( nullptr, bp, damage_instance( damage_type::ELECTRIC,
1612 dmg ) ).total_damage();
1613 }
1614
1615 if( total_damage > 0 ) {
1616 if( u.has_trait( trait_ELECTRORECEPTORS ) ) {
1617 u.add_msg_player_or_npc( m_bad, _( "You're painfully electrocuted!" ),
1618 _( "<npcname> is shocked!" ) );
1619 u.mod_pain( total_damage / 2 );
1620 } else {
1621 u.add_msg_player_or_npc( m_bad, _( "You're shocked!" ), _( "<npcname> is shocked!" ) );
1622 }
1623 } else {
1624 u.add_msg_player_or_npc( _( "The electric cloud doesn't affect you." ),
1625 _( "The electric cloud doesn't seem to affect <npcname>." ) );
1626 }
1627 }
1628 }
1629 if( ft == fd_fatigue ) {
1630 // Assume the rift is on the ground for now to prevent issues with the player being unable access vehicle controls on the same tile due to teleportation.
1631 if( !u.in_vehicle ) {
1632 // Teleports you... somewhere.
1633 if( rng( 0, 2 ) < cur.get_field_intensity() && u.is_player() ) {
1634 add_msg( m_bad, _( "You're violently teleported!" ) );
1635 u.hurtall( cur.get_field_intensity(), nullptr );
1636 teleport::teleport( u );
1637 }
1638 }
1639 }
1640 // Why do these get removed???
1641 // Stepping on a shock vent shuts it down.
1642 if( ft == fd_shock_vent || ft == fd_acid_vent ) {
1643 cur.set_field_intensity( 0 );
1644 }
1645 if( ft == fd_bees ) {
1646 // Player is immune to bees while underwater.
1647 if( !u.is_underwater() ) {
1648 const int intensity = cur.get_field_intensity();
1649 // Bees will try to sting you in random body parts, up to 8 times.
1650 for( int i = 0; i < rng( 1, 7 ); i++ ) {
1651 bodypart_id bp = u.get_random_body_part();
1652 int sum_cover = 0;
1653 for( const item &i : u.worn ) {
1654 if( i.covers( bp ) ) {
1655 sum_cover += i.get_coverage( bp );
1656 }
1657 }
1658 // Get stung if [clothing on a body part isn't thick enough (like t-shirt) OR clothing covers less than 100% of body part]
1659 // AND clothing on affected body part has low environmental protection value
1660 if( ( u.get_armor_cut( bp ) <= 1 || ( sum_cover < 100 && x_in_y( 100 - sum_cover, 100 ) ) ) &&
1661 u.add_env_effect( effect_stung, bp, intensity, 9_minutes ) ) {
1662 u.add_msg_if_player( m_bad, _( "The bees sting you in %s!" ),
1663 body_part_name_accusative( bp ) );
1664 }
1665 }
1666 }
1667 }
1668 if( ft == fd_incendiary ) {
1669 // Mysterious incendiary substance melts you horribly.
1670 if( u.has_trait( trait_M_SKIN2 ) ||
1671 u.has_trait( trait_M_SKIN3 ) ||
1672 cur.get_field_intensity() == 1 ) {
1673 u.add_msg_player_or_npc( m_bad, _( "The incendiary burns you!" ),
1674 _( "The incendiary burns <npcname>!" ) );
1675 u.hurtall( rng( 1, 3 ), nullptr );
1676 } else {
1677 u.add_msg_player_or_npc( m_bad, _( "The incendiary melts into your skin!" ),
1678 _( "The incendiary melts into <npcname>s skin!" ) );
1679 u.add_effect( effect_onfire, 8_turns, bodypart_id( "torso" ) );
1680 u.hurtall( rng( 2, 6 ), nullptr );
1681 }
1682 }
1683 // Both gases are unhealthy and become deadly if you cross a related threshold.
1684 if( ft == fd_fungicidal_gas || ft == fd_insecticidal_gas ) {
1685 // The gas won't harm you inside a vehicle.
1686 if( !inside ) {
1687 // Full body suits protect you from the effects of the gas.
1688 if( !( u.worn_with_flag( STATIC( flag_id( "GAS_PROOF" ) ) ) &&
1689 u.get_env_resist( bodypart_id( "mouth" ) ) >= 15 &&
1690 u.get_env_resist( bodypart_id( "eyes" ) ) >= 15 ) ) {
1691 const int intensity = cur.get_field_intensity();
1692 bool inhaled = u.add_env_effect( effect_poison, bodypart_id( "mouth" ), 5, intensity * 1_minutes );
1693 if( u.has_trait( trait_THRESH_MYCUS ) || u.has_trait( trait_THRESH_MARLOSS ) ||
1694 ( ft == fd_insecticidal_gas &&
1695 ( u.get_highest_category() == mutation_category_id( "INSECT" ) ||
1696 u.get_highest_category() == mutation_category_id( "SPIDER" ) ) ) ) {
1697 inhaled |= u.add_env_effect( effect_badpoison, bodypart_id( "mouth" ), 5, intensity * 1_minutes );
1698 u.hurtall( rng( intensity, intensity * 2 ), nullptr );
1699 u.add_msg_if_player( m_bad, _( "The %s burns your skin." ), cur.name() );
1700 }
1701
1702 if( inhaled ) {
1703 u.add_msg_if_player( m_bad, _( "The %s makes you feel sick." ), cur.name() );
1704 }
1705 }
1706 }
1707 }
1708 // Process npc complaints (moved here from fields processing)
1709 if( const int chance = std::get<0>( ft->npc_complain_data ) ) {
1710 if( u.is_npc() && chance > 0 && one_in( chance ) ) {
1711 const auto &npc_complain_data = ft->npc_complain_data;
1712 ( static_cast<npc *>( &u ) )->complain_about( std::get<1>( npc_complain_data ),
1713 std::get<2>( npc_complain_data ),
1714 std::get<3>( npc_complain_data ) );
1715 }
1716 }
1717 }
1718 }
1719
creature_in_field(Creature & critter)1720 void map::creature_in_field( Creature &critter )
1721 {
1722 bool in_vehicle = false;
1723 bool inside_vehicle = false;
1724 if( critter.is_monster() ) {
1725 monster_in_field( *static_cast<monster *>( &critter ) );
1726 } else {
1727 player *u = critter.as_player();
1728 if( u ) {
1729 in_vehicle = u->in_vehicle;
1730 // If we are in a vehicle figure out if we are inside (reduces effects usually)
1731 // and what part of the vehicle we need to deal with.
1732 if( in_vehicle ) {
1733 if( const optional_vpart_position vp = veh_at( u->pos() ) ) {
1734 if( vp->is_inside() ) {
1735 inside_vehicle = true;
1736 }
1737 }
1738 }
1739 player_in_field( *u );
1740 }
1741 }
1742
1743 field &curfield = get_field( critter.pos() );
1744 for( auto &field_entry_it : curfield ) {
1745 field_entry &cur_field_entry = field_entry_it.second;
1746 if( !cur_field_entry.is_field_alive() ) {
1747 continue;
1748 }
1749 const field_type_id cur_field_id = cur_field_entry.get_field_type();
1750
1751 for( const auto &fe : cur_field_entry.get_intensity_level().field_effects ) {
1752 if( in_vehicle && fe.immune_in_vehicle ) {
1753 continue;
1754 }
1755 if( inside_vehicle && fe.immune_inside_vehicle ) {
1756 continue;
1757 }
1758 if( !inside_vehicle && fe.immune_outside_vehicle ) {
1759 continue;
1760 }
1761 if( in_vehicle && !one_in( fe.chance_in_vehicle ) ) {
1762 continue;
1763 }
1764 if( inside_vehicle && !one_in( fe.chance_inside_vehicle ) ) {
1765 continue;
1766 }
1767 if( !inside_vehicle && !one_in( fe.chance_outside_vehicle ) ) {
1768 continue;
1769 }
1770
1771 const effect field_fx = fe.get_effect();
1772 if( critter.is_immune_field( cur_field_id ) || critter.is_immune_effect( field_fx.get_id() ) ) {
1773 continue;
1774 }
1775 bool effect_added = false;
1776 if( fe.is_environmental ) {
1777 effect_added = critter.add_env_effect( fe.id, fe.bp.id(), fe.intensity, fe.get_duration() );
1778 } else {
1779 effect_added = true;
1780 critter.add_effect( field_fx.get_id(), field_fx.get_duration(), field_fx.get_bp(),
1781 field_fx.is_permanent(), field_fx.get_intensity() );
1782 }
1783 if( effect_added ) {
1784 critter.add_msg_player_or_npc( fe.env_message_type, fe.get_message(), fe.get_message_npc() );
1785 }
1786 if( cur_field_id->decrease_intensity_on_contact ) {
1787 mod_field_intensity( critter.pos(), cur_field_id, -1 );
1788 }
1789 }
1790 }
1791 }
1792
monster_in_field(monster & z)1793 void map::monster_in_field( monster &z )
1794 {
1795 if( z.digging() ) {
1796 // Digging monsters are immune to fields
1797 return;
1798 }
1799 if( veh_at( z.pos() ) ) {
1800 // FIXME: Immune when in a vehicle for now.
1801 return;
1802 }
1803 field &curfield = get_field( z.pos() );
1804
1805 int dam = 0;
1806 // Iterate through all field effects on this tile.
1807 // Do not remove the field with remove_field, instead set it's intensity to 0. It will be removed
1808 // later by the field processing, which will also adjust field_count accordingly.
1809 for( auto &field_list_it : curfield ) {
1810 field_entry &cur = field_list_it.second;
1811 if( !cur.is_field_alive() ) {
1812 continue;
1813 }
1814 const field_type_id cur_field_type = cur.get_field_type();
1815 if( cur_field_type == fd_web ) {
1816 if( !z.has_flag( MF_WEBWALK ) ) {
1817 z.add_effect( effect_webbed, 1_turns, true, cur.get_field_intensity() );
1818 cur.set_field_intensity( 0 );
1819 }
1820 }
1821 if( cur_field_type == fd_acid ) {
1822 if( !z.flies() ) {
1823 const int d = rng( cur.get_field_intensity(), cur.get_field_intensity() * 3 );
1824 z.deal_damage( nullptr, bodypart_id( "torso" ), damage_instance( damage_type::ACID, d ) );
1825 z.check_dead_state();
1826 }
1827
1828 }
1829 if( cur_field_type == fd_sap ) {
1830 z.moves -= cur.get_field_intensity() * 5;
1831 mod_field_intensity( z.pos(), cur.get_field_type(), -1 );
1832 }
1833 if( cur_field_type == fd_sludge ) {
1834 if( !z.digs() && !z.flies() &&
1835 !z.has_flag( MF_SLUDGEPROOF ) ) {
1836 z.moves -= cur.get_field_intensity() * 300;
1837 cur.set_field_intensity( 0 );
1838 }
1839 }
1840 if( cur_field_type == fd_fire ) {
1841 // TODO: MATERIALS Use fire resistance
1842 if( z.has_flag( MF_FIREPROOF ) || z.has_flag( MF_FIREY ) ) {
1843 return;
1844 }
1845 // TODO: Replace the section below with proper json values
1846 if( z.made_of_any( Creature::cmat_flesh ) ) {
1847 dam += 3;
1848 }
1849 if( z.made_of( material_id( "veggy" ) ) ) {
1850 dam += 12;
1851 }
1852 if( z.made_of( phase_id::LIQUID ) || z.made_of_any( Creature::cmat_flammable ) ) {
1853 dam += 20;
1854 }
1855 if( z.made_of_any( Creature::cmat_flameres ) ) {
1856 dam += -20;
1857 }
1858 if( z.flies() ) {
1859 dam -= 15;
1860 }
1861 dam -= z.get_armor_type( damage_type::HEAT, bodypart_id( "torso" ) );
1862
1863 if( cur.get_field_intensity() == 1 ) {
1864 dam += rng( 2, 6 );
1865 } else if( cur.get_field_intensity() == 2 ) {
1866 dam += rng( 6, 12 );
1867 if( !z.flies() ) {
1868 z.moves -= 20;
1869 if( dam > 0 ) {
1870 z.add_effect( effect_onfire, 1_turns * rng( dam / 2, dam * 2 ) );
1871 }
1872 }
1873 } else if( cur.get_field_intensity() == 3 ) {
1874 dam += rng( 10, 20 );
1875 if( !z.flies() || one_in( 3 ) ) {
1876 z.moves -= 40;
1877 if( dam > 0 ) {
1878 z.add_effect( effect_onfire, 1_turns * rng( dam / 2, dam * 2 ) );
1879 }
1880 }
1881 }
1882 }
1883 if( cur_field_type == fd_smoke ) {
1884 if( !z.has_flag( MF_NO_BREATHE ) ) {
1885 if( cur.get_field_intensity() == 3 ) {
1886 z.moves -= rng( 10, 20 );
1887 }
1888 // Plants suffer from smoke even worse
1889 if( z.made_of( material_id( "veggy" ) ) ) {
1890 z.moves -= rng( 1, cur.get_field_intensity() * 12 );
1891 }
1892 }
1893
1894 }
1895 if( cur_field_type == fd_tear_gas ) {
1896 if( z.made_of_any( Creature::cmat_fleshnveg ) && !z.has_flag( MF_NO_BREATHE ) ) {
1897 if( cur.get_field_intensity() == 3 ) {
1898 z.add_effect( effect_stunned, rng( 1_minutes, 2_minutes ) );
1899 dam += rng( 4, 10 );
1900 } else if( cur.get_field_intensity() == 2 ) {
1901 z.add_effect( effect_stunned, rng( 5_turns, 10_turns ) );
1902 dam += rng( 2, 5 );
1903 } else {
1904 z.add_effect( effect_stunned, rng( 1_turns, 5_turns ) );
1905 }
1906 if( z.made_of( material_id( "veggy" ) ) ) {
1907 z.moves -= rng( cur.get_field_intensity() * 5, cur.get_field_intensity() * 12 );
1908 dam += cur.get_field_intensity() * rng( 8, 14 );
1909 }
1910 if( z.has_flag( MF_SEES ) ) {
1911 z.add_effect( effect_blind, cur.get_field_intensity() * 8_turns );
1912 }
1913 }
1914
1915 }
1916 if( cur_field_type == fd_relax_gas ) {
1917 if( z.made_of_any( Creature::cmat_fleshnveg ) && !z.has_flag( MF_NO_BREATHE ) ) {
1918 z.add_effect( effect_stunned, rng( cur.get_field_intensity() * 4_turns,
1919 cur.get_field_intensity() * 8_turns ) );
1920 }
1921 }
1922 if( cur_field_type == fd_dazzling ) {
1923 if( z.has_flag( MF_SEES ) && !z.has_flag( MF_ELECTRONIC ) ) {
1924 z.add_effect( effect_blind, cur.get_field_intensity() * 12_turns );
1925 z.add_effect( effect_stunned, cur.get_field_intensity() * rng( 5_turns, 12_turns ) );
1926 }
1927
1928 }
1929 if( cur_field_type == fd_toxic_gas ) {
1930 if( !z.has_flag( MF_NO_BREATHE ) ) {
1931 dam += cur.get_field_intensity();
1932 z.moves -= cur.get_field_intensity();
1933 }
1934
1935 }
1936 if( cur_field_type == fd_nuke_gas ) {
1937 if( !z.has_flag( MF_NO_BREATHE ) ) {
1938 if( cur.get_field_intensity() == 3 ) {
1939 z.moves -= rng( 60, 120 );
1940 dam += rng( 30, 50 );
1941 } else if( cur.get_field_intensity() == 2 ) {
1942 z.moves -= rng( 20, 50 );
1943 dam += rng( 10, 25 );
1944 } else {
1945 z.moves -= rng( 0, 15 );
1946 dam += rng( 0, 12 );
1947 }
1948 if( z.made_of( material_id( "veggy" ) ) ) {
1949 z.moves -= rng( cur.get_field_intensity() * 5, cur.get_field_intensity() * 12 );
1950 dam *= cur.get_field_intensity();
1951 }
1952 }
1953
1954 }
1955 if( cur_field_type == fd_flame_burst ) {
1956 // TODO: MATERIALS Use fire resistance
1957 if( z.has_flag( MF_FIREPROOF ) || z.has_flag( MF_FIREY ) ) {
1958 return;
1959 }
1960 if( z.made_of_any( Creature::cmat_flesh ) ) {
1961 dam += 3;
1962 }
1963 if( z.made_of( material_id( "veggy" ) ) ) {
1964 dam += 12;
1965 }
1966 if( z.made_of( phase_id::LIQUID ) || z.made_of_any( Creature::cmat_flammable ) ) {
1967 dam += 50;
1968 }
1969 if( z.made_of_any( Creature::cmat_flameres ) ) {
1970 dam += -25;
1971 }
1972 dam += rng( 0, 8 );
1973 z.moves -= 20;
1974 }
1975 if( cur_field_type == fd_electricity ) {
1976 // We don't want to increase dam, but deal a separate hit so that it can apply effects
1977 z.deal_damage( nullptr, bodypart_id( "torso" ),
1978 damage_instance( damage_type::ELECTRIC, rng( 1, cur.get_field_intensity() * 3 ) ) );
1979 }
1980 if( cur_field_type == fd_fatigue ) {
1981 if( rng( 0, 2 ) < cur.get_field_intensity() ) {
1982 dam += cur.get_field_intensity();
1983 teleport::teleport( z );
1984 }
1985 }
1986 if( cur_field_type == fd_incendiary ) {
1987 // TODO: MATERIALS Use fire resistance
1988 if( z.has_flag( MF_FIREPROOF ) || z.has_flag( MF_FIREY ) ) {
1989 return;
1990 }
1991 if( z.made_of_any( Creature::cmat_flesh ) ) {
1992 dam += 3;
1993 }
1994 if( z.made_of( material_id( "veggy" ) ) ) {
1995 dam += 12;
1996 }
1997 if( z.made_of( phase_id::LIQUID ) || z.made_of_any( Creature::cmat_flammable ) ) {
1998 dam += 20;
1999 }
2000 if( z.made_of_any( Creature::cmat_flameres ) ) {
2001 dam += -5;
2002 }
2003
2004 if( cur.get_field_intensity() == 1 ) {
2005 dam += rng( 2, 6 );
2006 } else if( cur.get_field_intensity() == 2 ) {
2007 dam += rng( 6, 12 );
2008 z.moves -= 20;
2009 if( !z.made_of( phase_id::LIQUID ) && !z.made_of_any( Creature::cmat_flameres ) ) {
2010 z.add_effect( effect_onfire, rng( 8_turns, 12_turns ) );
2011 }
2012 } else if( cur.get_field_intensity() == 3 ) {
2013 dam += rng( 10, 20 );
2014 z.moves -= 40;
2015 if( !z.made_of( phase_id::LIQUID ) && !z.made_of_any( Creature::cmat_flameres ) ) {
2016 z.add_effect( effect_onfire, rng( 12_turns, 16_turns ) );
2017 }
2018 }
2019 }
2020 if( cur_field_type == fd_fungal_haze ) {
2021 if( !z.type->in_species( species_FUNGUS ) &&
2022 !z.type->has_flag( MF_NO_BREATHE ) &&
2023 !z.make_fungus() ) {
2024 // Don't insta-kill jabberwocks, that's silly
2025 const int intensity = cur.get_field_intensity();
2026 z.moves -= rng( 10 * intensity, 30 * intensity );
2027 dam += rng( 0, 10 * intensity );
2028 }
2029 }
2030 if( cur_field_type == fd_fungicidal_gas ) {
2031 if( z.type->in_species( species_FUNGUS ) ) {
2032 const int intensity = cur.get_field_intensity();
2033 z.moves -= rng( 10 * intensity, 30 * intensity );
2034 dam += rng( 4, 7 * intensity );
2035 }
2036 }
2037 if( cur_field_type == fd_insecticidal_gas ) {
2038 if( z.made_of( material_id( "iflesh" ) ) && !z.has_flag( MF_INSECTICIDEPROOF ) ) {
2039 const int intensity = cur.get_field_intensity();
2040 z.moves -= rng( 10 * intensity, 30 * intensity );
2041 dam += rng( 4, 7 * intensity );
2042 }
2043 }
2044 }
2045
2046 if( dam > 0 ) {
2047 z.apply_damage( nullptr, bodypart_id( "torso" ), dam, true );
2048 z.check_dead_state();
2049 }
2050 }
2051
get_wind_blockers(const int & winddirection,const tripoint & pos)2052 std::tuple<maptile, maptile, maptile> map::get_wind_blockers( const int &winddirection,
2053 const tripoint &pos )
2054 {
2055 static const std::array<std::pair<int, std::tuple< point, point, point >>, 9> outputs = {{
2056 { 330, std::make_tuple( point_east, point_north_east, point_south_east ) },
2057 { 301, std::make_tuple( point_south_east, point_east, point_south ) },
2058 { 240, std::make_tuple( point_south, point_south_west, point_south_east ) },
2059 { 211, std::make_tuple( point_south_west, point_west, point_south ) },
2060 { 150, std::make_tuple( point_west, point_north_west, point_south_west ) },
2061 { 121, std::make_tuple( point_north_west, point_north, point_west ) },
2062 { 60, std::make_tuple( point_north, point_north_west, point_north_east ) },
2063 { 31, std::make_tuple( point_north_east, point_east, point_north ) },
2064 { 0, std::make_tuple( point_east, point_north_east, point_south_east ) }
2065 }
2066 };
2067
2068 tripoint removepoint;
2069 tripoint removepoint2;
2070 tripoint removepoint3;
2071 for( const std::pair<int, std::tuple< point, point, point >> &val : outputs ) {
2072 if( winddirection >= val.first ) {
2073 removepoint = pos + std::get<0>( val.second );
2074 removepoint2 = pos + std::get<1>( val.second );
2075 removepoint3 = pos + std::get<2>( val.second );
2076 break;
2077 }
2078 }
2079
2080 const maptile remove_tile = maptile_at( removepoint );
2081 const maptile remove_tile2 = maptile_at( removepoint2 );
2082 const maptile remove_tile3 = maptile_at( removepoint3 );
2083 return std::make_tuple( remove_tile, remove_tile2, remove_tile3 );
2084 }
2085
emit_field(const tripoint & pos,const emit_id & src,float mul)2086 void map::emit_field( const tripoint &pos, const emit_id &src, float mul )
2087 {
2088 if( !src.is_valid() ) {
2089 return;
2090 }
2091
2092 const float chance = src->chance() * mul;
2093 if( src.is_valid() && x_in_y( chance, 100 ) ) {
2094 const int qty = chance > 100.0f ? roll_remainder( src->qty() * chance / 100.0f ) : src->qty();
2095 propagate_field( pos, src->field(), qty, src->intensity() );
2096 }
2097 }
2098
propagate_field(const tripoint & center,const field_type_id & type,int amount,int max_intensity)2099 void map::propagate_field( const tripoint ¢er, const field_type_id &type, int amount,
2100 int max_intensity )
2101 {
2102 using gas_blast = std::pair<float, tripoint>;
2103 std::priority_queue<gas_blast, std::vector<gas_blast>, pair_greater_cmp_first> open;
2104 std::set<tripoint> closed;
2105 open.push( { 0.0f, center } );
2106
2107 const bool not_gas = type.obj().phase != phase_id::GAS;
2108
2109 while( amount > 0 && !open.empty() ) {
2110 if( closed.count( open.top().second ) ) {
2111 open.pop();
2112 continue;
2113 }
2114
2115 // All points with equal gas intensity should propagate at the same time
2116 std::list<gas_blast> gas_front;
2117 gas_front.push_back( open.top() );
2118 const int cur_intensity = get_field_intensity( open.top().second, type );
2119 open.pop();
2120 while( !open.empty() && get_field_intensity( open.top().second, type ) == cur_intensity ) {
2121 if( closed.count( open.top().second ) == 0 ) {
2122 gas_front.push_back( open.top() );
2123 }
2124
2125 open.pop();
2126 }
2127
2128 int increment = std::max<int>( 1, amount / gas_front.size() );
2129
2130 while( !gas_front.empty() ) {
2131 gas_blast gp = random_entry_removed( gas_front );
2132 closed.insert( gp.second );
2133 const int cur_intensity = get_field_intensity( gp.second, type );
2134 if( cur_intensity < max_intensity ) {
2135 const int bonus = std::min( max_intensity - cur_intensity, increment );
2136 mod_field_intensity( gp.second, type, bonus );
2137 amount -= bonus;
2138 } else {
2139 amount--;
2140 }
2141
2142 if( amount <= 0 ) {
2143 return;
2144 }
2145
2146 static const std::array<int, 8> x_offset = {{ -1, 1, 0, 0, 1, -1, -1, 1 }};
2147 static const std::array<int, 8> y_offset = {{ 0, 0, -1, 1, -1, 1, -1, 1 }};
2148 for( size_t i = 0; i < 8; i++ ) {
2149 tripoint pt = gp.second + point( x_offset[ i ], y_offset[ i ] );
2150 if( closed.count( pt ) > 0 ) {
2151 continue;
2152 }
2153
2154 if( impassable( pt ) && ( not_gas || !has_flag( TFLAG_PERMEABLE, pt ) ) ) {
2155 closed.insert( pt );
2156 continue;
2157 }
2158
2159 open.push( { static_cast<float>( rl_dist( center, pt ) ), pt } );
2160 }
2161 }
2162 }
2163 }
2164
processors_for_type(const field_type & ft)2165 std::vector<FieldProcessorPtr> map_field_processing::processors_for_type( const field_type &ft )
2166 {
2167 std::vector<FieldProcessorPtr> processors;
2168
2169 const bool intensity_upgrade_chance = std::any_of( ft.intensity_levels.begin(),
2170 ft.intensity_levels.end(),
2171 []( const field_intensity_level & elem ) {
2172 return elem.intensity_upgrade_chance > 0;
2173 } );
2174 const bool extra_radiation = std::any_of( ft.intensity_levels.begin(), ft.intensity_levels.end(),
2175 []( const field_intensity_level & elem ) {
2176 return elem.extra_radiation_max > 0;
2177 } );
2178
2179 const bool has_monster_spawn_chance = std::any_of( ft.intensity_levels.begin(),
2180 ft.intensity_levels.end(),
2181 []( const field_intensity_level & elem ) {
2182 return elem.monster_spawn_chance > 0 && elem.monster_spawn_count > 0;
2183 } );
2184
2185 if( intensity_upgrade_chance ) {
2186 processors.push_back( &field_processor_upgrade_intensity );
2187 }
2188 if( ft.underwater_age_speedup != 0_turns ) {
2189 processors.push_back( &field_processor_underwater_dissipation );
2190 }
2191 if( ft.apply_slime_factor > 0 ) {
2192 processors.push_back( &field_processor_apply_slime );
2193 }
2194 if( ft.gas_can_spread() ) {
2195 processors.push_back( &field_processor_spread_gas );
2196 }
2197 // Apply radiation
2198 if( extra_radiation ) {
2199 processors.push_back( &field_processor_extra_radiation );
2200 }
2201 // Apply wandering fields from vents
2202 if( ft.wandering_field ) {
2203 processors.push_back( &field_processor_wandering_field );
2204 }
2205 if( has_monster_spawn_chance ) {
2206 processors.push_back( &field_processor_monster_spawn );
2207 }
2208 // legacy
2209 if( ft.legacy_make_rubble ) {
2210 processors.push_back( &field_processor_make_rubble );
2211 }
2212
2213 // Per-type processors:
2214 if( ft.id == fd_acid ) {
2215 processors.push_back( &field_processor_fd_acid );
2216 }
2217 if( ft.id == fd_extinguisher ) {
2218 processors.push_back( &field_processor_fd_extinguisher );
2219 }
2220 if( ft.id == fd_fire ) {
2221 processors.push_back( &field_processor_fd_fire );
2222 }
2223 if( ft.id == fd_fungal_haze ) {
2224 processors.push_back( &field_processor_fd_fungal_haze );
2225 }
2226 if( ft.id == fd_fire_vent ) {
2227 processors.push_back( &field_processor_fd_fire_vent );
2228 }
2229 if( ft.id == fd_flame_burst ) {
2230 processors.push_back( &field_processor_fd_flame_burst );
2231 }
2232 if( ft.id == fd_electricity ) {
2233 processors.push_back( &field_processor_fd_electricity );
2234 }
2235 if( ft.id == fd_push_items ) {
2236 processors.push_back( &field_processor_fd_push_items );
2237 }
2238 if( ft.id == fd_shock_vent ) {
2239 processors.push_back( &field_processor_fd_shock_vent );
2240 }
2241 if( ft.id == fd_acid_vent ) {
2242 processors.push_back( &field_processor_fd_acid_vent );
2243 }
2244 if( ft.id == fd_bees ) {
2245 processors.push_back( &field_processor_fd_bees );
2246 }
2247 if( ft.id == fd_incendiary ) {
2248 processors.push_back( &field_processor_fd_incendiary );
2249 }
2250 if( ft.id == fd_fungicidal_gas ) {
2251 processors.push_back( &field_processor_fd_fungicidal_gas );
2252 }
2253
2254 return processors;
2255 }
2256