1 /*
2
3 Copyright (C) 2015-2018 Night Dive Studios, LLC.
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 */
19 /*
20 * $Source: r:/prj/cit/src/RCS/schedule.c $
21 * $Revision: 1.60 $
22 * $Author: xemu $
23 * $Date: 1994/11/22 15:39:20 $
24 *
25 *
26 */
27
28 #include "map.h"
29 #include "player.h"
30 #include "schedule.h"
31 #include "grenades.h"
32 #include "wares.h"
33 #include "objsim.h"
34 #include "objclass.h"
35 #include "otrip.h"
36 #include "faketime.h"
37 #include "trigger.h"
38 #include "objwpn.h"
39 #include "combat.h"
40 #include "objgame.h"
41 #include "objuse.h"
42 #include "gamesys.h"
43 #include "ai.h"
44 #include "frparams.h"
45 #include "damage.h"
46 #include "mainloop.h"
47 #include "colors.h"
48 #include "doorparm.h"
49 #include "mfdext.h"
50
51 #include "musicai.h" // for the explosion
52 #include "sfxlist.h"
53 #include "effect.h"
54 #include "objprop.h"
55
56 #include "frflags.h"
57 #include "frprotox.h"
58
59 // this will need to be initialized.
60 height_semaphor h_sems[NUM_HEIGHT_SEMAPHORS];
61
62 // ---------
63 // INTERNALS
64 // ---------
65 int expand_tstamp(ushort s);
66 int compare_tstamps(ushort t1, ushort t2);
67 int compare_events(void *e1, void *e2);
68
69 uchar register_h_event(uchar x, uchar y, uchar floor, char *sem, char *key, uchar no_sfx);
70 void unregister_h_event(char sem);
71
72 void null_event_handler(Schedule *s, SchedEvent *ev);
73 void trap_event_handler(Schedule *s, SchedEvent *ev);
74 void height_event_handler(Schedule *s, SchedEvent *ev);
75 void door_event_handler(Schedule *s, SchedEvent *ev);
76 void grenade_event_handler(Schedule *s, SchedEvent *ev);
77 void explosion_event_handler(Schedule *s, SchedEvent *ev);
78 void exposure_event_handler(Schedule *s, SchedEvent *ev);
79 void light_event_handler(Schedule *s, SchedEvent *ev);
80 void bark_event_handler(Schedule *s, SchedEvent *ev);
81 void email_event_handler(Schedule *s, SchedEvent *ev);
82
83 void reset_schedules(void);
84
85 // comparison function.
86 // 8/27/94 THIS FIX FOR WRAPPING BUG MAKES THE COMPARE FUNCTION MUCH LESS GENERAL.
87 // I.e. it relies on the fact that timestamps are in terms of TICKS2TSTAMP(player_struct.game_time)
88 // in the future, this should be fixed by adding more bits to the timestamp so it doesn't wrap, and
89 // then you can have schedule that don't go on gametime.
90
91 #define MAX_USHORT (0xFFFF)
92
expand_tstamp(ushort s)93 int expand_tstamp(ushort s) {
94 int gametime = TICKS2TSTAMP(player_struct.game_time);
95 int stamp = s;
96 if (stamp <= gametime - (MAX_USHORT / 2))
97 stamp += MAX_USHORT;
98 else if (stamp >= gametime + (MAX_USHORT / 2))
99 stamp -= MAX_USHORT;
100 return stamp;
101 }
102
compare_tstamps(ushort t1,ushort t2)103 int compare_tstamps(ushort t1, ushort t2) { return expand_tstamp(t1) - expand_tstamp(t2); }
104
compare_events(void * e1,void * e2)105 int compare_events(void *e1, void *e2) {
106 return compare_tstamps(((SchedEvent *)e1)->timestamp, ((SchedEvent *)e2)->timestamp);
107 }
108
109 // -----------------
110 // SUPPORT FUNCTIONS
111 // -----------------
112
113 void stop_terrain_elevator_sound(short sem);
114
register_h_event(uchar x,uchar y,uchar floor,char * sem,char * key,uchar no_sfx)115 uchar register_h_event(uchar x, uchar y, uchar floor, char *sem, char *key, uchar no_sfx) {
116 int i, fr;
117
118 fr = -1;
119 for (i = 0; i < NUM_HEIGHT_SEMAPHORS; i++) {
120 if (h_sems[i].inuse == 0)
121 fr = i;
122 else if (h_sems[i].x == x && h_sems[i].y == y && h_sems[i].floor == floor) {
123
124 // conflict. Take over the old semaphor.
125
126 stop_terrain_elevator_sound(i);
127
128 if (h_sems[i].key < MAX_HSEM_KEY)
129 h_sems[i].key++;
130 else
131 h_sems[i].key = 0;
132
133 *key = h_sems[i].key;
134 *sem = i;
135 if (!no_sfx) {
136 h_sems[i].inuse = 2;
137 if (play_digi_fx_loc(SFX_TERRAIN_ELEV_LOOP, -1, x << 8, y << 8) < 0)
138 h_sems[i].inuse = 1;
139 } else
140 h_sems[i].inuse = 1;
141 return (TRUE);
142 }
143 }
144 // no conflict. Allocate new semaphor.
145 if (fr < 0) {
146 // no free semaphor. Fail.
147 return (FALSE);
148 } else {
149 h_sems[fr].x = x;
150 h_sems[fr].y = y;
151 h_sems[fr].floor = floor;
152 h_sems[fr].key = 0;
153 *key = h_sems[fr].key;
154 *sem = fr;
155 if (!no_sfx) {
156 h_sems[fr].inuse = 2;
157 if (play_digi_fx_loc(SFX_TERRAIN_ELEV_LOOP, -1, x << 8, y << 8) < 0)
158 h_sems[fr].inuse = 1;
159 } else
160 h_sems[fr].inuse = 1;
161 return (TRUE);
162 }
163 }
164
unregister_h_event(char sem)165 void unregister_h_event(char sem) {
166 if (sem < 0 || sem >= NUM_HEIGHT_SEMAPHORS)
167 return;
168
169 stop_terrain_elevator_sound(sem);
170
171 h_sems[sem].inuse = 0;
172 }
173
174 // --------------
175 // EVENT HANDLERS
176 // --------------
177
null_event_handler(Schedule * s,SchedEvent * ev)178 void null_event_handler(Schedule *s, SchedEvent *ev) {}
179
trap_event_handler(Schedule * s,SchedEvent * ev)180 void trap_event_handler(Schedule *s, SchedEvent *ev) {
181 ObjID id1, id2;
182 uchar dummy;
183
184 id1 = ((TrapSchedEvent *)ev)->target_id;
185 id2 = ((TrapSchedEvent *)ev)->source_id;
186
187 do_multi_stuff(id1);
188 if (id2 != -1)
189 trap_activate(id2, &dummy);
190 }
191
192 #define FLOOR_HEIGHT_DELTA 0x4
height_event_handler(Schedule * s,SchedEvent * ev)193 void height_event_handler(Schedule *s, SchedEvent *ev) {
194 MapElem *pme;
195 HeightSchedEvent hse = *(HeightSchedEvent *)ev;
196 extern void rendedit_process_tilemap(FullMap * map, LGRect * r, uchar newMap);
197 short x, y;
198 LGRect bounds;
199 char ht, sign = (hse.steps_remaining > 0) ? 1 : -1;
200
201 // has someone else claimed this square?
202 if (h_sems[hse.semaphor].key != hse.key) {
203 return;
204 }
205 x = h_sems[hse.semaphor].x;
206 y = h_sems[hse.semaphor].y;
207 pme = MAP_GET_XY(x, y);
208 // look at top bit of step_size to determine floor or ceiling
209 if (hse.type == CEIL_SCHED_EVENT) {
210 ht = me_height_ceil(pme);
211 me_height_ceil_set(pme, ht + sign);
212 } else {
213 ObjRefID oref;
214 ObjID id;
215 ObjLoc newloc;
216 ht = me_height_flr(pme);
217 // Change height of objects, as well...
218 oref = me_objref(pme);
219 while (oref != OBJ_REF_NULL) {
220 // If we are on the old height, move us to the new height
221 id = objRefs[oref].obj;
222 if (abs(obj_floor_height(id) - objs[id].loc.z) < FLOOR_HEIGHT_DELTA) {
223 if (id == PLAYER_OBJ) {
224 extern void slam_posture_meter_state(void);
225 slam_posture_meter_state();
226 } else if (ObjProps[OPNUM(id)].physics_model) {
227 newloc = objs[id].loc;
228 newloc.z = obj_floor_compute(id, ht + sign);
229 obj_move_to(id, &newloc, TRUE);
230 }
231 }
232 // iterate
233 oref = objRefs[oref].next;
234 }
235 // Crank us to new height;
236 me_height_flr_set(pme, ht + sign);
237 }
238
239 hse.steps_remaining -= sign;
240 if (hse.steps_remaining != 0) {
241 hse.timestamp = TICKS2TSTAMP(player_struct.game_time + (CIT_CYCLE * HEIGHT_STEP_TIME) / HEIGHT_TIME_UNIT);
242 schedule_event(&(global_fullmap->sched[MAP_SCHEDULE_GAMETIME]), (SchedEvent *)&hse);
243 } else {
244 unregister_h_event(hse.semaphor);
245 }
246
247 {
248 bounds.ul.x = bounds.lr.x = x;
249 bounds.ul.y = bounds.lr.y = y;
250 rendedit_process_tilemap(global_fullmap, &bounds, FALSE);
251 }
252 }
253
254 #define ANTENNA_DESTROYED_QVAR 0x2
255 #define ANTENNAE_ALL_GONE_QBIT 0x99
256 #define PLOTWARE_QVAR 0x9
257 #define NUM_ANTENNAE_TO_DESTROY 4
258
door_event_handler(Schedule * s,SchedEvent * ev)259 void door_event_handler(Schedule *s, SchedEvent *ev) {
260 ObjID id;
261 short old_dest;
262 ushort code, curr_code;
263 extern uchar door_moving(ObjID id, uchar dir);
264
265 id = ((DoorSchedEvent *)ev)->door_id;
266 code = ((DoorSchedEvent *)ev)->secret_code;
267 if (objs[id].obclass == CLASS_DOOR) {
268 // construct code out of top 2 bits of inst_flags
269 curr_code = objs[id].info.inst_flags >> 6;
270
271 // Make sure that we actually care about this autoclose event
272 if (code == curr_code) {
273 // note the secret dont-autoclose-me-even-if-I-already-
274 // have-an-autoclose-scheduled cookie.
275 if (!(DOOR_REALLY_CLOSED(id) || door_moving(id, TRUE) ||
276 objDoors[objs[id].specID].autoclose_time == NEVER_AUTOCLOSE_COOKIE))
277 object_use(id, FALSE, OBJ_NULL);
278 }
279 } else if (ID2TRIP(id) == PLAS_ANTENNA_TRIPLE) {
280 ObjID ground0, p3obj;
281 ObjLoc blastLoc;
282 ExplosionData *kaboom;
283 extern short fr_sfx_time;
284
285 // An earth-shattering kaboom.
286 // turn the panel into a destroyed one
287 objs[id].info.type += 1;
288
289 // flash the screen
290 fr_global_mod_flag(FR_SOLIDFR_SLDCLR, FR_SOLIDFR_MASK);
291 fr_solidfr_color = GRENADE_COLOR;
292
293 // shake yer bootie
294 fr_global_mod_flag(FR_SFX_SHAKE, FR_SFX_MASK);
295 fr_sfx_time = CIT_CYCLE << 1; // 2 seconds of shake
296
297 // qvar tricks
298 old_dest = QUESTVAR_GET(ANTENNA_DESTROYED_QVAR);
299 QUESTVAR_SET(ANTENNA_DESTROYED_QVAR, old_dest + 1);
300 if (old_dest + 1 >= NUM_ANTENNAE_TO_DESTROY) {
301 QUESTVAR_SET(PLOTWARE_QVAR, QUESTVAR_GET(PLOTWARE_QVAR) + 1);
302 QUESTBIT_ON(ANTENNAE_ALL_GONE_QBIT);
303 do_multi_stuff(objFixtures[objs[id].specID].p1);
304 }
305
306 kaboom = &game_explosions[LARGE_GAME_EXPL];
307 p3obj = ground0 = objFixtures[objs[id].specID].p3;
308 if (ground0 == OBJ_NULL)
309 ground0 = id;
310 ObjLocCopy(objs[ground0].loc, blastLoc);
311 blastLoc.z = obj_height_from_fix(fix_from_obj_height(ground0) + 4 * RAYCAST_ATTACK_SIZE);
312 if (p3obj) {
313 ADD_DESTROYED_OBJECT(p3obj);
314 destroy_destroyed_objects();
315 }
316 do_explosion(blastLoc, ground0, M_EXPL2, kaboom);
317 fr_global_mod_flag(FR_SFX_SHAKE, FR_SFX_MASK);
318 fr_sfx_time = CIT_CYCLE << 1;
319 play_digi_fx_obj(SFX_EXPLOSION_1, 1, id);
320 mfd_notify_func(MFD_PLOTWARE_FUNC, MFD_ITEM_SLOT, FALSE, MFD_ACTIVE, TRUE);
321 }
322 }
323
grenade_event_handler(Schedule * s,SchedEvent * ev)324 void grenade_event_handler(Schedule *s, SchedEvent *ev) {
325 ObjID id;
326 ubyte unique_id;
327
328 id = ((GrenSchedEvent *)ev)->gren_id;
329
330 if (is_obj_destroyed(id))
331 return;
332
333 // make sure we have a real grenade
334 unique_id = objGrenades[objs[id].specID].unique_id;
335 if ((((GrenSchedEvent *)ev)->unique_id == unique_id) && unique_id) {
336 if (GrenadeProps[CPNUM(id)].flags & GREN_TIMING_TYPE)
337 ADD_DESTROYED_OBJECT(id);
338 // do_grenade_explosion(id, TRUE);
339 else
340 EDMS_obey_collisions(objs[id].info.ph);
341 }
342 }
343
explosion_event_handler(Schedule * s,SchedEvent * ev)344 void explosion_event_handler(Schedule *s, SchedEvent *ev) {}
345
exposure_event_handler(Schedule * s,SchedEvent * ev)346 void exposure_event_handler(Schedule *s, SchedEvent *ev) {
347 extern void expose_player(byte damage, ubyte type, ushort tsecs);
348 SchedExposeData *xd = (SchedExposeData *)&ev->data;
349 int count = xd->count;
350 int damage = xd->damage;
351 if (damage < 0)
352 damage = (damage - 1) / 2;
353 else
354 damage = (damage + 1) / 2;
355 expose_player(damage, xd->type, 0);
356 // count --;
357 if (damage != xd->damage)
358 // if (count > 0)
359 {
360 SchedEvent copy;
361 xd->damage -= damage;
362 xd->count = (ubyte)count;
363 copy = *ev;
364 copy.timestamp += xd->tsecs;
365 schedule_event(s, ©);
366 }
367 }
368
369 extern uchar muzzle_fire_light;
370 extern void lamp_turnon(uchar visible, uchar real);
371 extern void lamp_turnoff(uchar visible, uchar real);
372
light_event_handler(Schedule * s,SchedEvent * ev)373 void light_event_handler(Schedule *s, SchedEvent *ev) {
374 muzzle_fire_light = FALSE;
375
376 // is the player's lantern on - if not turn our faux lantern off, otherwise
377 // just turn the lantern on - to reset its values
378 if (!(player_struct.hardwarez_status[CPTRIP(LANTERN_HARD_TRIPLE)] & WARE_ON))
379 lamp_turnoff(TRUE, FALSE);
380 else
381 lamp_turnon(TRUE, FALSE);
382 }
383
bark_event_handler(Schedule * s,SchedEvent * ev)384 void bark_event_handler(Schedule *s, SchedEvent *ev) {
385 ubyte mfd_id;
386 void check_panel_ref(uchar puntme);
387 ubyte mfd_get_func(ubyte mfd_id, ubyte s);
388
389 // timeout bark, if it's still there at all.
390 for (mfd_id = 0; mfd_id < NUM_MFDS; mfd_id++) {
391 if (mfd_get_func(mfd_id, MFD_INFO_SLOT) == MFD_BARK_FUNC) {
392 check_panel_ref(TRUE);
393 break;
394 }
395 }
396 }
397
email_event_handler(Schedule * s,SchedEvent * ev)398 void email_event_handler(Schedule *s, SchedEvent *ev) {
399 extern void add_email_datamunge(short mung, uchar select);
400 EmailSchedEvent *e = (EmailSchedEvent *)ev;
401 add_email_datamunge(e->datamunge, TRUE);
402 }
403
404 // HERE IS THE ARRAY OF ALL EVENT HANDLERS
405
406 static SchedHandler sched_handlers[] = {
407 null_event_handler, grenade_event_handler, explosion_event_handler, door_event_handler,
408 trap_event_handler, exposure_event_handler, height_event_handler, height_event_handler,
409 light_event_handler, bark_event_handler, email_event_handler,
410 };
411
412 #define NUM_EVENT_TYPES (sizeof(sched_handlers) / sizeof(SchedHandler))
413
414 // ---------
415 // EXTERNALS
416 // ---------
417
418 static ushort current_tstamp = 0;
419
schedule_init(Schedule * s,int size,uchar grow)420 errtype schedule_init(Schedule *s, int size, uchar grow) {
421 TRACE("%s: schedule_init", __FUNCTION__);
422 return pqueue_init(&s->queue, size, sizeof(SchedEvent), compare_events, grow);
423 }
424
schedule_free(Schedule * s)425 errtype schedule_free(Schedule *s) {
426 TRACE("%s: schedule_free", __FUNCTION__);
427 return pqueue_destroy(&s->queue);
428 }
429
schedule_event(Schedule * s,SchedEvent * ev)430 errtype schedule_event(Schedule *s, SchedEvent *ev) {
431 errtype retval = OK;
432 if (!time_passes)
433 return ERR_NOEFFECT;
434 if (current_tstamp > 0 && compare_tstamps(ev->timestamp, current_tstamp) < 0) {
435 return ERR_NOEFFECT;
436 }
437
438 TRACE("%s: Scheduling an event.", __FUNCTION__);
439
440 retval = pqueue_insert(&s->queue, ev);
441 if (retval != OK) {
442 printf("Could not schedule event?\n");
443 }
444 return retval;
445 }
446
schedule_reset(Schedule * s)447 errtype schedule_reset(Schedule *s) {
448 s->queue.fullness = 0;
449 return OK;
450 }
451
reset_schedules(void)452 void reset_schedules(void) {
453 int i;
454 for (i = 0; i < NUM_MAP_SCHEDULES; i++)
455 schedule_reset(&global_fullmap->sched[i]);
456 schedule_reset(&game_seconds_schedule);
457 }
458
schedule_run(Schedule * s,ushort time)459 errtype schedule_run(Schedule *s, ushort time) {
460 SchedEvent ev;
461 errtype err;
462 current_tstamp = time;
463 for (err = pqueue_least(&s->queue, &ev); err == OK && compare_tstamps(ev.timestamp, time) < 0;
464 err = pqueue_least(&s->queue, &ev)) {
465 if (ev.type < NUM_EVENT_TYPES)
466 sched_handlers[ev.type](s, &ev);
467 pqueue_extract(&s->queue, &ev);
468 }
469 current_tstamp = 0;
470 return OK;
471 }
472
run_schedules(void)473 void run_schedules(void) {
474 schedule_run(&global_fullmap->sched[MAP_SCHEDULE_GAMETIME], TICKS2TSTAMP(player_struct.game_time));
475 schedule_run(&game_seconds_schedule, TICKS2TSTAMP(player_struct.game_time));
476 }
477
478 /*
479 uchar schedule_test_hotkey(short keycode, ulong context, void* data)
480 {
481 SchedEvent e;
482 #ifndef NO_DUMMIES
483 int dummy; dummy = keycode + context + (int)data;
484 #endif
485 e.timestamp = player_struct.game_time/CIT_CYCLE + 100;
486 e.type = 0;
487 schedule_event(&game_seconds_schedule,&e);
488 return TRUE;
489 }
490 */
491