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/trigger.c $
21 * $Revision: 1.161 $
22 * $Author: xemu $
23 * $Date: 1994/11/25 18:18:30 $
24 *
25 */
26
27 #define __TRIGGER_SRC
28
29 #include <stdlib.h>
30 #include <string.h>
31
32 #include "Prefs.h"
33
34 #include "ai.h"
35 #include "audiolog.h"
36 #include "criterr.h"
37 #include "cybstrng.h"
38 #include "damage.h"
39 #include "diffq.h"
40 #include "doorparm.h"
41 #include "effect.h"
42 #include "faketime.h"
43 #include "frflags.h"
44 #include "frprotox.h"
45 #include "gameloop.h" // for VITALS_UPDATE
46 #include "gamerend.h"
47 #include "gamescr.h"
48 #include "gamestrn.h"
49 #include "invent.h"
50 #include "mainloop.h" // for flag setting stuff
51 #include "map.h"
52 #include "mapflags.h"
53 #include "mfdext.h"
54 #include "musicai.h"
55 #include "objbit.h"
56 #include "objcrit.h"
57 #include "objgame.h"
58 #include "objsim.h"
59 #include "objstuff.h"
60 #include "objuse.h"
61 #include "olhext.h"
62 #include "otrip.h"
63 #include "physics.h"
64 #include "player.h"
65 #include "saveload.h"
66 #include "schedule.h"
67 #include "sfxlist.h"
68 #include "shodan.h"
69 #include "tilename.h"
70 #include "tools.h"
71 #include "trigger.h"
72 #include "hkeyfunc.h"
73 #include "cyber.h"
74 #include "colors.h"
75 #include "grenades.h"
76 #include "bark.h"
77 #include "view360.h"
78 #include "objload.h"
79 #include "rendfx.h"
80 #include "statics.h"
81 #include "citres.h"
82 #include "setploop.h"
83 #include "cutsloop.h"
84
85 #ifdef OLD_TELEPORT_BETWEEN_LEVELS
86 #include <gamewrap.h>
87 #include <gamerend.h>
88 #include <render.h>
89 #endif
90
91 // As far as I can tell, these NEVER GET USED. So I thought I'd move them out of the file
92 // to remove a dependency problem.
93
94 #define TRAP_NULL_CODE 0
95 #define TRAP_TELEPORT_CODE 1
96 #define TRAP_DAMAGE_CODE 2
97 #define TRAP_CREATE_OBJ_CODE 3
98 #define TRAP_QUESTBIT_CODE 4
99 #define TRAP_ENDGAME_CODE 5
100 #define TRAP_MULTI_CODE 6
101 #define TRAP_LIGHT_CODE 7
102 #define TRAP_SFX_CODE 8
103 #define TRAP_HEIGHT_CODE 9
104 #define TRAP_TERRAIN_CODE 10
105 #define TRAP_SCHEDULER_CODE 11
106 #define TRAP_ALT_SPLIT_CODE 12
107 #define TRAP_DESTROY_OBJ_CODE 13
108 #define TRAP_PLOT_CLOCK_CODE 14
109 #define TRAP_EMAIL_CODE 15
110 #define TRAP_EXPOSE_CODE 16
111 #define TRAP_INSTANCE_CODE 17
112 #define TRAP_ANIMATE_CODE 18
113 #define TRAP_HACK_CODE 19
114 #define TRAP_TEXTURE_CODE 20
115 #define TRAP_AI_CODE 21
116 #define TRAP_BARK_CODE 22
117 #define TRAP_MONSTER_CODE 23
118 #define TRAP_TRANSMOGRIFY_CODE 24
119
120 // Note that this will always give you boolean 0 or 1, as opposed to
121 // things like (p2 & 0x10000) that certain people once used which is
122 // always 0 when cast to a bool.
123
124 #define BIT_SET(val, bit) (((val) & (1 << bit)) == (1 << bit))
125
126 #define REACTOR_BOOM_QB 0x14
127 errtype do_special_reactor_hack();
128
129 errtype do_destroy(int victim_data);
130
131 ObjID current_trap;
132 uchar _tr_use_message;
133 #define trap_use_message (&_tr_use_message)
134
135 short qdata_get(short qdata);
136 errtype qdata_set(short qdata, short new_val);
137 uchar comparator_check(int comparator, ObjID obj, uchar *special_code);
138 errtype set_trap_data(ObjID id, char num_param, int new_val);
139 errtype do_timed_multi_stuff(int p);
140
141 errtype trap_null_func(int p1, int p2, int p3, int p4);
142 errtype trap_transmogrify_func(int p1, int p2, int p3, int p4);
143 uchar player_facing_square(LGPoint sq);
144 errtype trap_monster_func(int p1, int p2, int p3, int p4);
145 errtype do_ai_trap(ObjSpecID osid, int p1, int p3, int p4);
146 errtype trap_ai_func(int p1, int p2, int p3, int p4);
147 errtype trap_alternating_splitter_func(int p1, int p2, int p3, int p4);
148 errtype trap_main_light_func(int p1, int p2, int p3, int p4);
149 errtype trap_terrain_func(int p1, int p2, int p3, int p4);
150 errtype trap_height_func(int p1, int p2, int p3, int p4);
151 errtype real_instance_func(int p1, int p2, int p3, int p4);
152 errtype trap_instance_func(int p1, int p2, int p3, int p4);
153 void animate_callback_func(ObjID id, intptr_t user_data);
154 errtype real_animate_func(ObjID id, int p2, int p3, int p4);
155 errtype trap_animate_func(int p1, int p2, int p3, int p4);
156 void hack_shodan_conquer_func(char bonus_fun);
157 void hack_armageddon_func(int otrip, int x0, int y0, int r);
158 void hack_multi_trans(int trip, int newtype);
159 void hack_change_comparator(int p2, int p3);
160 void hack_taunt_diego(int p2, int p3);
161 errtype trap_hack_func(int p1, int p2, int p3, int p4);
162 errtype trap_multi_func(int p1, int p2, int p3, int p4);
163 errtype trap_destroy_object_func(int p1, int p2, int p3, int p4);
164 errtype trap_plot_clock_func(int p1, int p2, int p3, int p4);
165 errtype trap_email_func(int mung, int time, int p3, int p4);
166 errtype trap_texture_func(int p1, int p2, int p3, int p4);
167 errtype trap_expose_func(int dmg, int dtype, int tsecs, int dummy);
168 errtype trap_bark_func(int speaker, int strnum, int color, int hud_bark);
169
170 errtype grind_trap(char type, int p1, int p2, int p3, int p4, ubyte *destroy_count_ptr, ObjID id);
171 errtype do_level_entry_triggers();
172 errtype do_shodan_triggers();
173 errtype do_ecology_triggers();
174
175 #define SHODOMETER_QVAR_BASE 0x10
176
qdata_get(short qdata)177 short qdata_get(short qdata) {
178 short contents = qdata & 0xFFF;
179 if (qdata & 0x1000) {
180 if ((contents >= FIRST_SHODAN_QV) && (contents <= FIRST_SHODAN_QV + MAX_SHODOMETER_LEVEL)) {
181 short retval;
182 if (QUESTVAR_GET(MISSION_DIFF_QVAR) <= 1)
183 return (0);
184 retval = QUESTVAR_GET(contents) * 255 / player_struct.initial_shodan_vals[contents - FIRST_SHODAN_QV];
185 if (retval > 255)
186 retval = 255;
187 return (retval);
188 } else
189 return (QUESTVAR_GET(contents));
190 } else if (qdata & 0x2000)
191 return (QUESTBIT_GET(contents));
192 else
193 return (contents);
194 }
195
qdata_set(short qdata,short new_val)196 errtype qdata_set(short qdata, short new_val) {
197 if (qdata & 0x1000)
198 QUESTVAR_SET(qdata & 0xFFF, new_val);
199 else if (qdata & 0x2000) {
200 if ((new_val > 1) || (new_val < -1)) {
201 if (qdata_get(qdata))
202 new_val = 0;
203 else
204 new_val = 1;
205 }
206 if (new_val)
207 QUESTBIT_ON(qdata & 0xFFF);
208 else
209 QUESTBIT_OFF(qdata & 0xFFF);
210 }
211
212 mfd_notify_func(MFD_PLOTWARE_FUNC, MFD_ITEM_SLOT, FALSE, MFD_ACTIVE, TRUE);
213
214 return (OK);
215 }
216
comparator_check(int comparator,ObjID obj,uchar * special_code)217 uchar comparator_check(int comparator, ObjID obj, uchar *special_code) {
218 short cval, compval;
219 uchar fail_code;
220 short fail_amt = 0;
221 uchar truthval = FALSE;
222 // shodo_qvar is the questvariable for the shodometer on the current levelle
223 char shodo_qvar = SHODOMETER_QVAR_BASE + player_struct.level;
224
225 *special_code = 0;
226 fail_code = comparator >> 24;
227 comparator = comparator & 0xFFFFFF;
228 if (comparator == 0)
229 return (TRUE);
230 compval = comparator >> 16;
231 if (comparator & 0x1000) {
232 cval = qdata_get(0x1000 | (comparator & 0xFFF)); // QUESTVAR_GET(comparator & 0xFFF);
233 } else
234 cval = (QUESTBIT_GET(comparator & 0xFFF) != 0);
235 switch ((comparator & 0xE000) >> 13) {
236 case 0:
237 truthval = (cval == compval);
238 fail_amt = abs(cval - compval);
239 break;
240 case 1:
241 truthval = (cval < compval);
242 fail_amt = cval - compval;
243 break;
244 case 2:
245 truthval = (cval <= compval);
246 fail_amt = cval - compval;
247 break;
248 case 3:
249 truthval = (cval > compval);
250 fail_amt = compval - cval;
251 break;
252 case 4:
253 truthval = (cval >= compval);
254 fail_amt = compval - cval;
255 break;
256 case 5:
257 truthval = (cval != compval);
258 fail_amt = abs(cval - compval);
259 break;
260 case 6:
261 truthval = (compval > (rand() % 255));
262 break;
263 }
264 if (!truthval && fail_code) {
265 if ((comparator & 0xFFF) == shodo_qvar)
266 *special_code = fail_amt;
267 if ((*special_code != 0) && (fail_code == SPECIAL_SHODAN_FAIL_CODE)) {
268 short shodan_amt;
269 shodan_amt = lg_min(NUM_SHODAN_MUGS - 1, *special_code >> SHODAN_INTERVAL_SHIFT);
270 long_bark(obj, FIRST_SHODAN_MUG + shodan_amt, SHODAN_FAILURE_STRING, 0x4c);
271 } else {
272 string_message_info(REF_STR_TrapZeroMessage + fail_code);
273 #ifdef AUDIOLOGS
274 audiolog_bark_play(fail_code);
275 #endif
276 }
277 }
278
279 return (truthval);
280 }
281
set_trap_data(ObjID id,char num_param,int new_val)282 errtype set_trap_data(ObjID id, char num_param, int new_val) {
283 uint *pbase = NULL;
284 ObjSpecID osid = objs[id].specID;
285
286 if (num_param < 1 || num_param > 4)
287 return (ERR_NOEFFECT);
288
289 switch (objs[id].obclass) {
290 case CLASS_FIXTURE:
291 pbase = &(objFixtures[osid].p1);
292 break;
293 case CLASS_TRAP:
294 pbase = &(objTraps[osid].p1);
295 break;
296 }
297
298 if (pbase == NULL)
299 return (ERR_NOEFFECT);
300
301 *(pbase + (num_param - 1)) = new_val;
302
303 return (OK);
304 }
305
trigger_check_destroyed(ObjID id)306 errtype trigger_check_destroyed(ObjID id) {
307 errtype retval = OK;
308 if ((objs[id].obclass == CLASS_FIXTURE) && (objs[id].subclass == FIXTURE_SUBCLASS_CYBER))
309 retval = trap_activate(id, trap_use_message);
310 return (retval);
311 }
312
location_trigger_activate(ObjID id)313 errtype location_trigger_activate(ObjID id) {
314 errtype retval = ERR_NOEFFECT;
315
316 if (objs[id].info.type == ENTRY_TRIGGER_TYPE)
317 retval = trap_activate(id, trap_use_message);
318 if (objs[id].info.type == FLOOR_TRIGGER_TYPE) {
319 if (fix_from_obj_height(PLAYER_OBJ) <
320 (STANDARD_SIZE + (fix_from_map_height(me_height_flr(MAP_GET_XY(PLAYER_BIN_X, PLAYER_BIN_Y))))))
321 retval = trap_activate(id, trap_use_message);
322 }
323
324 return (retval);
325 }
326
trap_null_func(int p1,int p2,int p3,int p4)327 errtype trap_null_func(int p1, int p2, int p3, int p4) { return (OK); }
328
329 #define MAX_BRIDGE_FRAME 32
330
trap_transmogrify_func(int p1,int p2,int p3,int p4)331 errtype trap_transmogrify_func(int p1, int p2, int p3, int p4) {
332 extern void slam_posture_meter_state(void);
333 int source, dest, t1, t2;
334
335 if (!objs[p1].active) {
336 return (ERR_NOEFFECT);
337 }
338 t1 = p2 & 0xFFFF;
339
340 // okay, so this is kind of a dopey way to do things, but it's backwards-
341 // compatible with our initial transmog spec. For FC, we should not do
342 // this ... hopefully following ifdef trick will make sure we change it.
343
344 #ifdef FIRST_SHODAN_MUG
345 t2 = t1 ^ (p2 >> 16);
346 #else
347 #error "hey, look at me, I don't compile. Look at me! t2 = (p2 >> 16);"
348 #endif
349
350 if (((t1 > t2) ? t1 : t2) >= num_types(objs[p1].obclass, objs[p1].subclass)) {
351 return (ERR_NOEFFECT);
352 }
353 source = ID2TRIP(p1);
354 if (objs[p1].info.type == t1)
355 objs[p1].info.type = t2;
356 else
357 objs[p1].info.type = t1;
358 dest = ID2TRIP(p1);
359 // if (p3 > 0)
360 {
361 switch (source) {
362 case NON_BRIDGE_TRIPLE:
363 if ((source == NON_BRIDGE_TRIPLE) && ((dest == FORCE_BRIJ_TRIPLE) || (dest == FORCE_BRIJ2_TRIPLE))) {
364 remove_obj_from_animlist(p1);
365 objs[p1].info.current_frame = MAX_BRIDGE_FRAME;
366 objBigstuffs[objs[p1].specID].cosmetic_value = MAX_BRIDGE_FRAME;
367 // counting backwards from zero
368 add_obj_to_animlist(p1, FALSE, TRUE, FALSE, 16, 0, 0, 0);
369 if (source == NON_BRIDGE_TRIPLE) {
370 //hack: on level 4 only allow one simultaneously playing force bridge sound
371 if (player_struct.level != 4 || !digi_fx_playing(SFX_FORCE_BRIDGE, NULL))
372 play_digi_fx_obj(SFX_FORCE_BRIDGE, 1, p1);
373 }
374 }
375 break;
376 #ifdef TWO_WAY_TRANSMOGGING
377 case FORCE_BRIJ_TRIPLE:
378 case FORCE_BRIJ2_TRIPLE:
379 if (dest == NON_BRIDGE_TRIPLE) {
380 remove_obj_from_animlist(p1);
381 objs[p1].info.current_frame = 0;
382 objBigstuffs[objs[p1].specID].cosmetic_value = MAX_BRIDGE_FRAME;
383 add_obj_to_animlist(p1, FALSE, FALSE, FALSE, 16, 0, 0, 0);
384 }
385 #endif
386 }
387 }
388 slam_posture_meter_state();
389 obj_physics_refresh_area(OBJ_LOC_BIN_X(objs[p1].loc), OBJ_LOC_BIN_Y(objs[p1].loc), FALSE);
390 return (OK);
391 }
392
393 // Above this search size, just go through list of objCritters, otherwise
394 // look at all the objects
395 #define SEARCH_AREA_THRESHOLD 81
396
397 #define MIN_MONSTER_DISTANCE 7
398 #define NOLOOK_MONSTER_DISTANCE 14
399
400 #define CONSERVATIVE_OCTANT_ARC 0x2500
401
402 // Okay, this is stupid and uses fix_atan but WTF, I wanted to
403 // write something really quickly for alpha.
404
405 // cast to signed-ness before taking abs
406 #define fixang_abs(x) abs((short)(x))
407
player_facing_square(LGPoint sq)408 uchar player_facing_square(LGPoint sq) {
409 fixang plrh, sqh, delta;
410 plrh = fixang_from_phys_angle(phys_angle_from_obj(objs[PLAYER_OBJ].loc.h));
411 // reflect x,y around the line x=y to sneakily convert fixang in regular-
412 // guy coordinates to fixang in north-is-zero-and-clockwise coordinates
413 // so, anyway, I'm swapping x and y for a reason here.
414 sqh = fix_atan2(fix_make(sq.x - PLAYER_BIN_X, 0), fix_make(sq.y - PLAYER_BIN_Y, 0));
415 delta = sqh - plrh;
416 if (fixang_abs(delta) < CONSERVATIVE_OCTANT_ARC)
417 return (TRUE);
418 // note that we're working in a left-handed, clockwise world
419 if (view360_active_contexts[LEFT_CONTEXT] && fixang_abs(delta + 0x4000) < CONSERVATIVE_OCTANT_ARC)
420 return (TRUE);
421 if (view360_active_contexts[RIGHT_CONTEXT] && fixang_abs(delta - 0x4000) < CONSERVATIVE_OCTANT_ARC)
422 return (TRUE);
423 if (view360_active_contexts[MID_CONTEXT] && fixang_abs(delta + 0x8000) < CONSERVATIVE_OCTANT_ARC)
424 return (TRUE);
425 return (FALSE);
426 }
427
428 // p1 = triple
429 // p2 = area of effect
430 // p3 = quantity of object to create
trap_monster_func(int p1,int p2,int p3,int p4)431 errtype trap_monster_func(int p1, int p2, int p3, int p4) {
432 ObjID new_id, id1, id2;
433 ObjSpecID osid;
434 ObjRefID oref;
435 char minx, miny, sizex, sizey, quan, failures = 0, num_gen = 0;
436 LGPoint sq;
437 extern errtype obj_load_art(uchar flush_all);
438 uchar okay = FALSE;
439 char monster_count;
440 MapElem *pme;
441
442 TRACE("%s: trigger", __FUNCTION__);
443
444 quan = qdata_get(p3);
445 switch (player_struct.difficulty[COMBAT_DIFF_INDEX]) {
446 case 0:
447 quan = 0;
448 break;
449 case 3:
450 quan++;
451 }
452 while (quan > 0) {
453 id1 = p2 & 0xFFFF;
454 id2 = p2 >> 16;
455 if (id2 != OBJ_NULL) {
456 minx = lg_min(OBJ_LOC_BIN_X(objs[id1].loc), OBJ_LOC_BIN_X(objs[id2].loc));
457 miny = lg_min(OBJ_LOC_BIN_Y(objs[id1].loc), OBJ_LOC_BIN_Y(objs[id2].loc));
458 sizex = lg_max(OBJ_LOC_BIN_X(objs[id1].loc), OBJ_LOC_BIN_X(objs[id2].loc)) - minx;
459 sizey = lg_max(OBJ_LOC_BIN_Y(objs[id1].loc), OBJ_LOC_BIN_Y(objs[id2].loc)) - miny;
460 } else {
461 sizex = sizey = id1 * 2;
462 minx = OBJ_LOC_BIN_X(objs[current_trap].loc) - sizex;
463 miny = OBJ_LOC_BIN_Y(objs[current_trap].loc) - sizey;
464 }
465 okay = FALSE;
466 monster_count = 0;
467 while (!okay && (monster_count < 100)) {
468 int tiletype, room;
469
470 okay = TRUE;
471 sq.x = minx + rand() % (sizex + 1);
472 sq.y = miny + rand() % (sizey + 1);
473 pme = MAP_GET_XY(sq.x, sq.y);
474
475 tiletype = me_tiletype(pme);
476 room = MAP_HEIGHTS - me_height_ceil(pme) - me_height_flr(pme);
477
478 if (tiletype >= TILE_SLOPEUP_N && tiletype <= TILE_SLOPECV_SW) {
479 if (me_bits_mirror(pme) == MAP_FFLAT)
480 room -= me_param(pme);
481 else
482 room = 0;
483 } else if (tiletype != TILE_OPEN)
484 room = 0;
485 room = (room << MAP_ZSHF) / 8;
486 if (room >= 8) {
487 oref = me_objref(pme);
488 while (okay && (oref != OBJ_REF_NULL)) {
489 if (objs[objRefs[oref].obj].obclass == CLASS_CRITTER) {
490 okay = FALSE;
491 }
492 oref = objRefs[oref].next;
493 }
494 } else {
495 okay = FALSE;
496 break;
497 }
498 if (p4 & 0x8)
499 okay = TRUE;
500 if (okay) {
501 char d;
502 d = abs(PLAYER_BIN_X - sq.x) + abs(PLAYER_BIN_Y - sq.y);
503
504 // This should be in order of harshness
505 if ((p4 & 0x2) && player_facing_square(sq) && (d < NOLOOK_MONSTER_DISTANCE)) {
506 okay = FALSE;
507 }
508 if ((p4 & 0x1) && (d < MIN_MONSTER_DISTANCE)) {
509 okay = FALSE;
510 }
511 if (!(p4 & 0x4) && (me_bits_music(pme) == ELEVATOR_ZONE)) {
512 okay = FALSE;
513 }
514 }
515 monster_count++;
516 }
517 if (okay) {
518 new_id = object_place(p1, sq);
519 if (new_id == OBJ_NULL) {
520 quan = 0;
521 } else {
522 num_gen++;
523 // Set some default instance data
524 osid = objs[new_id].specID;
525 switch (objs[new_id].obclass) {
526 case CLASS_CRITTER:
527 objCritters[osid].mood = AI_MOOD_NEUTRAL;
528 objCritters[osid].orders = AI_ORDERS_ROAM;
529 break;
530 }
531 quan--;
532 }
533 } else {
534 failures++;
535 if (failures > quan)
536 quan = 0;
537 }
538 }
539 if (num_gen > 0)
540 obj_load_art(FALSE);
541 return (OK);
542 }
543
do_ai_trap(ObjSpecID osid,int p1,int p3,int p4)544 errtype do_ai_trap(ObjSpecID osid, int p1, int p3, int p4) {
545 ObjID oid;
546 oid = objCritters[osid].id;
547 if ((p1 < 0) || ((objs[oid].subclass == (p1 & 0xFF00) >> 8) && (objs[oid].info.type == (p1 & 0xFF)))) {
548 // Now look at the solo bit
549 if ((p1 == -1) || // we are already OK
550 (p1 & 0x20000) || // or we don't care
551 ((p1 & 0x10000) &&
552 (objs[oid].info.inst_flags & CLASS_INST_FLAG)) || // or if we want only loners, and we're a loner
553 ((!(p1 & 0x10000)) &&
554 (!(objs[oid].info.inst_flags & CLASS_INST_FLAG)))) // or we want only non-loners, and we're a non-loner
555 {
556 if ((p3 < 0x1000) && ((QUESTVAR_GET(COMBAT_DIFF_QVAR) > 0) || (qdata_get(p3) == AI_MOOD_FRIENDLY)))
557 objCritters[osid].mood = qdata_get(p3);
558 if (p4 < 0x1000)
559 objCritters[osid].orders = qdata_get(p4);
560 }
561 }
562 return (OK);
563 }
564
trap_ai_func(int p1,int p2,int p3,int p4)565 errtype trap_ai_func(int p1, int p2, int p3, int p4) {
566 int x1, x2, y1, y2, i, j;
567 ObjSpecID osid;
568 ObjRefID oref;
569 ObjID oid, o1, o2;
570
571 TRACE("%s: trigger", __FUNCTION__);
572
573 if (p1 & 0x40000) {
574 if ((objs[p1 & 0xFFFF].active) && (objs[p1 & 0xFFFF].obclass == CLASS_CRITTER))
575 do_ai_trap(objs[p1 & 0xFFFF].specID, -1, p3, p4);
576 return (OK);
577 }
578 if ((p2 & 0xFFFF) == 0) {
579 // Radius or total AOE
580 if ((p2 >> 16) == 0) {
581 x1 = 0;
582 y1 = 0;
583 x2 = global_fullmap->x_size;
584 y2 = global_fullmap->y_size;
585 } else {
586 x1 = OBJ_LOC_BIN_X(objs[current_trap].loc) - (p2 >> 16);
587 y1 = OBJ_LOC_BIN_Y(objs[current_trap].loc) - (p2 >> 16);
588 x2 = x1 + (2 * (p2 >> 16));
589 y2 = y1 + (2 * (p2 >> 16));
590 }
591 } else {
592 // Object-demarked rectangle AOE
593 o1 = p2 & 0xFFFF;
594 o2 = p2 >> 16;
595 if ((o1 == OBJ_NULL) || (o2 == OBJ_NULL)) {
596 return (ERR_NOEFFECT);
597 } else {
598 x1 = lg_min(OBJ_LOC_BIN_X(objs[o1].loc), OBJ_LOC_BIN_X(objs[o2].loc));
599 x2 = lg_max(OBJ_LOC_BIN_X(objs[o1].loc), OBJ_LOC_BIN_X(objs[o2].loc));
600 y1 = lg_min(OBJ_LOC_BIN_Y(objs[o1].loc), OBJ_LOC_BIN_Y(objs[o2].loc));
601 y2 = lg_max(OBJ_LOC_BIN_Y(objs[o1].loc), OBJ_LOC_BIN_Y(objs[o2].loc));
602 }
603 }
604
605 // If the area to search is greater than a certain threshold, use
606 // one search method, otherwise use another.
607 if ((x2 - x1) * (y2 - y1) > SEARCH_AREA_THRESHOLD) {
608 osid = objCritters[0].id;
609 while (osid != OBJ_SPEC_NULL) {
610 oid = objCritters[osid].id;
611 if ((OBJ_LOC_BIN_X(objs[oid].loc) >= x1) && (OBJ_LOC_BIN_X(objs[oid].loc) <= x2) &&
612 (OBJ_LOC_BIN_Y(objs[oid].loc) >= y1) && (OBJ_LOC_BIN_Y(objs[oid].loc) <= y2)) {
613 do_ai_trap(osid, p1, p3, p4);
614 }
615 osid = objCritters[osid].next;
616 }
617 } else {
618 for (j = y1; j <= y2; j++) {
619 for (i = x1; i <= x2; i++) {
620 oref = me_objref(MAP_GET_XY(i, j));
621 while (oref != OBJ_REF_NULL) {
622 // Make sure we only do this to the "true" ref
623 // on each obj
624 oid = objRefs[oref].obj;
625 if ((objs[oid].ref == oref) && (objs[oid].obclass == CLASS_CRITTER)) {
626 if (p1 & 0x80000)
627 do_ai_trap(objs[oid].specID, -2, p3, p4);
628 else
629 do_ai_trap(objs[oid].specID, p1, p3, p4);
630 }
631 oref = objRefs[oref].next;
632 }
633 }
634 }
635 }
636 return (OK);
637 }
638
639 #define TRAP_TIME_UNIT 10 // how many time-setting units in a second
640
trap_scheduler_func(int p1,int p2,int p3,int p4)641 errtype trap_scheduler_func(int p1, int p2, int p3, int p4) {
642 TrapSchedEvent new_event;
643 uint *p;
644
645 TRACE("%s: trigger", __FUNCTION__);
646
647 if ((p3 >= 0xFFFF) || ((p3 > 0x1000) && (QUESTBIT_GET(p3 & 0xFFF))) || ((p3 < 0x1000) && (p3 > 0))) {
648 switch (objs[current_trap].obclass) {
649 case CLASS_TRAP:
650 p = &(objTraps[objs[current_trap].specID].p3);
651 case CLASS_FIXTURE:
652 p = &(objFixtures[objs[current_trap].specID].p3);
653 }
654 if ((p3 < 0x1000) && (p3 > 0))
655 (*p)--;
656 new_event.timestamp =
657 TICKS2TSTAMP(player_struct.game_time + (CIT_CYCLE * (ushort)qdata_get(p2)) / TRAP_TIME_UNIT) + 1;
658 if (qdata_get(p4) != 0)
659 new_event.timestamp += rand() % qdata_get(p4);
660 new_event.type = TRAP_SCHED_EVENT;
661 new_event.target_id = qdata_get(p1);
662 new_event.source_id = current_trap;
663 return (schedule_event(&(global_fullmap->sched[MAP_SCHEDULE_GAMETIME]), (SchedEvent *)&new_event));
664 }
665 return (OK);
666 }
667
trap_alternating_splitter_func(int p1,int p2,int p3,int p4)668 errtype trap_alternating_splitter_func(int p1, int p2, int p3, int p4) {
669 uchar found_new = FALSE;
670 char loop_count = 0;
671 int n = p4;
672 ObjID tr = current_trap;
673
674 while (!found_new) {
675 switch (n) {
676 case 0:
677 do_timed_multi_stuff(qdata_get(p1));
678 found_new = TRUE;
679 break;
680 case 1:
681 do_timed_multi_stuff(qdata_get(p2));
682 found_new = TRUE;
683 break;
684 case 2:
685 do_timed_multi_stuff(qdata_get(p3));
686 found_new = TRUE;
687 break;
688 }
689 n += 1;
690 set_trap_data(tr, 4, n);
691 if ((n > 2) || ((n == 2) && (p3 == 0))) {
692 set_trap_data(tr, 4, 0);
693 }
694 loop_count++;
695 if (loop_count > 10) {
696 found_new = TRUE;
697 }
698 }
699 return (OK);
700 }
701
702 errtype trap_lighting_func(uchar floor, int p1, int p2, int p3, int p4);
703
704 // NUM_LIGHT_STEPS * LIGHT_TICKS should equal the total time of a transition, in this case .5 seconds
705 #define NUM_LIGHT_STEPS 8
706 #define LIGHT_TICKS (CIT_CYCLE >> 4)
707
trap_main_light_func(int p1,int p2,int p3,int p4)708 errtype trap_main_light_func(int p1, int p2, int p3, int p4) {
709 uint *p;
710 if ((p3 & 0x10000) || (p3 & 0x20000))
711 trap_lighting_func(FALSE, p1, p2, p3 & 0xffff, p4);
712 if (!(p3 & 0x10000))
713 trap_lighting_func(TRUE, p1, p2, p3 & 0xffff, p4);
714
715 TRACE("%s: trigger", __FUNCTION__);
716
717 // Do transition rescheduling & incrementing
718 if (p2 & 0xFFFF) {
719 TrapSchedEvent new_event;
720 int num_steps = (p2 & 0xFFF0000) >> 16;
721 switch (objs[current_trap].obclass) {
722 case CLASS_TRAP:
723 p = &(objTraps[objs[current_trap].specID].p2);
724 break;
725 case CLASS_FIXTURE:
726 p = &(objFixtures[objs[current_trap].specID].p2);
727 break;
728 }
729 *p &= 0xF000FFFF;
730 if (num_steps < NUM_LIGHT_STEPS) {
731 // Now increment & re-schedule
732 num_steps++;
733 *p |= (num_steps << 16);
734 new_event.timestamp = TICKS2TSTAMP(player_struct.game_time + LIGHT_TICKS) + 1;
735 new_event.type = TRAP_SCHED_EVENT;
736 new_event.target_id = OBJ_NULL;
737 new_event.source_id = current_trap;
738 schedule_event(&(global_fullmap->sched[MAP_SCHEDULE_GAMETIME]), (SchedEvent *)&new_event);
739 }
740 }
741 return (OK);
742 }
743
744 #define MAX_LIGHT_VAL 15
745
746 #define LIGHT_PLAIN_ALT 0
747 #define LIGHT_EW_SMOOTH 1
748 #define LIGHT_NS_SMOOTH 2
749 #define LIGHT_RADIAL 3
750
trap_lighting_func(uchar floor,int p1,int p2,int p3,int p4)751 errtype trap_lighting_func(uchar floor, int p1, int p2, int p3, int p4) {
752 int i, j;
753 int targ1, targ2, setme, delta;
754 int otarg1, otarg2;
755 int x1, x2, y1, y2;
756 fix rad_delt, rdx, rdy;
757 short num_steps = -1;
758 char trans_type;
759 ObjID o1, o2;
760 MapElem *pme;
761 char v[4];
762 char light_state;
763 uint *p;
764
765 TRACE("%s: trigger", __FUNCTION__);
766
767 o1 = qdata_get(p1 & 0xFFFF);
768 o2 = qdata_get(p1 >> 16);
769
770 // p2 will eventually be used for lighting transition types (interp or area staggered)
771 // although, the high bits of it are used for maintaining state.
772
773 trans_type = p2 & 0xFFFF;
774 if (trans_type != 0) {
775 num_steps = (p2 & 0xFFF0000) >> 16;
776 }
777
778 if (p3 == LIGHT_RADIAL) {
779 // compute the bounding box of the radius
780 x1 = OBJ_LOC_BIN_X(objs[current_trap].loc) - o1;
781 x2 = x1 + (2 * o1);
782 y1 = OBJ_LOC_BIN_Y(objs[current_trap].loc) - o1;
783 y2 = y1 + (2 * o1);
784 } else {
785 if ((o1 == OBJ_NULL) || (o2 == OBJ_NULL) || (!objs[o1].active) || (!objs[o2].active)) {
786 return (ERR_NOEFFECT);
787 }
788
789 x1 = lg_min(OBJ_LOC_BIN_X(objs[o1].loc), OBJ_LOC_BIN_X(objs[o2].loc));
790 x2 = lg_max(OBJ_LOC_BIN_X(objs[o1].loc), OBJ_LOC_BIN_X(objs[o2].loc));
791 y1 = lg_min(OBJ_LOC_BIN_Y(objs[o1].loc), OBJ_LOC_BIN_Y(objs[o2].loc));
792 y2 = lg_max(OBJ_LOC_BIN_Y(objs[o1].loc), OBJ_LOC_BIN_Y(objs[o2].loc));
793 }
794
795 v[0] = p4 & 0xFF;
796 v[1] = (p4 & 0xFF00) >> 8;
797 v[2] = (p4 & 0xFF0000) >> 16;
798 v[3] = p4 >> 24;
799 // radial light doesn't actually necessarily set any of its neighboring
800 // points to its lighting values ... lighting from trap may fall from
801 // an illegal value into the legal range due to distance from trap to
802 // floor vertex.
803 if (p3 != LIGHT_RADIAL) {
804 for (i = 0; i < 4; i++) {
805 if (v[i] > MAX_LIGHT_VAL) {
806 return (ERR_RANGE);
807 }
808 }
809 }
810
811 // Compare against it and figure out which set of lighting
812 // values to use. Plain lighting cares about 0 vs 1, everyone
813 // else is 0 & 1 vs 2 & 3.
814 light_state = p2 >> 28;
815 if (p3 == LIGHT_PLAIN_ALT) {
816 if (light_state) {
817 targ1 = v[0];
818 otarg1 = v[1];
819 } else {
820 targ1 = v[1];
821 otarg1 = v[0];
822 }
823 } else {
824 if (light_state) {
825 targ1 = v[0];
826 otarg1 = v[2];
827 targ2 = v[1];
828 otarg2 = v[3];
829 } else {
830 targ1 = v[2];
831 otarg1 = v[0];
832 targ2 = v[3];
833 otarg2 = v[1];
834 }
835 }
836
837 // Toggle state if done transiting (or not transiting)
838 switch (objs[current_trap].obclass) {
839 case CLASS_TRAP:
840 p = &(objTraps[objs[current_trap].specID].p2);
841 break;
842 case CLASS_FIXTURE:
843 p = &(objFixtures[objs[current_trap].specID].p2);
844 break;
845 }
846 if ((num_steps == -1) || (num_steps == NUM_LIGHT_STEPS)) {
847 if (!light_state)
848 *p |= 0x10000000; // turn on state
849 else
850 *p &= 0xFFFFFFF; // turn off highest bits
851 } else // otherwise, tone down the destination appropriately.
852 {
853 targ1 -= ((targ1 - otarg1) * (NUM_LIGHT_STEPS - num_steps) / NUM_LIGHT_STEPS);
854 if (p3 != LIGHT_PLAIN_ALT)
855 targ2 -= ((targ2 - otarg2) * (NUM_LIGHT_STEPS - num_steps) / NUM_LIGHT_STEPS);
856 }
857
858 // Now go and crank through it...
859 switch (p3) {
860 case LIGHT_PLAIN_ALT:
861 delta = 0;
862 break;
863 case LIGHT_EW_SMOOTH:
864 delta = (targ2 - targ1) / (x2 - x1);
865 break;
866 case LIGHT_NS_SMOOTH:
867 delta = (targ2 - targ1) / (y2 - y1);
868 break;
869 }
870 #ifdef OLD_LIGHT
871 setme = targ1 - otarg1;
872 #endif
873 setme = targ1;
874 for (j = y1; j <= y2; j++) {
875 for (i = x1; i <= x2; i++) {
876 pme = MAP_GET_XY(i, j);
877 // This code now does lighting deltas
878 // Note, however, that it is still confused by light values getting "pegged"
879
880 #define FIX_HALF (FIX_UNIT >> 1)
881 if (p3 == LIGHT_RADIAL) {
882 rdx = fix_make(i, 0) - (objs[current_trap].loc.x << 8);
883 rdy = fix_make(j, 0) - (objs[current_trap].loc.y << 8);
884 rad_delt = fix_fast_pyth_dist(rdx, rdy);
885 if (rad_delt <= fix_make(o1, 0))
886 setme = targ1 + fix_int((rad_delt / o1) * (targ2 - targ1));
887 else
888 setme = me_templight_flr(pme);
889 if (setme > MAX_LIGHT_VAL)
890 setme = MAX_LIGHT_VAL;
891 }
892
893 if (floor) {
894 #ifdef OLD_LIGHT
895 // if ((setme + me_templight_flr(pme) > 0xF) || (me_templight_flr(pme) - setme < 0))
896 // Spew(DSRC_GAMESYS_Traps, ("pegged lights at 0x%x, 0x%x -- %d + %d = %d\n",
897 // i,j,setme,me_templight_flr(pme),setme+me_templight_flr(pme)));
898 new_val = me_templight_flr(pme) + setme;
899 if (newval > 0xF)
900 newval = 0xF;
901 else if (newval < 0)
902 newval = 0;
903 me_templight_flr_set(pme, newval);
904 #endif
905 me_templight_flr_set(pme, setme);
906 } else {
907 #ifdef OLD_LIGHT
908 // if ((setme + me_templight_ceil(pme) > 0xF) || (me_templight_ceil(pme) - setme < 0))
909 // Spew(DSRC_GAMESYS_Traps, ("pegged lights at 0x%x, 0x%x -- %d + %d = %d\n",
910 // i,j,setme,me_templight_ceil(pme),setme+me_templight_ceil(pme)));
911 new_val = me_templight_ceil(pme) + setme;
912 if (newval > 0xF)
913 newval = 0xF;
914 else if (newval < 0)
915 newval = 0;
916 me_templight_ceil_set(pme, newval);
917 #endif
918 me_templight_ceil_set(pme, setme);
919 }
920 if (p3 == LIGHT_EW_SMOOTH) {
921 if (i == x2 - 1)
922 setme = targ2;
923 else
924 setme += delta;
925 }
926 }
927
928 // Now set values for next time around
929 switch (p3) {
930 case LIGHT_EW_SMOOTH:
931 setme = targ1;
932 break;
933 case LIGHT_NS_SMOOTH:
934 if (j == y2 - 1)
935 setme = targ2;
936 else
937 setme += delta;
938 break;
939 }
940 }
941
942 return (OK);
943 }
944
trap_damage_func(int p1,int p2,int p3,int p4)945 errtype trap_damage_func(int p1, int p2, int p3, int p4) {
946 short dval;
947
948 // Can't be inverted!
949 dval = qdata_get(p1);
950 if (dval > 0)
951 damage_object(PLAYER_OBJ, EXPLOSION_TYPE, dval, 0);
952
953 dval = qdata_get(p2 & 0xFFFF);
954 if (!(p2 & 0x10000))
955 damage_object(PLAYER_OBJ, dval, p2 >> 24, 0x01);
956 else
957 player_struct.hit_points = lg_min((short)player_struct.hit_points + dval, PLAYER_MAX_HP);
958
959 dval = qdata_get(p3 & 0xFFFF);
960 if (p3 < 0x10000)
961 player_struct.energy -= dval;
962 else
963 player_struct.energy += dval;
964
965 dval = qdata_get(p4 & 0xFFFF);
966 if (p4 < 0x10000)
967 player_struct.fatigue += dval;
968 else
969 player_struct.fatigue -= dval;
970
971 chg_set_flg(VITALS_UPDATE);
972 return (OK);
973 }
974
975 uchar fake_endgame = FALSE;
976 #define ENDGAME_TICKS CIT_CYCLE * 2
977
trap_sfx_func(int p1,int p2,int p3,int p4)978 errtype trap_sfx_func(int p1, int p2, int p3, int p4) {
979 extern short fr_solidfr_time;
980 extern short fr_sfx_time;
981 extern short fr_surge_time;
982 extern char surg_fx_frame;
983 short scr_fx, sfx_time, sound_fx;
984 short wacky, wacky_sev;
985 extern short surge_duration;
986 extern ulong player_death_time;
987
988 sound_fx = qdata_get(p1 & 0xFFFF);
989 scr_fx = qdata_get(p3);
990 sfx_time = qdata_get(p4);
991 wacky = qdata_get(p2 & 0xFFFF);
992 wacky_sev = qdata_get(p2 >> 16);
993
994 if (sound_fx)
995 play_digi_fx(sound_fx, qdata_get(p1 >> 16));
996
997 switch (wacky) {
998 // Power Surge effect
999 case 1:
1000 if (fr_surge_time == 0) {
1001 fr_surge_time = surge_duration;
1002 surg_fx_frame = 0;
1003 play_digi_fx(SFX_SURGE, 1);
1004 }
1005 break;
1006
1007 // Shake that booty (or head)
1008 case 2:
1009 // if (!sound_fx)
1010 play_digi_fx(SFX_RUMBLE, 2);
1011 fr_global_mod_flag(FR_SFX_SHAKE, FR_SFX_MASK);
1012 fr_sfx_time = CIT_CYCLE * 4; // 4 seconds of shake
1013 break;
1014
1015 // Fake endgame
1016 case 3: {
1017 extern void physics_zero_all_controls();
1018 extern ulong secret_sfx_time;
1019 physics_zero_all_controls();
1020 secret_render_fx = FAKEWIN_REND_SFX;
1021 secret_sfx_time = *tmd_ticks;
1022 fr_surge_time = surge_duration;
1023 chg_set_sta(GL_CHG_2);
1024 } break;
1025
1026 // Teleport special effect
1027 case 4:
1028 fr_global_mod_flag(FR_SFX_TELEPORT, FR_SFX_MASK);
1029 fr_sfx_time = CIT_CYCLE; // 1 second of teleport effect
1030 break;
1031
1032 case 5: {
1033 // let's do some damage static!
1034 extern void set_dmg_percentage(int which, ubyte percent);
1035 set_dmg_percentage(DMG_BLOOD, 100); // 100 is the amount of static (100/255) is the percent of static
1036 } break;
1037 }
1038 switch (scr_fx) {
1039 case 1:
1040 fr_global_mod_flag(FR_SOLIDFR_SLDCLR, FR_SOLIDFR_MASK);
1041 fr_solidfr_color = GRENADE_COLOR;
1042 break;
1043 case 2:
1044 fr_global_mod_flag(FR_SOLIDFR_SLDCLR, FR_SOLIDFR_MASK);
1045 fr_solidfr_color = RED_BASE;
1046 break;
1047 case 3:
1048 fr_global_mod_flag(FR_SOLIDFR_STATIC, FR_SOLIDFR_MASK);
1049 break;
1050 case 4: {
1051 extern short vhold_shift;
1052 fr_global_mod_flag(FR_SFX_VHOLD, FR_SFX_MASK);
1053 vhold_shift = 0;
1054 break;
1055 }
1056 }
1057 if (scr_fx) {
1058 if (scr_fx >= 4)
1059 fr_sfx_time = sfx_time;
1060 else
1061 fr_solidfr_time = sfx_time;
1062 }
1063 return (OK);
1064 }
1065
trap_create_obj_func(int p1,int p2,int p3,int p4)1066 errtype trap_create_obj_func(int p1, int p2, int p3, int p4) {
1067 ObjID new_id, oid;
1068 ObjLoc new_loc;
1069
1070 TRACE("%s: trigger", __FUNCTION__);
1071
1072 if ((p1 & 0xFFFF) == OBJ_NULL) {
1073 return (ERR_NOEFFECT);
1074 }
1075
1076 // use questvar if asked for
1077 oid = qdata_get(p1 & 0xFFFF);
1078
1079 if (!objs[oid].active) {
1080 return (ERR_NOEFFECT);
1081 }
1082
1083 // We are okay to go, so clone the darned thing
1084 if ((p1 > 0xFFFF) && !(p1 & 0x10000000))
1085 new_id = oid;
1086 else {
1087 new_id = obj_create_clone(oid);
1088
1089 // set the QV, if appropriate
1090 qdata_set(p1 >> 16, new_id);
1091 }
1092
1093 // Now move it to where the trap says
1094 new_loc = objs[oid].loc;
1095 if (p2 < 0x4000) {
1096 p2 = qdata_get(p2);
1097 new_loc.x = (p2 << 8) | (new_loc.x & 0xFF);
1098 }
1099 if (p3 < 0x4000) {
1100 p3 = qdata_get(p3);
1101 new_loc.y = (p3 << 8) | (new_loc.y & 0xFF);
1102 }
1103 if (p4 < 0x4000) {
1104 new_loc.z = qdata_get(p4);
1105 }
1106 obj_move_to(new_id, &new_loc, TRUE);
1107 obj_physics_refresh_area(OBJ_LOC_BIN_X(new_loc), OBJ_LOC_BIN_Y(new_loc), TRUE);
1108 return (OK);
1109 }
1110
trap_questbit_func(int p1,int p2,int p3,int p4)1111 errtype trap_questbit_func(int p1, int p2, int p3, int p4) {
1112 char message_buf[100];
1113 short qarg, mod;
1114 qarg = p2 & 0xFFFF;
1115 mod = p2 >> 16;
1116
1117 TRACE("%s: trigger", __FUNCTION__);
1118
1119 if ((p1 & 0xF000) == 0)
1120 p1 |= 0x2000;
1121
1122 if (p1 == 0x2091) // KLC - special hack for auto shutoff of on-line help.
1123 {
1124 olh_active = FALSE; // KLC - this is kept in a global now.
1125
1126 gShockPrefs.goOnScreenHelp = FALSE; // Yeah, got to update this one too and
1127 SavePrefs(); // save the prefs out to disk.
1128 return (OK);
1129 }
1130
1131 if (p1 & 0x2000) {
1132 switch (qarg) {
1133 case 0:
1134 qdata_set(p1, FALSE);
1135 break;
1136 case 1:
1137 qdata_set(p1, TRUE);
1138 break;
1139 default:
1140 if (qdata_get(p1))
1141 qdata_set(p1, FALSE);
1142 else
1143 qdata_set(p1, TRUE);
1144 break;
1145 }
1146 if (((p1 & 0xFFF) == REACTOR_BOOM_QB) && (qdata_get(p1)))
1147 do_special_reactor_hack();
1148 } else {
1149 switch (mod) {
1150 case 0:
1151 qdata_set(p1, qdata_get(qarg));
1152 break;
1153 case 1:
1154 qdata_set(p1, qdata_get(p1) + qdata_get(qarg));
1155 break;
1156 case 2:
1157 qdata_set(p1, qdata_get(p1) - qdata_get(qarg));
1158 break;
1159 case 3:
1160 qdata_set(p1, qdata_get(p1) * qdata_get(qarg));
1161 break;
1162 case 4:
1163 qdata_set(p1, qdata_get(p1) / qdata_get(qarg));
1164 break;
1165 case 5:
1166 qdata_set(p1, qdata_get(p1) % qdata_get(qarg));
1167 break;
1168 }
1169 }
1170 if (qdata_get(p1)) {
1171 if (qdata_get(p3)) {
1172 message_info(get_string(REF_STR_TrapZeroMessage + qdata_get(p3), message_buf, 80));
1173 #ifdef AUDIOLOGS
1174 audiolog_bark_play(qdata_get(p3));
1175 #endif
1176 *trap_use_message = TRUE;
1177 }
1178 } else {
1179 if (qdata_get(p4)) {
1180 message_info(get_string(REF_STR_TrapZeroMessage + qdata_get(p4), message_buf, 80));
1181 #ifdef AUDIOLOGS
1182 audiolog_bark_play(qdata_get(p4));
1183 #endif
1184 *trap_use_message = TRUE;
1185 }
1186 }
1187
1188 return (OK);
1189 }
1190
1191 extern uchar alternate_death;
1192
1193 extern bool gPlayingGame;
1194 extern bool gDeadPlayerQuit;
1195 extern bool gGameCompletedQuit;
1196
trap_cutscene_func(int p1,int p2,int p3,int p4)1197 errtype trap_cutscene_func(int p1, int p2, int p3, int p4) {
1198 short cs = qdata_get(p1);
1199
1200 INFO("Playing cutscene %i %i\n", cs, qdata_get(p2));
1201
1202 //if (qdata_get(p1) == 0) // KLC - if we are to play the endgame cutscene
1203 //{
1204 //gGameCompletedQuit = TRUE;
1205
1206 //gPlayingGame = FALSE; // Hop out of the game loop.
1207 // KLC play_cutscene(qdata_get(p1), qdata_get(p2));
1208
1209 play_cutscene(WIN_CUTSCENE, TRUE);
1210 setup_mode = SETUP_CREDITS;
1211 extern int WonGame_ShowStats;
1212 WonGame_ShowStats = 1;
1213 //}
1214
1215 alternate_death = (qdata_get(p2) != 0);
1216 return (OK);
1217 }
1218
trap_terrain_func(int p1,int p2,int p3,int p4)1219 errtype trap_terrain_func(int p1, int p2, int p3, int p4) {
1220 TRACE("%s: trigger", __FUNCTION__);
1221 MapElem *pme;
1222 uchar reprocess = FALSE;
1223 extern void rendedit_process_tilemap(FullMap * map, LGRect * r, uchar newMap);
1224 LGRect bounds;
1225
1226 pme = MAP_GET_XY(qdata_get(p1), qdata_get(p2));
1227 if (pme == NULL)
1228 return (ERR_NOEFFECT);
1229 if (p3 < 0x4000) {
1230 me_tiletype_set(pme, qdata_get(p3));
1231 reprocess = TRUE;
1232 }
1233 if (p4 < 0x4000) {
1234 me_param_set(pme, qdata_get(p4));
1235 reprocess = TRUE;
1236 }
1237 if (reprocess) {
1238 bounds.ul.x = bounds.lr.x = qdata_get(p1);
1239 bounds.ul.y = bounds.lr.y = qdata_get(p2);
1240 rendedit_process_tilemap(global_fullmap, &bounds, FALSE);
1241 }
1242 obj_physics_refresh_area(qdata_get(p1), qdata_get(p2), TRUE);
1243 return (OK);
1244 }
1245
trap_height_func(int p1,int p2,int p3,int p4)1246 errtype trap_height_func(int p1, int p2, int p3, int p4) {
1247 TRACE("%s: trigger", __FUNCTION__);
1248 MapElem *pme;
1249 HeightSchedEvent hse;
1250 ushort use_val;
1251 uchar x, y;
1252 char steps;
1253 char ht;
1254 uchar did_sfx = FALSE;
1255 extern uchar register_h_event(uchar x, uchar y, uchar floor, char *sem, char *key, uchar no_sfx);
1256
1257 x = qdata_get(p1);
1258 y = qdata_get(p2);
1259 pme = MAP_GET_XY(x, y);
1260
1261 hse.timestamp = TICKS2TSTAMP(player_struct.game_time + (CIT_CYCLE * HEIGHT_STEP_TIME) / HEIGHT_TIME_UNIT) + 1;
1262 use_val = qdata_get(p3 & 0xFFFF);
1263 if (use_val < 0x100) {
1264 if (me_height_flr(pme) != use_val) {
1265 hse.type = FLOOR_SCHED_EVENT;
1266
1267 steps = use_val - me_height_flr(pme);
1268 hse.steps_remaining = steps;
1269 hse.sfx_code = 0;
1270 if (p4 >> 16)
1271 did_sfx = TRUE;
1272 if (register_h_event(x, y, TRUE, &hse.semaphor, &hse.key, p4 >> 16)) {
1273 schedule_event(&(global_fullmap->sched[MAP_SCHEDULE_GAMETIME]), (SchedEvent *)&hse);
1274 }
1275 }
1276 }
1277 use_val = qdata_get(p3 >> 16);
1278 if (use_val < 0x100) {
1279 // convert to make life easier on Erik
1280 use_val = 32 - use_val;
1281
1282 if (me_height_ceil(pme) != use_val) {
1283 hse.type = CEIL_SCHED_EVENT;
1284 ht = me_height_ceil(pme);
1285 steps = use_val - ht;
1286 hse.steps_remaining = steps;
1287 hse.sfx_code = p4 >> 16;
1288 if (register_h_event(x, y, FALSE, &hse.semaphor, &hse.key, (did_sfx) ? FALSE : (p4 >> 16))) {
1289 schedule_event(&(global_fullmap->sched[MAP_SCHEDULE_GAMETIME]), (SchedEvent *)&hse);
1290 }
1291 }
1292 }
1293 obj_physics_refresh_area(x, y, TRUE);
1294 return (OK);
1295 }
1296
real_instance_func(int p1,int p2,int p3,int p4)1297 errtype real_instance_func(int p1, int p2, int p3, int p4) {
1298 ObjSpecID osid;
1299 if (p1 == 0)
1300 return (OK);
1301 if (!objs[p1].active) {
1302 return (ERR_NOEFFECT);
1303 }
1304 osid = objs[p1].specID;
1305 switch (objs[p1].obclass) {
1306 case CLASS_BIGSTUFF:
1307 if (p2 != -1)
1308 objBigstuffs[osid].cosmetic_value = p2;
1309 if (p3 != -1)
1310 objBigstuffs[osid].data1 = p3;
1311 if (p4 != -1)
1312 objBigstuffs[osid].data2 = p4;
1313 break;
1314 case CLASS_DOOR:
1315 if (p2 != -1)
1316 objDoors[osid].locked = p2;
1317 if (p3 != -1) {
1318 objDoors[osid].stringnum = p3 >> 8;
1319 objDoors[osid].cosmetic_value = p3 & 0xFF;
1320 }
1321 if (p4 != -1) {
1322 objDoors[osid].access_level = p4 >> 8;
1323 objDoors[osid].autoclose_time = p4 & 0xFF;
1324 }
1325 break;
1326 case CLASS_FIXTURE:
1327 case CLASS_TRAP:
1328 set_trap_data(p1, p2, p3);
1329 break;
1330 }
1331 return (OK);
1332 }
1333
trap_instance_func(int p1,int p2,int p3,int p4)1334 errtype trap_instance_func(int p1, int p2, int p3, int p4) {
1335 real_instance_func(qdata_get(p1 & 0xFFFF), p2, p3, p4);
1336 real_instance_func(qdata_get(p1 >> 16), p2, p3, p4);
1337 return (OK);
1338 }
1339
animate_callback_func(ObjID id,intptr_t user_data)1340 void animate_callback_func(ObjID id, intptr_t user_data) {
1341 int p3;
1342
1343 p3 = (int)user_data;
1344
1345 if (BIT_SET(p3, 17))
1346 do_multi_stuff(p3 & 0x7FFF);
1347 else
1348 real_animate_func(id, 0, p3, 0);
1349 }
1350
real_animate_func(ObjID id,int p2,int p3,int p4)1351 errtype real_animate_func(ObjID id, int p2, int p3, int p4) {
1352 errtype retval = OK;
1353 int frames;
1354 uchar reverse;
1355
1356 if (id == 0)
1357 return (OK);
1358 if (!objs[id].active) {
1359 return (ERR_NOEFFECT);
1360 }
1361
1362 // Don't allow animation updates of "destroyed" screens
1363 if (objs[id].info.current_hp == 0)
1364 return (OK);
1365
1366 frames = objBigstuffs[objs[id].specID].cosmetic_value;
1367 if (frames == 0)
1368 frames = 4;
1369
1370 if (p4 != 0)
1371 remove_obj_from_animlist(id);
1372 if (p2 == 0) {
1373 reverse = BIT_SET(p2, 15);
1374 if (p3 & 0xF0000000)
1375 frames = p3 >> 28;
1376 real_instance_func(id, frames, -1, p3 & 0x7FFF);
1377 remove_obj_from_animlist(id);
1378 objs[id].info.current_frame = reverse ? frames - 1 : 0;
1379 add_obj_to_animlist(id, TRUE, BIT_SET(p3, 15), BIT_SET(p3, 16), 0, 0, 0, 0);
1380 } else {
1381 reverse = BIT_SET(p2, 15);
1382 if (p2 & 0xF0000000)
1383 frames = p2 >> 28;
1384 real_instance_func(id, frames, -1, p2 & 0x7FFF);
1385 objs[id].info.current_frame = reverse ? frames - 1 : 0;
1386 if (p3 != 0)
1387 retval = add_obj_to_animlist(id, FALSE, BIT_SET(p2, 15), BIT_SET(p2, 16), 0, 6, p3, ANIMCB_REMOVE);
1388 else
1389 retval = add_obj_to_animlist(id, FALSE, BIT_SET(p2, 15), BIT_SET(p2, 16), 0, 0, 0, 0);
1390 }
1391 return (retval);
1392 }
1393
trap_animate_func(int p1,int p2,int p3,int p4)1394 errtype trap_animate_func(int p1, int p2, int p3, int p4) {
1395 real_animate_func(qdata_get(p1 & 0xFFFF), p2, p3, p4);
1396 real_animate_func(qdata_get(p1 >> 16), p2, p3, p4);
1397 return (OK);
1398 }
1399
1400 // Note that this blows away the usual shodan time countdown,
1401 // so can only be used in endgame!
1402 uchar *shodan_bitmask = NULL;
1403 grs_bitmap shodan_draw_fs;
1404 grs_bitmap shodan_draw_normal;
1405
hack_shodan_conquer_func(char c)1406 void hack_shodan_conquer_func(char c) {
1407 extern void begin_shodan_conquer_fx(uchar begin);
1408 extern char thresh_fail;
1409 extern uchar shodan_phase_in(uchar * bitmask, short x, short y, short w, short h, short num, uchar dir);
1410 shodan_bitmask = tmap_static_mem;
1411 LG_memset(shodan_bitmask, 0, SHODAN_BITMASK_SIZE / 8);
1412 shodan_draw_fs.bits = tmap_static_mem + (SHODAN_BITMASK_SIZE / 8);
1413 shodan_draw_normal.bits = shodan_draw_fs.bits + (320 * 200);
1414 load_res_bitmap(&shodan_draw_fs, SHODAN_FULLSCRN_CONQUER_REF, FALSE);
1415 load_res_bitmap(&shodan_draw_normal, SHODAN_CONQUER_REF, FALSE);
1416 thresh_fail = 0;
1417 time_until_shodan_avatar = player_struct.game_time + SHODAN_INTERVAL;
1418
1419 begin_shodan_conquer_fx(TRUE);
1420 }
1421
hack_armageddon_func(int otrip,int x0,int y0,int r)1422 void hack_armageddon_func(int otrip, int x0, int y0, int r) {
1423 int ulx, uly, lrx, lry;
1424 int i, j;
1425 ObjRefID oref;
1426 ObjID oid;
1427
1428 ulx = x0 - r;
1429 uly = y0 - r;
1430 lrx = x0 + r;
1431 lry = y0 + r;
1432
1433 for (j = uly; j <= lry; j++) {
1434 for (i = ulx; i <= lrx; i++) {
1435 oref = me_objref(MAP_GET_XY(i, j));
1436 while (oref != OBJ_REF_NULL) {
1437 // Make sure we only do this to the "true" ref
1438 // on each obj
1439 oid = objRefs[oref].obj;
1440 if ((objs[oid].ref == oref) && (objs[oid].obclass == CLASS_CRITTER)) {
1441 if (ID2TRIP(oid) == otrip)
1442 ADD_DESTROYED_OBJECT(oid);
1443 }
1444 oref = objRefs[oref].next;
1445 }
1446 }
1447 }
1448 }
1449
hack_area_spew(int p2,int p3,int p4)1450 void hack_area_spew(int p2, int p3, int p4) {
1451 int ulx, uly, lrx, lry;
1452 int x, y;
1453 MapElem *pme;
1454 uchar state;
1455 ObjID obj;
1456
1457 ulx = lg_min(OBJ_LOC_BIN_X(objs[current_trap].loc), OBJ_LOC_BIN_X(objs[(ObjID)p2].loc));
1458 lrx = lg_max(OBJ_LOC_BIN_X(objs[current_trap].loc), OBJ_LOC_BIN_X(objs[(ObjID)p2].loc));
1459 uly = lg_min(OBJ_LOC_BIN_Y(objs[current_trap].loc), OBJ_LOC_BIN_Y(objs[(ObjID)p2].loc));
1460 lry = lg_max(OBJ_LOC_BIN_Y(objs[current_trap].loc), OBJ_LOC_BIN_Y(objs[(ObjID)p2].loc));
1461
1462 obj = (ObjID)(p3 & 0xFFFF);
1463 if (obj == OBJ_NULL)
1464 state = 0;
1465 else
1466 state = (objs[obj].info.current_frame == 0);
1467 state = state ^ ((p3 >> 16) != 0);
1468
1469 for (x = ulx; x <= lrx; x++) {
1470 for (y = uly; y <= lry; y++) {
1471 pme = MAP_GET_XY(x, y);
1472 switch (p4) {
1473 case 0:
1474 me_hazard_rad_set(pme, state);
1475 break;
1476 }
1477 }
1478 }
1479 }
1480
hack_multi_trans(int trip,int newtype)1481 void hack_multi_trans(int trip, int newtype) {
1482 ObjSpecID osid;
1483 ObjID oid;
1484
1485 osid = objCritters[0].id;
1486 while (osid != OBJ_SPEC_NULL) {
1487 oid = objCritters[osid].id;
1488 if (ID2TRIP(oid) == trip) {
1489 if (newtype > 0xF)
1490 do_destroy(oid);
1491 else
1492 trap_transmogrify_func(oid, newtype, 0, 0);
1493 }
1494 osid = objCritters[osid].next;
1495 }
1496 }
1497
hack_change_comparator(int p2,int p3)1498 void hack_change_comparator(int p2, int p3) {
1499 ObjSpecID osid;
1500
1501 osid = objs[p2].specID;
1502 switch (objs[p2].obclass) {
1503 case CLASS_TRAP:
1504 objTraps[osid].comparator = p3;
1505 return;
1506 case CLASS_FIXTURE:
1507 objFixtures[osid].comparator = p3;
1508 return;
1509 default:
1510 return;
1511 }
1512 }
1513
1514 // Wow. Pretty non-general here.
1515 // Diego taunts the player.
1516 #define FIXANG_OCT (FIXANG_PI / 4)
hack_taunt_diego(int p2,int p3)1517 void hack_taunt_diego(int p2, int p3) {
1518 fixang plrh;
1519
1520 // triggers trap p3 if and only if player's facing is within
1521 // an octant either way of fixang p2.
1522 plrh = fixang_from_phys_angle(phys_angle_from_obj(objs[PLAYER_OBJ].loc.h));
1523 plrh -= p2;
1524 if ((plrh < FIXANG_OCT) || (plrh >= ((fixang)-FIXANG_OCT)))
1525 do_multi_stuff(p3);
1526 }
1527
1528 #define REPULSOR_TOGGLE_HACK 0x1
1529 #define REPULSOR_UP 0
1530 #define REPULSOR_DOWN 1
1531
1532 #define REACTOR_DIGIT_HACK 0x2
1533 #define REACTOR_COMBO_QVAR 0x1f
1534 #define SCREEN_DIGIT_0 (REFINDEX(REF_STR_ScreenZero + 0x34))
1535
1536 #define REACTOR_KEYPAD_HACK 0x3
1537
1538 #define FIXTURE_FRAME_HACK 0x4
1539
1540 #define DOOR_HACK 0x5
1541
1542 #define GAME_OVER_HACK 0x6
1543
1544 #define TURN_OBJECT_HACK 0x7
1545
1546 #define ARMAGEDDON_HACK 0x8
1547
1548 #define SHODAN_CONQUER_HACK 0x9
1549
1550 #define COMPARATOR_HACK 0xA
1551
1552 #define PLOTWARE_HACK 0xB
1553
1554 #define AREASPEW_HACK 0xC
1555
1556 #define DIEGO_HACK 0xD
1557
1558 #define PANEL_REF_HACK 0xE
1559
1560 #define EARTH_DESTROYED_HACK 0xF
1561
1562 #define MULTI_TRANSMOG_HACK 0x10
1563
trap_hack_func(int p1,int p2,int p3,int p4)1564 errtype trap_hack_func(int p1, int p2, int p3, int p4) {
1565 uchar door_moving(ObjID door, uchar dir);
1566 void plotware_showpage(uchar page);
1567 void check_panel_ref(uchar puntme);
1568 void email_slam_hack(short which);
1569
1570 TRACE("%s: trigger", __FUNCTION__);
1571
1572 // As we need hacks in the game, just add new
1573 // cases here to do your particular hack.
1574 switch (p1) {
1575 case REPULSOR_TOGGLE_HACK:
1576 if (ID2TRIP(p2) == REPULSOR_TRIPLE) {
1577 Obj *repul = &objs[p2];
1578 ObjRefID oref;
1579 ObjID oid;
1580 uint *upness = &(objTraps[repul->specID].p4);
1581 int nu_tmap, old_tmap, nu_frame;
1582 MapElem *pme;
1583 ObjLoc where = repul->loc;
1584 extern errtype obj_physics_refresh(short x, short y, uchar use_floor);
1585
1586 switch (p4) {
1587 case 0:
1588 *upness = (*upness == REPULSOR_UP) ? REPULSOR_DOWN : REPULSOR_UP;
1589 break;
1590 case 1:
1591 *upness = REPULSOR_UP;
1592 break;
1593 case 2:
1594 *upness = REPULSOR_DOWN;
1595 break;
1596 }
1597
1598 if (*upness == REPULSOR_UP) {
1599 nu_tmap = (p3 >> 8) & 0x1F;
1600 old_tmap = p3 & 0x1F;
1601 nu_frame = 0;
1602 } else {
1603 old_tmap = (p3 >> 8) & 0x1F;
1604 nu_tmap = p3 & 0x1F;
1605 nu_frame = 1;
1606 }
1607
1608 pme = MAP_GET_XY(OBJ_LOC_BIN_X(where), OBJ_LOC_BIN_Y(where));
1609 if (nu_tmap != old_tmap) {
1610 if (me_tmap_flr(pme) == old_tmap)
1611 me_tmap_flr_set(pme, nu_tmap);
1612 if (me_tmap_ceil(pme) == old_tmap)
1613 me_tmap_ceil_set(pme, nu_tmap);
1614 }
1615 oref = me_objref(pme);
1616 while (oref != OBJ_REF_NULL) {
1617 oid = objRefs[oref].obj;
1618 if ((objs[oid].ref == oref) && ID2TRIP(oid) == REPULSWALL_TRIPLE)
1619 objs[oid].info.current_frame = nu_frame;
1620 oref = objRefs[oref].next;
1621 }
1622 obj_physics_refresh(OBJ_LOC_BIN_X(where), OBJ_LOC_BIN_Y(where), FALSE);
1623 }
1624 break;
1625 case REACTOR_DIGIT_HACK:
1626 if (objs[p2].obclass == CLASS_BIGSTUFF) {
1627 uint combo;
1628
1629 combo = (QUESTVAR_GET(REACTOR_COMBO_QVAR) << 12) | (QUESTVAR_GET(REACTOR_COMBO_QVAR + 1));
1630
1631 objBigstuffs[ID2SPEC(p2)].data2 = 0x100 | (SCREEN_DIGIT_0 + (0xF & (combo >> (4 * (6 - p3)))));
1632 }
1633 break;
1634 case REACTOR_KEYPAD_HACK:
1635 if (objs[p2].obclass == CLASS_FIXTURE) {
1636 uint *field = &(objFixtures[ID2SPEC(p2)].p1);
1637
1638 field += (p3 - 1);
1639 *field = (*field & ~0xFFFF) | QUESTVAR_GET(p4);
1640 }
1641 break;
1642 case FIXTURE_FRAME_HACK:
1643 objs[p2].info.current_frame = p3;
1644 if (p4)
1645 mfd_notify_func(MFD_FIXTURE_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, TRUE);
1646 break;
1647 case DOOR_HACK:
1648 if (objs[p2].obclass == CLASS_DOOR) {
1649
1650 uchar closed;
1651
1652 // p3 indicates operation on door:
1653 // 0=null; 1=open; 2=close; 3=toggle; 4=disable autoclose
1654
1655 if (p3 < 4) {
1656 closed = door_moving(p2, TRUE) || DOOR_REALLY_CLOSED(p2);
1657
1658 if (p3 && ((p3 == 3) || ((p3 == 1) == closed)))
1659 do_multi_stuff(p2);
1660 } else {
1661 objDoors[objs[p2].specID].autoclose_time = NEVER_AUTOCLOSE_COOKIE;
1662 remove_obj_from_animlist(p2);
1663 }
1664 }
1665 break;
1666 case GAME_OVER_HACK: {
1667 extern void player_dead(void);
1668 extern int curr_alog;
1669 extern char secret_pending_hack;
1670 if (curr_alog != -1)
1671 secret_pending_hack = 1;
1672 else {
1673 gDeadPlayerQuit = TRUE; // The player is dead.
1674 //gPlayingGame = FALSE; // Hop out of the game loop.
1675
1676 INFO("GAME OVER!\n");
1677 play_cutscene(DEATH_CUTSCENE, FALSE);
1678 }
1679 break;
1680 }
1681 case TURN_OBJECT_HACK: {
1682 short head, lo, hi, phb;
1683
1684 phb = (p3 >> 24) & 0xF;
1685 switch (phb) {
1686 case 1:
1687 head = objs[p2].loc.p;
1688 break;
1689 case 2:
1690 head = objs[p2].loc.b;
1691 break;
1692 default:
1693 head = objs[p2].loc.h;
1694 break;
1695 }
1696 hi = p4 & 0xFF;
1697 if (hi == 0)
1698 hi = 255;
1699 lo = (p4 >> 8) & 0xFF;
1700 if (!(p3 & 0xFF))
1701 p3 |= 0x10;
1702 head += (p3 & 0xFF0000) ? -(p3 & 0xFF) : (p3 & 0xFF);
1703 if ((p3 & 0xFF00) == 0) {
1704 if (hi != lo)
1705 head = lo + (head - lo) % (hi - lo);
1706 } else {
1707 if (head > hi) {
1708 head = hi;
1709 p3 = p3 ^ 0x10000;
1710 } else if (head < lo) {
1711 head = lo;
1712 p3 = p3 ^ 0x10000;
1713 }
1714 }
1715 switch (phb) {
1716 case 1:
1717 objs[p2].loc.p = head;
1718 break;
1719 case 2:
1720 objs[p2].loc.b = head;
1721 break;
1722 default:
1723 objs[p2].loc.h = head;
1724 break;
1725 }
1726 objTraps[objs[current_trap].specID].p3 = p3;
1727 break;
1728 }
1729 case ARMAGEDDON_HACK:
1730 hack_armageddon_func(p2, OBJ_LOC_BIN_X(objs[current_trap].loc), OBJ_LOC_BIN_Y(objs[current_trap].loc), p3);
1731 break;
1732 case SHODAN_CONQUER_HACK:
1733 hack_shodan_conquer_func(p2);
1734 break;
1735 case COMPARATOR_HACK:
1736 hack_change_comparator(p2, p3);
1737 break;
1738 case PLOTWARE_HACK:
1739 // show page p2
1740 plotware_showpage(p2);
1741 break;
1742 case AREASPEW_HACK:
1743 hack_area_spew(p2, p3, p4);
1744 break;
1745 case DIEGO_HACK:
1746 hack_taunt_diego(p2, p3);
1747 break;
1748 case PANEL_REF_HACK:
1749 if (player_struct.panel_ref == p2)
1750 check_panel_ref(TRUE);
1751 break;
1752 case EARTH_DESTROYED_HACK:
1753 email_slam_hack(0x01D);
1754 break;
1755 case MULTI_TRANSMOG_HACK:
1756 hack_multi_trans(p2, p3);
1757 break;
1758 }
1759 return (OK);
1760 }
1761
do_multi_stuff(ObjID id)1762 errtype do_multi_stuff(ObjID id) {
1763 ObjID other;
1764
1765 if (id != OBJ_NULL) {
1766 if (objs[id].obclass == CLASS_TRAP)
1767 trap_activate(id, trap_use_message);
1768 else {
1769 switch (objs[id].obclass) {
1770 case CLASS_DOOR:
1771 objDoors[objs[id].specID].locked = 0;
1772 objDoors[objs[id].specID].access_level = 0;
1773 other = objDoors[objs[id].specID].other_half;
1774 if ((other) && objs[other].obclass == CLASS_DOOR) {
1775 // door has a door other half. Unlock it too.
1776 objDoors[objs[other].specID].locked = 0;
1777 objDoors[objs[other].specID].access_level = 0;
1778 }
1779 break;
1780 }
1781 object_use(id, FALSE, OBJ_NULL);
1782 }
1783 }
1784 return (OK);
1785 }
1786
1787 // number of multi delay units in a second
1788 #define MULTI_TIME_UNIT 10
1789
do_timed_multi_stuff(int p)1790 errtype do_timed_multi_stuff(int p) {
1791 TrapSchedEvent new_event;
1792
1793 if (p >> 16) {
1794 // Time delay
1795 new_event.timestamp =
1796 TICKS2TSTAMP(player_struct.game_time + (ushort)(CIT_CYCLE * (p >> 16)) / MULTI_TIME_UNIT) + 1;
1797 new_event.type = TRAP_SCHED_EVENT;
1798 new_event.target_id = qdata_get(p & 0xFFFF);
1799 new_event.source_id = -1;
1800 schedule_event(&(global_fullmap->sched[MAP_SCHEDULE_GAMETIME]), (SchedEvent *)&new_event);
1801 } else {
1802 // Do immediately
1803 do_multi_stuff(qdata_get(p & 0xFFFF));
1804 }
1805 return (OK);
1806 }
1807
trap_multi_func(int p1,int p2,int p3,int p4)1808 errtype trap_multi_func(int p1, int p2, int p3, int p4) {
1809 do_timed_multi_stuff(p1);
1810 do_timed_multi_stuff(p2);
1811 do_timed_multi_stuff(p3);
1812 do_timed_multi_stuff(p4);
1813 return (OK);
1814 }
1815
do_destroy(int victim_data)1816 errtype do_destroy(int victim_data) {
1817 ObjID victim;
1818
1819 victim = qdata_get(victim_data);
1820
1821 if (victim != OBJ_NULL)
1822 ADD_DESTROYED_OBJECT(victim);
1823 return (OK);
1824 }
1825
trap_destroy_object_func(int p1,int p2,int p3,int p4)1826 errtype trap_destroy_object_func(int p1, int p2, int p3, int p4) {
1827 char message_buf[100];
1828
1829 do_destroy(p1);
1830 do_destroy(p2);
1831 do_destroy(p3);
1832 if (p4 > 0) {
1833 message_info(get_string(REF_STR_TrapZeroMessage + p4, message_buf, 80));
1834 #ifdef AUDIOLOGS
1835 audiolog_bark_play(p4);
1836 #endif
1837 *trap_use_message = TRUE;
1838 }
1839 return (OK);
1840 }
1841
trap_plot_clock_func(int p1,int p2,int p3,int p4)1842 errtype trap_plot_clock_func(int p1, int p2, int p3, int p4) { return (OK); }
1843
trap_email_func(int mung,int time,int p3,int p4)1844 errtype trap_email_func(int mung, int time, int p3, int p4) {
1845 void add_email_datamunge(short mung, uchar select);
1846
1847 #ifdef DOOM_EMULATION_MODE
1848 if (QUESTVAR_GET(MISSION_DIFF_QVAR) == 0)
1849 return (OK);
1850 #endif
1851 if (time == 0) {
1852 add_email_datamunge(mung, TRUE);
1853 *trap_use_message = TRUE;
1854 } else {
1855 EmailSchedEvent ev;
1856 ev.type = EMAIL_SCHED_EVENT;
1857 ev.timestamp = TICKS2TSTAMP((time << APPROX_CIT_CYCLE_SHFT) + player_struct.game_time);
1858 ev.datamunge = mung;
1859 schedule_event(&game_seconds_schedule, (SchedEvent *)&ev);
1860 }
1861 return (OK);
1862 }
1863
trap_texture_func(int p1,int p2,int p3,int p4)1864 errtype trap_texture_func(int p1, int p2, int p3, int p4) {
1865 ObjID id1, id2;
1866 short cx, cy, minx, maxx, miny, maxy;
1867 int i, src[3], dest[3];
1868 MapElem *pme;
1869
1870 id2 = qdata_get(p1 >> 16);
1871 if (id2 != OBJ_NULL) {
1872 id1 = qdata_get(p1 & 0xFFFF);
1873 if (!objs[id1].active || !objs[id2].active) {
1874 return (OK);
1875 }
1876 minx = lg_min(OBJ_LOC_BIN_X(objs[id1].loc), OBJ_LOC_BIN_X(objs[id2].loc));
1877 maxx = lg_max(OBJ_LOC_BIN_X(objs[id1].loc), OBJ_LOC_BIN_X(objs[id2].loc));
1878 miny = lg_min(OBJ_LOC_BIN_Y(objs[id1].loc), OBJ_LOC_BIN_Y(objs[id2].loc));
1879 maxy = lg_max(OBJ_LOC_BIN_Y(objs[id1].loc), OBJ_LOC_BIN_Y(objs[id2].loc));
1880 } else {
1881 minx = maxx = (p1 & 0xFF00) >> 8;
1882 miny = maxy = (p1 & 0xFF);
1883 }
1884
1885 src[0] = p2 >> 16;
1886 src[1] = p3 >> 16;
1887 src[2] = p4 >> 16;
1888 dest[0] = p2 & 0xFFFF;
1889 dest[1] = p3 & 0xFFFF;
1890 dest[2] = p4 & 0xFFFF;
1891
1892 for (cx = minx; cx <= maxx; cx++) {
1893 for (cy = miny; cy <= maxy; cy++) {
1894 pme = MAP_GET_XY(cx, cy);
1895 for (i = 0; i < 3; i++) {
1896 if ((src[i] >= 0x1000) || (src[i] == me_tmap(pme, i))) {
1897 if (dest[i] < 0x1000) {
1898 me_tmap_set(pme, i, dest[i]);
1899 }
1900 }
1901 }
1902 }
1903 }
1904 return (OK);
1905 }
1906
trap_teleport_func(int targ_x,int targ_y,int targ_z,int targlevel)1907 errtype trap_teleport_func(int targ_x, int targ_y, int targ_z, int targlevel) {
1908 ObjLoc newloc;
1909 errtype errcode = OK;
1910 uchar to_cyber = FALSE;
1911
1912 if (targlevel >= 0x1000)
1913 targlevel = player_struct.level;
1914
1915 // If going between cyber and real, static out the screen during the load
1916 if (targlevel != player_struct.level) {
1917 to_cyber = go_to_different_level(targlevel);
1918 }
1919 if (errcode == OK) {
1920 newloc = objs[PLAYER_OBJ].loc;
1921 if (targ_x < 0x4000) {
1922 targ_x = qdata_get(targ_x);
1923 newloc.x = (targ_x << 8) + 0x80;
1924 }
1925 if (targ_y < 0x4000) {
1926 targ_y = qdata_get(targ_y);
1927 newloc.y = (targ_y << 8) + 0x80;
1928 }
1929 if (targ_z < 0x4000) {
1930 targ_z = qdata_get(targ_z);
1931 newloc.z = targ_z;
1932 }
1933 obj_move_to(PLAYER_OBJ, &newloc, TRUE);
1934 if (to_cyber) {
1935 recall_objloc = newloc;
1936 // KLC if (music_on)
1937 // KLC start_music();
1938 }
1939 } else {
1940 return (ERR_FOPEN);
1941 }
1942 return (OK);
1943 }
1944
trap_expose_func(int dmg,int dtype,int tsecs,int p4)1945 errtype trap_expose_func(int dmg, int dtype, int tsecs, int p4) {
1946 extern void expose_player(byte damage, ubyte type, ushort tsecs);
1947 short damage = qdata_get(dmg & 0xFFFF);
1948
1949 if (dmg & 0x10000) {
1950 damage &= 0xFFFF;
1951 if (dtype == RADIATION_TYPE) {
1952 if (damage <= player_struct.rad_post_expose) {
1953 player_struct.rad_post_expose -= damage;
1954 return OK;
1955 }
1956 damage -= player_struct.rad_post_expose;
1957 player_struct.rad_post_expose = 0;
1958 }
1959 if (dtype == BIO_TYPE) {
1960 if (damage <= player_struct.bio_post_expose) {
1961 player_struct.bio_post_expose -= damage;
1962 return OK;
1963 }
1964 damage -= player_struct.bio_post_expose;
1965 player_struct.bio_post_expose = 0;
1966 }
1967 damage = lg_max(-damage, -128);
1968 }
1969 expose_player((byte)damage, (ubyte)qdata_get(dtype), (ushort)qdata_get(tsecs));
1970 return OK;
1971 }
1972
trap_bark_func(int speaker,int strnum,int color,int hud_bark)1973 errtype trap_bark_func(int speaker, int strnum, int color, int hud_bark) {
1974 int string_id = REF_STR_TrapZeroMessage + qdata_get(strnum);
1975 SchedEvent new_event;
1976 ushort special;
1977 uint timeout = 0, len;
1978
1979 special = (speaker < 0) ? -speaker : 0;
1980
1981 if (hud_bark) {
1982 // just message_info for now
1983 string_message_info(string_id);
1984 #ifdef AUDIOLOGS
1985 audiolog_bark_play(string_id - REF_STR_TrapZeroMessage);
1986 #endif
1987 } else if (special) {
1988 int mug;
1989
1990 mug = (-special == SHODAN_BARK_CODE) ? SHODAN_MUG : DIEGO_MUG;
1991 long_bark(PLAYER_OBJ, mug, string_id, (ubyte)color);
1992 timeout = SHODAN_BARK_TIMEOUT;
1993 } else if ((ObjID)speaker == OBJ_NULL) {
1994 long_bark(PLAYER_OBJ, 0, string_id, (ubyte)color);
1995 timeout = NULL_BARK_TIMEOUT;
1996 } else {
1997 // long_bark(speaker,0,string_id,(ubyte)color);
1998 long_bark(PLAYER_OBJ, 0, string_id, (ubyte)color);
1999 timeout = NULL_BARK_TIMEOUT;
2000 }
2001 if (timeout > 0) {
2002 // convert to sixteenths of a second
2003 timeout = (timeout * 16) / 10;
2004
2005 len = strlen(get_temp_string(string_id));
2006 timeout *= 10 + len;
2007 new_event.timestamp = TICKS2TSTAMP(player_struct.game_time) + timeout;
2008 new_event.type = BARK_SCHED_EVENT;
2009 schedule_event(&game_seconds_schedule, &new_event);
2010 }
2011 return OK;
2012 }
2013
2014 errtype (*trap_functions[])(int, int, int, int) = {
2015 trap_null_func,
2016 trap_teleport_func,
2017 trap_damage_func,
2018 trap_create_obj_func,
2019 trap_questbit_func,
2020 trap_cutscene_func,
2021 trap_multi_func,
2022 trap_main_light_func,
2023 trap_sfx_func,
2024 trap_height_func,
2025 trap_terrain_func,
2026 trap_scheduler_func,
2027 trap_alternating_splitter_func,
2028 trap_destroy_object_func,
2029 trap_plot_clock_func,
2030 trap_email_func,
2031 trap_expose_func,
2032 trap_instance_func,
2033 trap_animate_func,
2034 trap_hack_func,
2035 trap_texture_func,
2036 trap_ai_func,
2037 trap_bark_func,
2038 trap_monster_func,
2039 trap_transmogrify_func
2040 };
2041
2042 ubyte num_trap_types = (sizeof(trap_functions) / sizeof(trap_functions[0]));
2043
grind_trap(char type,int p1,int p2,int p3,int p4,ubyte * destroy_count_ptr,ObjID id)2044 errtype grind_trap(char type, int p1, int p2, int p3, int p4, ubyte *destroy_count_ptr, ObjID id) {
2045 trap_functions[type](p1, p2, p3, p4);
2046 if (*destroy_count_ptr > 0) {
2047 (*destroy_count_ptr) = (*destroy_count_ptr) - 1;
2048 if (*destroy_count_ptr == 0)
2049 ADD_DESTROYED_OBJECT(id);
2050 }
2051 return (OK);
2052 }
2053
trap_activate(ObjID id,uchar * use_message)2054 uchar trap_activate(ObjID id, uchar *use_message) {
2055 uchar retval = FALSE;
2056 ubyte traptype;
2057 int comparator;
2058 int p1, p2, p3, p4;
2059 ubyte *destroy_count_ptr;
2060 uchar special;
2061
2062 *trap_use_message = *use_message;
2063 current_trap = id;
2064
2065 switch (objs[id].obclass) {
2066 case CLASS_TRAP:
2067 traptype = objTraps[objs[id].specID].trap_type;
2068 destroy_count_ptr = &(objTraps[objs[id].specID].destroy_count);
2069 if (objs[id].subclass == TRAP_SUBCLASS_TRIGGER) {
2070 // Triggers that overwrite their comparator have their comparator
2071 // set to zero to avoid problems in interpretation.
2072 switch (objs[id].info.type) {
2073 case DEATHWATCH_TRIGGER_TYPE:
2074 case AREA_ENTRY_TRIGGER_TYPE:
2075 case AREA_CONTINUOUS_TRIGGER_TYPE:
2076 comparator = 0;
2077 break;
2078 default:
2079 comparator = objTraps[objs[id].specID].comparator;
2080 break;
2081 }
2082 }
2083 p1 = objTraps[objs[id].specID].p1;
2084 p2 = objTraps[objs[id].specID].p2;
2085 p3 = objTraps[objs[id].specID].p3;
2086 p4 = objTraps[objs[id].specID].p4;
2087 break;
2088 case CLASS_FIXTURE:
2089 traptype = objFixtures[objs[id].specID].trap_type;
2090 destroy_count_ptr = &(objFixtures[objs[id].specID].destroy_count);
2091 if ((objs[id].subclass == FIXTURE_SUBCLASS_RECEPTACLE) || (objs[id].subclass == FIXTURE_SUBCLASS_VENDING))
2092 comparator = 0;
2093 else
2094 comparator = objFixtures[objs[id].specID].comparator;
2095 p1 = objFixtures[objs[id].specID].p1;
2096 p2 = objFixtures[objs[id].specID].p2;
2097 p3 = objFixtures[objs[id].specID].p3;
2098 p4 = objFixtures[objs[id].specID].p4;
2099 break;
2100 default:
2101 retval = FALSE;
2102 goto out;
2103 }
2104
2105 if (comparator_check(comparator, id, &special)) {
2106 grind_trap(traptype, p1, p2, p3, p4, destroy_count_ptr, id);
2107 retval = TRUE;
2108 }
2109 out:
2110 *use_message = *trap_use_message;
2111 return (retval);
2112 }
2113
2114 // Look through all deathwatch triggers on the level
2115 // if lots of goofy things like explosions are being destroyed here, we may want to
2116 // use some sort of heuristic to speed things up (don't search if destroyed thing
2117 // was an anim, etc.)
2118 // also, we might want to be able to override this behavior if destroying objects
2119 // via the editor is causing unwanted trap problems....
2120
check_deathwatch_triggers(ObjID id,uchar really_dead)2121 errtype check_deathwatch_triggers(ObjID id, uchar really_dead) {
2122 ObjSpecID osid, nextid;
2123 int comp;
2124 uchar dummy;
2125
2126 if (id == OBJ_NULL)
2127 return (ERR_NOEFFECT);
2128
2129 osid = objTraps[0].id;
2130
2131 while (osid != OBJ_SPEC_NULL) {
2132 nextid = objTraps[osid].next;
2133 if (objs[objTraps[osid].id].info.type == DEATHWATCH_TRIGGER_TYPE) {
2134 comp = objTraps[osid].comparator;
2135
2136 // Does this particular trigger care about us?
2137 if (really_dead == !(comp & 0x2000000)) {
2138 if (((comp & 0x1000000) && ((comp & 0xFFFF) == id)) || (ID2TRIP(id) == (comp & 0xFFFFFF))) {
2139 trap_activate(objTraps[osid].id, &dummy);
2140 }
2141 }
2142 }
2143 osid = nextid;
2144 }
2145 return (OK);
2146 }
2147
2148 #define SQ(x) (x * x)
2149 #define in_bin_radius(x1, y1, x2, y2, r) (SQ(r) > SQ((x1 - x2)) + SQ((y1 - y2)))
2150
check_entrance_triggers(uchar old_x,uchar old_y,uchar new_x,uchar new_y)2151 errtype check_entrance_triggers(uchar old_x, uchar old_y, uchar new_x, uchar new_y) {
2152 ObjID id;
2153 ObjSpecID osid;
2154 uchar trap_x, trap_y;
2155 uchar rad;
2156 uchar invert, in_rad_before, in_rad_now;
2157
2158 osid = objTraps[0].id;
2159 while (osid != OBJ_SPEC_NULL) {
2160 id = objTraps[osid].id;
2161
2162 switch (objs[id].subclass) {
2163 case TRAP_SUBCLASS_TRIGGER:
2164 trap_x = OBJ_LOC_BIN_X(objs[id].loc);
2165 trap_y = OBJ_LOC_BIN_Y(objs[id].loc);
2166 switch (objs[id].info.type) {
2167 // Yer basic entry & floor trigger -- this will be used for tripbeams in the near future too
2168 case FLOOR_TRIGGER_TYPE:
2169 case ENTRY_TRIGGER_TYPE:
2170 if ((trap_x == new_x) && (trap_y == new_y))
2171 location_trigger_activate(id);
2172 break;
2173 case AREA_ENTRY_TRIGGER_TYPE:
2174 case AREA_CONTINUOUS_TRIGGER_TYPE:
2175 rad = abs(objTraps[osid].comparator);
2176 invert = (objTraps[osid].comparator >= 0x1000);
2177 in_rad_now = in_bin_radius(trap_x, trap_y, new_x, new_y, rad);
2178 if (objs[id].info.type == AREA_CONTINUOUS_TRIGGER_TYPE) {
2179 if ((in_rad_now && !invert) || (!in_rad_now && invert))
2180 trap_activate(id, trap_use_message);
2181 } else {
2182 in_rad_before = in_bin_radius(trap_x, trap_y, old_x, old_y, rad);
2183 if (in_rad_before != in_rad_now) {
2184 if ((in_rad_now && !invert) || (!in_rad_now && invert))
2185 trap_activate(id, trap_use_message);
2186 }
2187 }
2188 break;
2189 }
2190 break;
2191 }
2192 osid = objTraps[osid].next;
2193 }
2194 return (OK);
2195 }
2196
2197 #define REACTOR_BOOM_QB 0x14
2198
do_special_reactor_hack()2199 errtype do_special_reactor_hack() {
2200 ObjSpecID osid;
2201
2202 if (!qdata_get(REACTOR_BOOM_QB | 0x2000))
2203 return (OK);
2204
2205 // Secret reactor alarm hack
2206 osid = objAnimatings[0].id;
2207 while (osid != OBJ_SPEC_NULL) {
2208 if ((ID2TRIP(objAnimatings[osid].id) == ALERT_PANEL_OFF_TRIPLE) ||
2209 (ID2TRIP(objAnimatings[osid].id) == HORZ_KLAXOFF_TRIPLE)) {
2210 objs[objAnimatings[osid].id].info.type++;
2211 }
2212 osid = objAnimatings[osid].next;
2213 }
2214 obj_load_art(FALSE);
2215 return (OK);
2216 }
2217
do_level_entry_triggers()2218 errtype do_level_entry_triggers() {
2219 ObjSpecID osid;
2220 uchar special;
2221
2222 osid = objTraps[0].id;
2223 while (osid != OBJ_SPEC_NULL) {
2224 if (ID2TRIP(objTraps[osid].id) == LEVEL_TRIG_TRIPLE) {
2225 if (comparator_check(objTraps[osid].comparator, objTraps[osid].id, &special)) {
2226 grind_trap(objTraps[osid].trap_type, objTraps[osid].p1, objTraps[osid].p2, objTraps[osid].p3,
2227 objTraps[osid].p4, &objTraps[osid].destroy_count, objTraps[osid].id);
2228 }
2229 }
2230 osid = objTraps[osid].next;
2231 }
2232
2233 do_special_reactor_hack();
2234
2235 return (OK);
2236 }
2237
do_shodan_triggers()2238 errtype do_shodan_triggers() {
2239 ObjSpecID osid;
2240 uchar special;
2241
2242 osid = objTraps[0].id;
2243 while (osid != OBJ_SPEC_NULL) {
2244 if (ID2TRIP(objTraps[osid].id) == SHODO_TRIG_TRIPLE) {
2245 if (comparator_check(objTraps[osid].comparator, objTraps[osid].id, &special)) {
2246 grind_trap(objTraps[osid].trap_type, objTraps[osid].p1, objTraps[osid].p2, objTraps[osid].p3,
2247 objTraps[osid].p4, &objTraps[osid].destroy_count, objTraps[osid].id);
2248 }
2249 }
2250 osid = objTraps[osid].next;
2251 }
2252 return (OK);
2253 }
2254
do_ecology_triggers()2255 errtype do_ecology_triggers() {
2256 ObjSpecID osid, osid2;
2257 ObjClass cl;
2258 char counter = 0, quan;
2259 int trip;
2260 ObjSpec ospec;
2261 ObjID id;
2262 extern uchar trigger_check;
2263
2264 osid = objTraps[0].id;
2265 while (osid != OBJ_SPEC_NULL) {
2266 id = objTraps[osid].id;
2267 if (ID2TRIP(id) == ECOLOGY_TRIG_TRIPLE) {
2268 quan = objTraps[osid].comparator >> 24;
2269 cl = (ObjClass)((objTraps[osid].comparator & 0xFF0000) >> 16);
2270 trip = objTraps[osid].comparator & 0xFFFFFF;
2271 osid2 = (*(ObjSpec *)objSpecHeaders[cl].data).bits.id;
2272 counter = 0;
2273 while (osid2 != OBJ_SPEC_NULL) {
2274 ospec = *(ObjSpec *)(objSpecHeaders[cl].data + (osid2 * objSpecHeaders[cl].struct_size));
2275 if (ID2TRIP(ospec.bits.id) == trip)
2276 counter++;
2277 if (counter > quan)
2278 osid2 = OBJ_SPEC_NULL;
2279 else
2280 osid2 = ospec.next;
2281 }
2282 if (counter < quan) {
2283 trigger_check = FALSE;
2284 grind_trap(objTraps[osid].trap_type, objTraps[osid].p1, objTraps[osid].p2, objTraps[osid].p3,
2285 objTraps[osid].p4, &objTraps[osid].destroy_count, objTraps[osid].id);
2286 trigger_check = TRUE;
2287 }
2288 }
2289 osid = objTraps[osid].next;
2290 }
2291 return (OK);
2292 }
2293