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, &copy);
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