1 #include "timed_event.h"
2 
3 #include <array>
4 #include <memory>
5 #include <new>
6 #include <string>
7 
8 #include "avatar.h"
9 #include "avatar_action.h"
10 #include "character.h"
11 #include "debug.h"
12 #include "enums.h"
13 #include "event.h"
14 #include "event_bus.h"
15 #include "game.h"
16 #include "game_constants.h"
17 #include "line.h"
18 #include "map.h"
19 #include "map_iterator.h"
20 #include "mapdata.h"
21 #include "memorial_logger.h"
22 #include "messages.h"
23 #include "monster.h"
24 #include "morale_types.h"
25 #include "optional.h"
26 #include "options.h"
27 #include "rng.h"
28 #include "sounds.h"
29 #include "translations.h"
30 #include "type_id.h"
31 
32 static const itype_id itype_petrified_eye( "petrified_eye" );
33 
34 static const mtype_id mon_amigara_horror( "mon_amigara_horror" );
35 static const mtype_id mon_copbot( "mon_copbot" );
36 static const mtype_id mon_dark_wyrm( "mon_dark_wyrm" );
37 static const mtype_id mon_dermatik( "mon_dermatik" );
38 static const mtype_id mon_eyebot( "mon_eyebot" );
39 static const mtype_id mon_riotbot( "mon_riotbot" );
40 static const mtype_id mon_sewer_snake( "mon_sewer_snake" );
41 static const mtype_id mon_spider_cellar_giant( "mon_spider_cellar_giant" );
42 static const mtype_id mon_spider_widow_giant( "mon_spider_widow_giant" );
43 
timed_event(timed_event_type e_t,const time_point & w,int f_id,tripoint p)44 timed_event::timed_event( timed_event_type e_t, const time_point &w, int f_id, tripoint p )
45     : type( e_t )
46     , when( w )
47     , faction_id( f_id )
48     , map_point( p )
49 {
50 }
51 
actualize()52 void timed_event::actualize()
53 {
54     avatar &player_character = get_avatar();
55     map &here = get_map();
56     switch( type ) {
57         case timed_event_type::HELP:
58             debugmsg( "Currently disabled while NPC and monster factions are being rewritten." );
59             break;
60 
61         case timed_event_type::ROBOT_ATTACK: {
62             const tripoint u_pos = player_character.global_sm_location();
63             if( rl_dist( u_pos, map_point ) <= 4 ) {
64                 const mtype_id &robot_type = one_in( 2 ) ? mon_copbot : mon_riotbot;
65 
66                 get_event_bus().send<event_type::becomes_wanted>( player_character.getID() );
67                 point rob( u_pos.x > map_point.x ? 0 - SEEX * 2 : SEEX * 4,
68                            u_pos.y > map_point.y ? 0 - SEEY * 2 : SEEY * 4 );
69                 g->place_critter_at( robot_type, tripoint( rob, player_character.posz() ) );
70             }
71         }
72         break;
73 
74         case timed_event_type::SPAWN_WYRMS: {
75             if( here.get_abs_sub().z >= 0 ) {
76                 return;
77             }
78             get_memorial().add(
79                 pgettext( "memorial_male", "Drew the attention of more dark wyrms!" ),
80                 pgettext( "memorial_female", "Drew the attention of more dark wyrms!" ) );
81             int num_wyrms = rng( 1, 4 );
82             for( int i = 0; i < num_wyrms; i++ ) {
83                 if( monster *const mon = g->place_critter_around( mon_dark_wyrm, player_character.pos(), 2 ) ) {
84                     here.ter_set( mon->pos(), t_rock_floor );
85                 }
86             }
87             // You could drop the flag, you know.
88             if( player_character.has_amount( itype_petrified_eye, 1 ) ) {
89                 sounds::sound( player_character.pos(), 60, sounds::sound_t::alert, _( "a tortured scream!" ), false,
90                                "shout",
91                                "scream_tortured" );
92                 if( !player_character.is_deaf() ) {
93                     add_msg( _( "The eye you're carrying lets out a tortured scream!" ) );
94                     player_character.add_morale( MORALE_SCREAM, -15, 0, 30_minutes, 30_seconds );
95                 }
96             }
97             // They just keep coming!
98             if( !one_in( 25 ) ) {
99                 get_timed_events().add( timed_event_type::SPAWN_WYRMS,
100                                         calendar::turn + rng( 1_minutes, 3_minutes ) );
101             }
102         }
103         break;
104 
105         case timed_event_type::AMIGARA: {
106             get_event_bus().send<event_type::angers_amigara_horrors>();
107             int num_horrors = rng( 3, 5 );
108             cata::optional<tripoint> fault_point;
109             bool horizontal = false;
110             for( const tripoint &p : here.points_on_zlevel() ) {
111                 if( here.ter( p ) == t_fault ) {
112                     fault_point = p;
113                     horizontal = here.ter( p + tripoint_east ) == t_fault || here.ter( p + tripoint_west ) == t_fault;
114                     break;
115                 }
116             }
117             for( int i = 0; fault_point && i < num_horrors; i++ ) {
118                 for( int tries = 0; tries < 10; ++tries ) {
119                     tripoint monp = player_character.pos();
120                     if( horizontal ) {
121                         monp.x = rng( fault_point->x, fault_point->x + 2 * SEEX - 8 );
122                         for( int n = -1; n <= 1; n++ ) {
123                             if( here.ter( point( monp.x, fault_point->y + n ) ) == t_rock_floor ) {
124                                 monp.y = fault_point->y + n;
125                             }
126                         }
127                     } else {
128                         // Vertical fault
129                         monp.y = rng( fault_point->y, fault_point->y + 2 * SEEY - 8 );
130                         for( int n = -1; n <= 1; n++ ) {
131                             if( here.ter( point( fault_point->x + n, monp.y ) ) == t_rock_floor ) {
132                                 monp.x = fault_point->x + n;
133                             }
134                         }
135                     }
136                     if( g->place_critter_at( mon_amigara_horror, monp ) ) {
137                         break;
138                     }
139                 }
140             }
141         }
142         break;
143 
144         case timed_event_type::ROOTS_DIE:
145             get_event_bus().send<event_type::destroys_triffid_grove>();
146             for( const tripoint &p : here.points_on_zlevel() ) {
147                 if( here.ter( p ) == t_root_wall && one_in( 3 ) ) {
148                     here.ter_set( p, t_underbrush );
149                 }
150             }
151             break;
152 
153         case timed_event_type::TEMPLE_OPEN: {
154             get_event_bus().send<event_type::opens_temple>();
155             bool saw_grate = false;
156             for( const tripoint &p : here.points_on_zlevel() ) {
157                 if( here.ter( p ) == t_grate ) {
158                     here.ter_set( p, t_stairs_down );
159                     if( !saw_grate && player_character.sees( p ) ) {
160                         saw_grate = true;
161                     }
162                 }
163             }
164             if( saw_grate ) {
165                 add_msg( _( "The nearby grates open to reveal a staircase!" ) );
166             }
167         }
168         break;
169 
170         case timed_event_type::TEMPLE_FLOOD: {
171             bool flooded = false;
172 
173             ter_id flood_buf[MAPSIZE_X][MAPSIZE_Y];
174             for( const tripoint &p : here.points_on_zlevel() ) {
175                 flood_buf[p.x][p.y] = here.ter( p );
176             }
177             for( const tripoint &p : here.points_on_zlevel() ) {
178                 if( here.ter( p ) == t_water_sh ) {
179                     bool deepen = false;
180                     for( const tripoint &w : points_in_radius( p, 1 ) ) {
181                         if( here.ter( w ) == t_water_dp ) {
182                             deepen = true;
183                             break;
184                         }
185                     }
186                     if( deepen ) {
187                         flood_buf[p.x][p.y] = t_water_dp;
188                         flooded = true;
189                     }
190                 } else if( here.ter( p ) == t_rock_floor ) {
191                     bool flood = false;
192                     for( const tripoint &w : points_in_radius( p, 1 ) ) {
193                         if( here.ter( w ) == t_water_dp || here.ter( w ) == t_water_sh ) {
194                             flood = true;
195                             break;
196                         }
197                     }
198                     if( flood ) {
199                         flood_buf[p.x][p.y] = t_water_sh;
200                         flooded = true;
201                     }
202                 }
203             }
204             if( !flooded ) {
205                 // We finished flooding the entire chamber!
206                 return;
207             }
208             // Check if we should print a message
209             if( flood_buf[player_character.posx()][player_character.posy()] != here.ter(
210                     player_character.pos() ) ) {
211                 if( flood_buf[player_character.posx()][player_character.posy()] == t_water_sh ) {
212                     add_msg( m_warning, _( "Water quickly floods up to your knees." ) );
213                     get_memorial().add(
214                         pgettext( "memorial_male", "Water level reached knees." ),
215                         pgettext( "memorial_female", "Water level reached knees." ) );
216                 } else {
217                     // Must be deep water!
218                     add_msg( m_warning, _( "Water fills nearly to the ceiling!" ) );
219                     get_memorial().add(
220                         pgettext( "memorial_male", "Water level reached the ceiling." ),
221                         pgettext( "memorial_female", "Water level reached the ceiling." ) );
222                     avatar_action::swim( here, player_character, player_character.pos() );
223                 }
224             }
225             // flood_buf is filled with correct tiles; now copy them back to here
226             for( const tripoint &p : here.points_on_zlevel() ) {
227                 here.ter_set( p, flood_buf[p.x][p.y] );
228             }
229             get_timed_events().add( timed_event_type::TEMPLE_FLOOD,
230                                     calendar::turn + rng( 2_turns, 3_turns ) );
231         }
232         break;
233 
234         case timed_event_type::TEMPLE_SPAWN: {
235             static const std::array<mtype_id, 4> temple_monsters = { {
236                     mon_sewer_snake, mon_dermatik, mon_spider_widow_giant, mon_spider_cellar_giant
237                 }
238             };
239             const mtype_id &montype = random_entry( temple_monsters );
240             g->place_critter_around( montype, player_character.pos(), 2 );
241         }
242         break;
243 
244         default:
245             // Nothing happens for other events
246             break;
247     }
248 }
249 
per_turn()250 void timed_event::per_turn()
251 {
252     Character &player_character = get_player_character();
253     map &here = get_map();
254     switch( type ) {
255         case timed_event_type::WANTED: {
256             // About once every 5 minutes. Suppress in classic zombie mode.
257             if( here.get_abs_sub().z >= 0 && one_in( 50 ) && !get_option<bool>( "DISABLE_ROBOT_RESPONSE" ) ) {
258                 point place = here.random_outdoor_tile();
259                 if( place.x == -1 && place.y == -1 ) {
260                     // We're safely indoors!
261                     return;
262                 }
263                 g->place_critter_at( mon_eyebot, tripoint( place, player_character.posz() ) );
264                 if( player_character.sees( tripoint( place, player_character.posz() ) ) ) {
265                     add_msg( m_warning, _( "An eyebot swoops down nearby!" ) );
266                 }
267                 // One eyebot per trigger is enough, really
268                 when = calendar::turn;
269             }
270         }
271         break;
272 
273         case timed_event_type::SPAWN_WYRMS:
274             if( here.get_abs_sub().z >= 0 ) {
275                 when -= 1_turns;
276                 return;
277             }
278             if( calendar::once_every( 3_turns ) && !player_character.is_deaf() ) {
279                 add_msg( m_warning, _( "You hear screeches from the rock above and around you!" ) );
280             }
281             break;
282 
283         case timed_event_type::AMIGARA:
284             add_msg( m_warning, _( "The entire cavern shakes!" ) );
285             break;
286 
287         case timed_event_type::TEMPLE_OPEN:
288             add_msg( m_warning, _( "The earth rumbles." ) );
289             break;
290 
291         default:
292             // Nothing happens for other events
293             break;
294     }
295 }
296 
process()297 void timed_event_manager::process()
298 {
299     for( auto it = events.begin(); it != events.end(); ) {
300         it->per_turn();
301         if( it->when <= calendar::turn ) {
302             it->actualize();
303             it = events.erase( it );
304         } else {
305             it++;
306         }
307     }
308 }
309 
add(const timed_event_type type,const time_point & when,const int faction_id)310 void timed_event_manager::add( const timed_event_type type, const time_point &when,
311                                const int faction_id )
312 {
313     add( type, when, faction_id, get_player_character().global_sm_location() );
314 }
315 
add(const timed_event_type type,const time_point & when,const int faction_id,const tripoint & where)316 void timed_event_manager::add( const timed_event_type type, const time_point &when,
317                                const int faction_id,
318                                const tripoint &where )
319 {
320     events.emplace_back( type, when, faction_id, where );
321 }
322 
queued(const timed_event_type type) const323 bool timed_event_manager::queued( const timed_event_type type ) const
324 {
325     return const_cast<timed_event_manager &>( *this ).get( type ) != nullptr;
326 }
327 
get(const timed_event_type type)328 timed_event *timed_event_manager::get( const timed_event_type type )
329 {
330     for( auto &e : events ) {
331         if( e.type == type ) {
332             return &e;
333         }
334     }
335     return nullptr;
336 }
337