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 &center, 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