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