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/gamesys.c $
21  * $Revision: 1.128 $
22  * $Author: xemu $
23  * $Date: 1994/11/28 06:42:41 $
24  *
25  */
26 
27 #include <stdbool.h>
28 #include <stdlib.h>
29 
30 #include "ai.h"
31 #include "combat.h" // for ADD_DESTROYED_OBJECT
32 #include "cyber.h"
33 #include "cybstrng.h"
34 #include "damage.h"
35 #include "diffq.h" // for time limit
36 #include "drugs.h"
37 #include "effect.h"
38 #include "faketime.h"
39 #include "frflags.h"
40 #include "frprotox.h"
41 #include "fullscrn.h"
42 #include "game_screen.h"
43 #include "gameloop.h"
44 #include "gameobj.h"
45 #include "gamerend.h"
46 #include "gamesys.h"
47 #include "gametime.h"
48 #include "hud.h"
49 #include "lvldata.h"
50 #include "mainloop.h"
51 #include "map.h"
52 #include "mfdint.h"
53 #include "miscqvar.h"
54 #include "musicai.h"
55 #include "newmfd.h"
56 #include "objbit.h"
57 #include "objcrit.h"
58 #include "objects.h"
59 #include "objgame.h"
60 #include "objprop.h"
61 #include "objsim.h"
62 #include "objstuff.h"
63 #include "objuse.h"
64 #include "otrip.h"
65 #include "palfx.h"
66 #include "pathfind.h"
67 #include "player.h"
68 #include "physics.h"
69 #include "rendfx.h"
70 #include "schedule.h"
71 #include "sfxlist.h"
72 #include "shodan.h"
73 #include "tools.h"
74 #include "tpolys.h"
75 #include "trigger.h"
76 #include "visible.h"
77 #include "wares.h"
78 #include "weapons.h"
79 #include "cutsloop.h"
80 
81 // -------
82 // DEFINES
83 // -------
84 #define SECOND_UPDATE_FREQ APPROX_CIT_CYCLE_HZ
85 #define HEALTH_RESTORE_PRECISION (APPROX_CIT_CYCLE_SHFT)
86 #define HEALTH_RESTORE_SHF (6u)
87 #define HEALTH_RESTORE_UNIT (1u << HEALTH_RESTORE_SHF) // unit time for hp/energy regen
88 #define HEALTH_RESTORE_MASK (HEALTH_RESTORE_UNIT - 1)
89 #define sqr(x) ((x) * (x))
90 #define MAX_FATIGUE 10000
91 #define FATIGUE_WARNING_LEVEL 7000
92 
93 // -------
94 // GLOBALS
95 // -------
96 int run_fatigue_rate = 5;
97 extern short fr_solidfr_time;
98 extern short fr_sfx_time;
99 ulong fr_shake_time;
100 uchar gamesys_on = TRUE;
101 
102 // hud vars
103 short enviro_edrain_rate = 0;
104 short enviro_absorb_rate = 0;
105 
106 LevelData level_gamedata;
107 Schedule game_seconds_schedule;
108 
109 // prototypes
110 void game_sched_init(void);
111 void game_sched_free(void);
112 void unshodanizing_callback(ObjID id, intptr_t user_data);
113 void check_nearby_objects(void);
114 void fatigue_player(void);
115 uchar shodan_phase_in(uchar *bitmask, short x, short y, short w, short h, short num, uchar dir);
116 void check_hazard_regions(MapElem *newElem);
117 uchar panel_ref_sanity(ObjID obj);
118 void check_panel_ref(uchar puntme);
119 int apply_rate(int var, int rate, int t0, int t1, int vmin, int vmax);
120 void do_stuff_every_second(void);
121 void expose_player_real(short damage, ubyte type, ushort tsecs);
122 void expose_player(byte damage, ubyte type, ushort tsecs);
123 
game_sched_init(void)124 void game_sched_init(void) { schedule_init(&game_seconds_schedule, GAME_SCHEDULE_SIZE, FALSE); }
125 
game_sched_free(void)126 void game_sched_free(void) {
127     //extern errtype schedule_free(Schedule * s);
128     schedule_free(&game_seconds_schedule);
129 }
130 
131 short fatigue_accum_rate = 100;
132 
133 #define OBJ_CHECK_TICKS CIT_CYCLE
134 ulong obj_check_time = 0;
135 
136 #define MACHINE_LAYER_BASE 25
137 #define OBJ_CHECK_RADIUS 7
138 
unshodanizing_callback(ObjID id,intptr_t user_data)139 void unshodanizing_callback(ObjID id, intptr_t user_data) {
140     bool ud = (bool)user_data;
141     if (ud) {
142         objBigstuffs[objs[id].specID].data2 = SHODAN_STATIC_MAGIC_COOKIE | (TPOLY_TYPE_CUSTOM_MAT << TPOLY_INDEX_BITS);
143         objBigstuffs[objs[id].specID].cosmetic_value = 0;
144         objs[id].info.current_frame = 0;
145     } else
146         add_obj_to_animlist(id, FALSE, TRUE, FALSE, 0, 3, TRUE, ANIMCB_REMOVE);
147 }
148 
149 #define STOCHASTIC_SHODAN_MASK 0xF
150 #define STOCHASTIC_MONSTER_MASK 0xF
151 
152 #define STOCHASTIC_SPARKING_MASK 0xFF
153 #define STOCHASTIC_SPARKING_LEVEL 0x10
154 #define SPARKING_RADIUS 5
155 
156 #define STOCHASTIC_FROG_MASK 0xF
157 #define STOCHASTIC_FROG_LEVEL 0x2
158 #define FIRST_GROVE_LEVEL 11
159 
160 #define NEAR_NOISE_RADIUS 5
161 #define PERIL_RADIUS 4
162 
163 #define MONSTER_THEME_PATH_LENGTH 12
164 
check_nearby_objects()165 void check_nearby_objects() {
166     short x, y;
167     int dist, best_dist;
168     ObjRefID oref;
169     int trip;
170     extern char mlimbs_machine;
171     extern int mlimbs_monster;
172     int new_monster;
173     extern short compute_3drep(Obj * cobj, ObjID cobjid, int obj_type);
174 #ifdef USE_3DREP_FOR_SHODANIZING
175     short rep;
176 #endif
177     ObjID id;
178     ObjSpecID osid;
179     LGPoint dest_pt, source_pt;
180     int pf_id;
181     extern uchar music_on;
182 
183     // Should probably distribute different kinds of checks so that there is not a big hit every OBJ_CHECK_TIME
184     if (obj_check_time < player_struct.game_time) {
185         mlimbs_machine = 0;
186         mlimbs_peril = 0;
187         best_dist = OBJ_CHECK_RADIUS + 1;
188         source_pt.x = OBJ_LOC_BIN_X(objs[PLAYER_OBJ].loc);
189         source_pt.y = OBJ_LOC_BIN_Y(objs[PLAYER_OBJ].loc);
190         new_monster = -1;
191         for (x = PLAYER_BIN_X - OBJ_CHECK_RADIUS; x < PLAYER_BIN_X + OBJ_CHECK_RADIUS; x++) {
192             for (y = PLAYER_BIN_Y - OBJ_CHECK_RADIUS; y < PLAYER_BIN_Y + OBJ_CHECK_RADIUS; y++) {
193                 // Make sure we ain't lookin' off the edge of the map...
194                 if ((x < 0) || (x >= MAP_XSIZE) || (y < 0) || (y >= MAP_YSIZE))
195                     continue;
196 
197                 // Hmm, I should probably modularize out the individual kinds of checks....
198                 oref = me_objref(MAP_GET_XY(x, y));
199                 while (oref != OBJ_REF_NULL) {
200                     id = objRefs[oref].obj;
201                     dist = long_fast_pyth_dist(x - PLAYER_BIN_X, y - PLAYER_BIN_Y);
202                     if (id != PLAYER_OBJ) {
203                         osid = objs[id].specID;
204                         trip = ID2TRIP(id);
205                         switch (objs[id].obclass) {
206                         case CLASS_CRITTER:
207                             if (music_on) {
208                                 if ((new_monster == -1) && (dist < best_dist)) {
209                                     // Boy, I wonder how gruesomely slow this is going to be...
210                                     dest_pt.x = OBJ_LOC_BIN_X(objs[id].loc);
211                                     dest_pt.y = OBJ_LOC_BIN_Y(objs[id].loc);
212                                     pf_id = -1;
213                                     if (pf_id != -1) {
214                                         check_requests(TRUE);
215                                         if ((paths[pf_id].num_steps != 0) &&
216                                             (paths[pf_id].num_steps <= MONSTER_THEME_PATH_LENGTH)) {
217                                             delete_path(pf_id);
218                                             pf_id = -1;
219                                         }
220                                     }
221                                     // go ahead if we couldn't allocate a path or the path was sufficiently short
222                                     if (pf_id == -1) {
223                                         switch (ID2TRIP(id)) {
224                                         case SERVBOT_TRIPLE:
225                                         case REPAIRBOT_TRIPLE:
226                                         case REPAIRBOT2_TRIPLE:
227                                         case FLIER_TRIPLE:
228                                         case AUTOBOMB_TRIPLE:
229                                             new_monster = MONSTER_MUSIC_SMALL_ROBOT;
230                                             break;
231                                         default:
232                                             new_monster = objs[id].subclass;
233                                             break;
234                                         }
235                                         best_dist = dist;
236                                     } else {
237                                         delete_path(pf_id);
238                                         pf_id = -1;
239                                     }
240                                 }
241                                 if ((dist < PERIL_RADIUS) && (objCritters[osid].mood != AI_MOOD_FRIENDLY) &&
242                                     (objCritters[osid].orders != AI_ORDERS_SLEEP)) {
243                                     mlimbs_peril = DEFAULT_PERIL_MAX;
244                                 }
245                             }
246                             if ((dist < NEAR_NOISE_RADIUS) && (CritterProps[CPTRIP(trip)].near_sound != 255) &&
247                                 (objCritters[osid].mood != AI_MOOD_ATTACKING) &&
248                                 (get_crit_posture(osid) != DEATH_CRITTER_POSTURE) &&
249                                 ((rand() & STOCHASTIC_MONSTER_MASK) == 1))
250                                 play_digi_fx_obj(CritterProps[CPTRIP(trip)].near_sound, 1, objCritters[osid].id);
251                             break;
252                         }
253                         switch (trip) {
254                         case MUSIC_MARK_TRIPLE:
255                             // Hm, should we have this use comparator_check
256                             if (player_struct.level >= FIRST_GROVE_LEVEL) {
257                                 if ((rand() & STOCHASTIC_FROG_MASK) < STOCHASTIC_FROG_LEVEL)
258                                     play_digi_fx_obj(SFX_GROVE_1, 1, id);
259                             } else
260                                 mlimbs_machine = MACHINE_LAYER_BASE + objTraps[objs[id].specID].p1;
261                             break;
262                         case SPARK_CABLE_TRIPLE:
263                             if ((dist < SPARKING_RADIUS) &&
264                                 ((rand() & STOCHASTIC_SPARKING_MASK) < STOCHASTIC_SPARKING_LEVEL))
265                                 play_digi_fx_obj(SFX_SPARKING_CABLE, 1, id);
266                             break;
267                         case HORZ_KLAXON_TRIPLE: {
268                             // uchar digi_fx_playing(int fx_id, int *handle_ptr);
269                             if (!digi_fx_playing(SFX_KLAXON, NULL))
270                                 play_digi_fx_obj(SFX_KLAXON, 1, id);
271                         } break;
272                         case TV_TRIPLE:
273                         case MONITOR2_TRIPLE:
274                         case SCREEN_TRIPLE:
275                         case BIGSCREEN_TRIPLE:
276                         case SUPERSCREEN_TRIPLE:
277 #ifdef USE_3DREP_FOR_SHODANIZING
278                             rep = compute_3drep(&(objs[id]), id, ObjProps[OPNUM(id)].render_type);
279                             if ((rep & TPOLY_INDEX_MASK) == SHODAN_STATIC_MAGIC_COOKIE)
280 #else
281                             if (((objBigstuffs[objs[id].specID].data2 & TPOLY_INDEX_MASK) ==
282                                  SHODAN_STATIC_MAGIC_COOKIE) &&
283                                 (((objBigstuffs[objs[id].specID].data2 & TPOLY_TYPE_MASK) >> TPOLY_INDEX_BITS) ==
284                                  TPOLY_TYPE_CUSTOM_MAT))
285 #endif
286                             {
287                                 // Chance of shodanizing....
288                                 if ((rand() & STOCHASTIC_SHODAN_MASK) == 1) {
289                                     objBigstuffs[objs[id].specID].data2 = FIRST_SHODAN_ANIM;
290                                     objBigstuffs[objs[id].specID].cosmetic_value = NUM_SHODAN_FRAMES;
291                                     objs[id].info.current_frame = 0;
292                                     // animate me:  cycle, but don't repeat
293                                     add_obj_to_animlist(id, FALSE, FALSE, FALSE, 0, 3, 0, ANIMCB_REMOVE);
294                                 }
295                             }
296                             break;
297                         default:
298                             ;
299                         }
300                     }
301                     oref = objRefs[oref].next;
302                 }
303             }
304         }
305         mlimbs_monster = new_monster;
306         obj_check_time = player_struct.game_time + OBJ_CHECK_TICKS;
307     }
308 }
309 
310 /*
311 #define CFG_FATIGUE_VAR "fatigue"
312 extern ubyte fatigue_threshold;
313 void reload_fatigue_parms()
314 {
315    int i;
316    int vec[5];
317    i = 5;
318 //   player_struct.fatigue_regen = DEFAULT_FATIGUE_REGEN;
319    if (config_get_value(CFG_FATIGUE_VAR,CONFIG_INT_TYPE,vec,&i))
320    {
321       switch (i)
322       {
323       case 5:
324          fatigue_accum_rate                  = vec[4];
325       case 4:
326          player_struct.fatigue_regen_max     = vec[3];
327       case 3:
328          player_struct.fatigue_regen_base    = vec[2];
329       case 2:
330          fatigue_threshold                   = vec[1];
331       case 1:
332          run_fatigue_rate                    = vec[0];
333       default:
334          break;
335       }
336    }
337 
338 }
339 */
340 
341 uchar fatigue_warning;
342 #define fatigue_val(x) (((x) > SPRINT_CONTROL_THRESHOLD) ? ((int)(x)-SPRINT_CONTROL_THRESHOLD) : 0)
343 #define FATIGUE_DENOM (CONTROL_MAX_VAL - SPRINT_CONTROL_THRESHOLD)
344 uchar gamesys_fatigue = TRUE;
345 
346 #define SKATE_MOD 8
347 
fatigue_player(void)348 void fatigue_player(void) {
349     byte *c = player_struct.controls;
350     int deltat, deltaf;
351     extern uchar jumpjets_active;
352     if (gamesys_fatigue && !jumpjets_active && !EDMS_pelvis_is_climbing()) {
353         deltat = player_struct.deltat;
354         deltaf = run_fatigue_rate * (fatigue_val(c[CONTROL_YVEL]) + fatigue_val(2 * c[CONTROL_ZVEL]) +
355                                      // fatigue_val(c[CONTROL_XVEL])/64 +
356                                      // fatigue_val(c[CONTROL_XYROT])/256 +
357                                      // fatigue_val(c[CONTROL_XZROT])/256 +
358                                      // fatigue_val(c[CONTROL_YZROT])/256 +
359                                      0);
360         if (player_struct.posture != POSTURE_STAND)
361             deltaf /= sqr(player_struct.posture + 1);
362         if (motionware_mode == MOTION_SKATES)
363             deltaf /= SKATE_MOD;
364         player_struct.fatigue += deltaf * deltat / FATIGUE_DENOM + player_struct.fatigue_spend * deltat;
365         if (player_struct.fatigue > MAX_FATIGUE)
366             player_struct.fatigue = MAX_FATIGUE;
367         if (player_struct.drug_status[DRUG_STAMINUP] <= 0 && player_struct.fatigue > FATIGUE_WARNING_LEVEL) {
368             if (!fatigue_warning) {
369                 hud_set(HUD_FATIGUE);
370                 fatigue_warning = TRUE;
371             }
372         } else if (fatigue_warning) {
373             hud_unset(HUD_FATIGUE);
374             fatigue_warning = FALSE;
375         }
376     }
377 }
378 
379 uchar gamesys_render_fx = TRUE;
380 uchar gamesys_restore_health = TRUE;
381 uchar gamesys_slow_proj = TRUE;
382 uchar gamesys_beam_wpns = TRUE;
383 uchar gamesys_drugs = TRUE;
384 
385 ulong next_contin_trig;
386 
387 #define NUM_CONTIN_SECONDS 5
388 #define CONTIN_INTERVAL CIT_CYCLE *NUM_CONTIN_SECONDS
389 
390 short fr_surge_time = 0;
391 char surg_fx_frame = 0;
392 short surge_duration = 60;
393 
394 #define NUM_SURG_FX_FRAMES 7
395 // WH: Unportable, replaced with actual values
396 // short surge_vals[NUM_SURG_FX_FRAMES] = {-1 << 8, -5 << 8, 0 << 8, 2 << 8, 2 << 8, 1 << 8, 1 << 8};
397 short surge_vals[NUM_SURG_FX_FRAMES] = {-256, -1280, 0, 512, 512, 256, 256};
398 
399 #define CONQUER_THRESHOLD 512
400 #define UNCONQUER_THRESHOLD 32
401 #define MAX_SHODAN_FAILURES 10
402 char thresh_fail = 0;
shodan_phase_in(uchar * bitmask,short x,short y,short w,short h,short num,uchar dir)403 uchar shodan_phase_in(uchar *bitmask, short x, short y, short w, short h, short num, uchar dir) {
404     int i = 0, nx, ny, val, oval;
405     while (i < num) {
406         nx = rand() % w;
407         ny = rand() % h;
408         val = x + (y * FULL_VIEW_WIDTH);
409         val += (nx + (ny * FULL_VIEW_WIDTH));
410         oval = val;
411 
412         if (dir) {
413             while (SHODAN_CONQUER_GET(bitmask, val) && (val < SHODAN_BITMASK_SIZE) && (val - oval <= CONQUER_THRESHOLD))
414                 val++;
415             if (val < SHODAN_BITMASK_SIZE)
416                 SHODAN_CONQUER_SET(bitmask, val);
417             if (val - oval > CONQUER_THRESHOLD) {
418                 i = num;
419                 thresh_fail++;
420             }
421         } else {
422             while (!SHODAN_CONQUER_GET(bitmask, val) && (val < SHODAN_BITMASK_SIZE) &&
423                    (val - oval <= UNCONQUER_THRESHOLD))
424                 val++;
425             if (val < SHODAN_BITMASK_SIZE)
426                 SHODAN_CONQUER_UNSET(bitmask, val);
427             if (val - oval > UNCONQUER_THRESHOLD)
428                 i = num;
429         }
430         i++;
431     }
432     if (thresh_fail > MAX_SHODAN_FAILURES) {
433         return (TRUE);
434     }
435     return (FALSE);
436 }
437 
438 #define NUM_SHODAN_REGIONS 4
439 short shodan_region_full_x[] = {0, 0, FULL_VIEW_WIDTH * 7 / 8, 0};
440 short shodan_region_full_y[] = {0, 0, 0, FULL_VIEW_HEIGHT * 7 / 8};
441 short shodan_region_full_width[] = {FULL_VIEW_WIDTH, FULL_VIEW_WIDTH / 8, FULL_VIEW_WIDTH / 8, FULL_VIEW_WIDTH};
442 short shodan_region_full_height[] = {FULL_VIEW_HEIGHT / 8, FULL_VIEW_HEIGHT, FULL_VIEW_HEIGHT, FULL_VIEW_HEIGHT / 8};
443 
444 // stolen from trigger.c
445 #define GAME_OVER_HACK 0x6
446 
447 #define KEY_CODE_ESC 0x1b
448 
449 #define DETECT_AUDIOLOG_QVAR_CHANGE
450 
gamesys_run(void)451 errtype gamesys_run(void) {
452     ObjSpecID osi;
453     uchar dummy;
454     // extern void destroy_destroyed_objects(void);
455     // extern uchar trap_activate(ObjID id, uchar * use_message);
456     extern void set_global_lighting(short new_val);
457     extern uchar *shodan_bitmask;
458     extern ulong page_amount;
459 
460 #ifdef AUTOCORRECT_DIFF_TRASH
461     for (int i = 0; i < 4; i++) {
462         extern char diff_qvars[4];
463         if (player_struct.difficulty[i] != QUESTVAR_GET(diff_qvars[i]))
464             QUESTVAR_SET(diff_qvars[i], player_struct.difficulty[i]);
465     }
466 #endif
467 
468     //   page_amount = 0;
469 
470     if (!gamesys_on)
471         return (OK);
472 
473     if (gamesys_render_fx) {
474         if (fr_solidfr_time > 0) {
475             fr_solidfr_time -= player_struct.deltat;
476             if (fr_solidfr_time <= 0)
477                 fr_global_mod_flag(0, FR_SOLIDFR_MASK);
478         }
479 
480         if (fr_sfx_time > 0) {
481             fr_sfx_time -= player_struct.deltat;
482             if (fr_sfx_time <= 0)
483 
484             {
485                 fr_global_mod_flag(0, FR_SFX_MASK);
486             }
487         }
488         if (fr_surge_time > 0) {
489             fr_surge_time -= player_struct.deltat;
490             if (fr_surge_time <= 0) {
491                 if (surg_fx_frame >= NUM_SURG_FX_FRAMES)
492                     fr_surge_time = 0;
493                 else {
494                     fr_surge_time = surge_duration;
495                     set_global_lighting(surge_vals[surg_fx_frame]);
496                 }
497                 surg_fx_frame++;
498             }
499         }
500     }
501 
502     if (shodan_bitmask != NULL) {
503         if (player_struct.game_time > time_until_shodan_avatar) {
504             char i;
505             if (thresh_fail) {
506                 errtype trap_hack_func(int p1, int p2, int p3, int p4);
507                 extern void begin_shodan_conquer_fx(uchar begin);
508                 begin_shodan_conquer_fx(FALSE);
509                 shodan_bitmask = NULL;
510                 trap_hack_func(GAME_OVER_HACK, 0, 0, 0);
511                 palfx_fade_down();
512             } else {
513                 for (i = 0; i < NUM_SHODAN_REGIONS; i++) {
514                     shodan_phase_in(shodan_bitmask, shodan_region_full_x[i], shodan_region_full_y[i],
515                                     shodan_region_full_width[i], shodan_region_full_height[i],
516                                     QUESTVAR_GET(CYBER_DIFF_QVAR) + 1, TRUE);
517                     shodan_phase_in(shodan_bitmask, 0, 0, FULL_VIEW_WIDTH, FULL_VIEW_HEIGHT,
518                                     (3 * QUESTVAR_GET(CYBER_DIFF_QVAR)) + 1, TRUE);
519                 }
520             }
521             if (thresh_fail) {
522                 // extern errtype mai_player_death();
523                 mai_player_death();
524                 time_until_shodan_avatar = player_struct.game_time + (CIT_CYCLE * 8);
525             } else
526                 time_until_shodan_avatar = player_struct.game_time + SHODAN_INTERVAL;
527         }
528     }
529 
530     // update fatigue
531     if (!global_fullmap->cyber) {
532 
533         fatigue_player();
534         // update drug effects
535         if (gamesys_drugs)
536             drugs_update();
537 
538         // cool off all beam weapons
539         if (gamesys_beam_wpns)
540             cool_off_beam_weapons();
541     }
542 
543     do_stuff_every_second();
544 
545     check_nearby_objects();
546 
547     // destroy old slow projectiles
548     if (gamesys_slow_proj) {
549         osi = objPhysicss[0].id;
550         while (osi != OBJ_SPEC_NULL) {
551             if (objPhysicss[osi].duration < player_struct.game_time) {
552                 ADD_DESTROYED_OBJECT(objPhysicss[osi].id);
553             }
554             osi = objPhysicss[osi].next;
555         }
556         destroy_destroyed_objects();
557     }
558 
559     // Run continuous triggers
560     if (player_struct.game_time > next_contin_trig) {
561         osi = objTraps[0].id;
562         while (osi != OBJ_SPEC_NULL) {
563             if (ID2TRIP(objTraps[osi].id) == CONTIN_TRIG_TRIPLE)
564                 trap_activate(objTraps[osi].id, &dummy);
565             osi = objTraps[osi].next;
566         }
567         next_contin_trig = player_struct.game_time + CONTIN_INTERVAL;
568     }
569 
570     return (OK);
571 }
572 
573 // ----------------------------------------
574 // check_hazard_regions()
575 //
576 // Checks to see if we're in a bio/radiation zone
577 
check_hazard_regions(MapElem * newElem)578 void check_hazard_regions(MapElem *newElem) {
579     fix hdiff = fix_from_obj_height(PLAYER_OBJ) - fix_from_map_height(me_height_flr(newElem));
580     if (me_hazard_rad(newElem) > 0 && hdiff <= fix_make(level_gamedata.hazard.rad_h, 0) / 8) {
581         short exp = (short)me_hazard_rad(newElem) * (short)level_gamedata.hazard.rad;
582         exp -= (short)player_struct.hit_points_lost[RADIATION_TYPE - 1];
583         if (exp > 0) {
584             expose_player_real(exp, RADIATION_TYPE, 0);
585         }
586 
587         hud_set(HUD_RADIATION);
588         if (rand() % 0xFF < 0x80)
589             play_digi_fx(SFX_RADIATION, 1);
590     } else
591         hud_unset(HUD_RADIATION);
592 
593     if (!level_gamedata.hazard.zerogbio) {
594         if (me_hazard_bio(newElem) > 0 && hdiff <= fix_make(level_gamedata.hazard.bio_h, 0) / 8) {
595             short exp = me_hazard_bio(newElem) * level_gamedata.hazard.bio;
596             exp -= player_struct.hit_points_lost[BIO_TYPE - 1];
597             if (exp > 0)
598                 expose_player_real(exp, BIO_TYPE, 0);
599             hud_set(HUD_BIOHAZARD);
600         } else
601             hud_unset(HUD_BIOHAZARD);
602     } else {
603         if (me_hazard_bio(newElem))
604             hud_set(HUD_ZEROGRAV);
605         else
606             hud_unset(HUD_ZEROGRAV);
607     }
608     if ((player_struct.hud_modes & (HUD_RADIATION | HUD_BIOHAZARD | HUD_ENVIROUSE)) == 0) {
609         enviro_edrain_rate = 0;
610     }
611 }
612 
613 #define Z_THRESHOLD FIX_UNIT
614 
panel_ref_sanity(ObjID obj)615 uchar panel_ref_sanity(ObjID obj) {
616     int objtrip = OPNUM(obj), obj_type;
617     obj_type = ObjProps[objtrip].render_type;
618     if ((obj_type == FAUBJ_TPOLY) || (obj_type == FAUBJ_TEXBITMAP))
619         if (objs[obj].obclass == CLASS_FIXTURE) {
620             fixang obj_to_p, objh, delt;
621             fix dy, dx;
622 
623             objh = fixang_from_phys_angle(phys_angle_from_obj(objs[obj].loc.h));
624             dy = fix_from_obj_coord(objs[PLAYER_OBJ].loc.y) - fix_from_obj_coord(objs[obj].loc.y);
625             dx = fix_from_obj_coord(objs[PLAYER_OBJ].loc.x) - fix_from_obj_coord(objs[obj].loc.x);
626 
627             // x and y swapped here to transform coordinate system
628             obj_to_p = fix_atan2(dx, dy);
629             delt = (objh - FIXANG_PI / 2) - obj_to_p;
630 
631             //	      mprintf("Called with %d, locs %d %d and %d %d, got delt %x from %x and %x\n",
632             //	         obj,objs[PLAYER_OBJ].loc.x,objs[PLAYER_OBJ].loc.y,objs[obj].loc.x,objs[obj].loc.y,delt,objh,obj_to_p);
633 
634             // hmmm?
635             if (delt > FIXANG_PI)
636                 return FALSE;
637         }
638     return TRUE;
639 }
640 
641 // -------------------------------------
642 // check_panel_ref
643 //
644 // Checks to see if we've walked away from a panel
645 // in an mfd, and closes the mfd.
646 
check_panel_ref(uchar puntme)647 void check_panel_ref(uchar puntme) {
648     // static short old_x, old_y;
649     // extern void restore_mfd_slot(int mfd_id);
650     extern uchar check_object_dist(ObjID obj1, ObjID obj2, fix crit);
651 
652     ObjID id = player_struct.panel_ref;
653 
654     if (id != OBJ_NULL && (id != PLAYER_OBJ || puntme)) {
655         extern ubyte mfd_get_func(ubyte mfd_id, ubyte s);
656         extern uchar mfd_distance_remove(ubyte slot_funca);
657         uchar punt = puntme;
658         if (objs[id].active) {
659             punt = punt || !check_object_dist(id, PLAYER_OBJ, MAX_USE_DIST);
660             punt = punt || !panel_ref_sanity(id);
661         }
662         if (punt) {
663             uchar punt_mfd[NUM_MFDS], punting = 0;
664             uint8_t mfd_id;
665 
666             for (mfd_id = 0; mfd_id < NUM_MFDS; mfd_id++) {
667                 punt_mfd[mfd_id] = mfd_distance_remove(mfd_get_func(mfd_id, MFD_INFO_SLOT));
668                 punting = punting || punt_mfd[mfd_id];
669             }
670             if (punting) {
671                 mfd_notify_func(MFD_EMPTY_FUNC, MFD_INFO_SLOT, TRUE, MFD_ACTIVE, TRUE);
672             }
673             for (mfd_id = 0; mfd_id < NUM_MFDS; mfd_id++) {
674                 if (punt_mfd[mfd_id] && player_struct.mfd_current_slots[mfd_id] == MFD_INFO_SLOT) {
675                     restore_mfd_slot(mfd_id);
676                 }
677             }
678             player_struct.panel_ref = OBJ_NULL;
679         }
680     }
681     // old_x = PLAYER_BIN_X;
682     // old_y = PLAYER_BIN_Y;
683 }
684 
685 // =================================================
686 // do_stuff_every_second()
687 //
688 // deals with game system stuff that gets done every nerd second.
689 // (A nerd second is 256 clock ticks.  A nerd minute is 64 nerd seconds)
690 
691 // 1 nerd minute = 64*256/(60*280) = 2*256/(15*35) = 512/525 minutes.
692 
693 // Rates at which exposure levels degrade by type, in units of
694 // percent per nerd minute, compounded every nerd second.
695 
696 uchar exposure_degrade_rates[] = {
697     100, //   EXPLOSION_TYPE
698     100, //   ENERGY_BEAM_TYPE
699     100, //   MAGNETIC_TYPE
700     100, //   RADIATION_TYPE
701     100, //
702     100, //   ARMOR_PIERCING_TYPE
703     100, //   NEEDLE_TYPE
704     100, //   BIO_TYPE
705 };
706 
707 // Integrates the function f(t) = num >> denom_shf from t0 to t1,
708 // dealing with roundoff in a graceful way so that
709 // integrating over lots of small intervals roughly equals
710 // the large interval.
711 #define find_delta(t0, t1, num, denom_shf) ((((t1) * (num)) >> (denom_shf)) - (((t0) * (num)) >> (denom_shf)))
712 
713 // computes the change to var by the specified rate as accrued over the
714 // time from t0 to t1, clamped by [vmin,vmax].
715 // returns the new value of var.
716 
717 // t0 and t1 are times in nerd seconds, rate is in units per minute.
718 
apply_rate(int var,int rate,int t0,int t1,int vmin,int vmax)719 int apply_rate(int var, int rate, int t0, int t1, int vmin, int vmax) {
720     int delta = find_delta(t0, t1, rate, HEALTH_RESTORE_SHF);
721     int final = lg_min(vmax, lg_max(var + delta, vmin));
722     return final;
723 }
724 
725 #define ENERGY_VAR_RATE 50
726 
727 extern ObjID shodan_avatar_id;
728 
729 #define MY_FORALLOBJSPECS(pmo, objspec) \
730     for (pmo = (objspec[OBJ_SPEC_NULL]).id; pmo != OBJ_SPEC_NULL; pmo = objspec[pmo].next)
731 
732 #define REACTOR_BOOM_QB 0x14 // stolen from trigger.c
733 #define BRIDGE_SEPARATED_QB 0x98
734 
735 #define REALSPACE_HUDS \
736     (HUD_RADPOISON | HUD_BIOPOISON | HUD_FATIGUE | HUD_BIOHAZARD | HUD_RADIATION | HUD_ZEROGRAV | HUD_ENVIROUSE)
737 
738 extern bool gPlayingGame;
739 extern bool gDeadPlayerQuit;
740 
do_stuff_every_second()741 void do_stuff_every_second() {
742     long running_dt = player_struct.game_time - player_struct.last_second_update;
743     extern int bio_energy_var;
744     extern int bio_absorb;
745     extern int rad_absorb;
746     extern ulong player_death_time;
747     int last = (player_struct.last_second_update >> HEALTH_RESTORE_PRECISION) & HEALTH_RESTORE_MASK;
748     int next = (player_struct.game_time >> HEALTH_RESTORE_PRECISION) & HEALTH_RESTORE_MASK;
749 
750     if (running_dt > SECOND_UPDATE_FREQ) {
751         if (global_fullmap->cyber) {
752             char i;
753             ObjSpecID osid;
754             ObjID new_id, shrine_obj = OBJ_NULL;
755             extern uchar *shodan_bitmask;
756 
757             for (i = 0; i < NUM_CS_EFFECTS; i++)
758                 if ((cspace_effect_times[i] != 0) && (cspace_effect_times[i] <= player_struct.game_time)) {
759                     cspace_effect_times[i] = 0;
760                     if (cspace_effect_turnoff[i] != NULL)
761                         cspace_effect_turnoff[i](TRUE, TRUE);
762                 }
763 
764             if ((time_until_shodan_avatar != 0) && (player_struct.game_time > time_until_shodan_avatar) &&
765                 (shodan_avatar_id == OBJ_NULL) && (shodan_bitmask == NULL)) {
766                 time_until_shodan_avatar = 0;
767                 MY_FORALLOBJSPECS(osid, objSmallstuffs) {
768                     if (ID2TRIP(objSmallstuffs[osid].id) == SHODO_SHRINE_TRIPLE) {
769                         shrine_obj = objSmallstuffs[osid].id;
770                     }
771                 }
772                 if (shrine_obj != OBJ_NULL) {
773                     LGPoint sq;
774                     sq.x = OBJ_LOC_BIN_X(objs[shrine_obj].loc);
775                     sq.y = OBJ_LOC_BIN_Y(objs[shrine_obj].loc);
776                     new_id = object_place(CYBER_SHODAN_TRIPLE, sq);
777                     objCritters[objs[new_id].specID].mood = AI_MOOD_HOSTILE;
778                     shodan_avatar_id = new_id;
779                 }
780             }
781             hud_unset(REALSPACE_HUDS);
782         } else {
783             extern void update_email_ware(void);
784 
785             if ((QUESTBIT_GET(REACTOR_BOOM_QB)) && (!QUESTBIT_GET(BRIDGE_SEPARATED_QB)) && ((rand() & 0x3F) == 1)) {
786                 play_digi_fx(SFX_RUMBLE, 2);
787                 fr_global_mod_flag(FR_SFX_SHAKE, FR_SFX_MASK);
788                 fr_sfx_time = CIT_CYCLE * 5; // 2 seconds of shake
789             }
790             if (next < last)
791                 next += HEALTH_RESTORE_UNIT;
792             if (player_struct.energy_regen + player_struct.energy_spend != 0) {
793                 int num = player_struct.energy_regen - player_struct.energy_spend;
794                 if (num != 0) {
795                     int finale = apply_rate(player_struct.energy, num, last, next, 0, MAX_ENERGY);
796                     bio_energy_var -= ENERGY_VAR_RATE * (finale - player_struct.energy);
797                     player_struct.energy = (ubyte)finale;
798                     chg_set_flg(VITALS_UPDATE);
799                 }
800             }
801             if (player_struct.energy == 0) {
802                 extern void hardware_power_outage(void);
803                 extern errtype gear_power_outage(void);
804 
805                 if (!player_struct.energy_out) {
806                     string_message_info(REF_STR_PowerRanOut);
807                     play_digi_fx(SFX_POWER_OUT, 1);
808                     player_struct.energy_out = TRUE;
809                 }
810                 hardware_power_outage();
811                 gear_power_outage();
812             } else
813                 player_struct.energy_out = FALSE;
814             if (player_struct.hit_points < PLAYER_MAX_HP && player_struct.hit_points_regen != 0) {
815 
816                 int num = player_struct.hit_points_regen;
817                 player_struct.hit_points = apply_rate(player_struct.hit_points, num, last, next, 0, PLAYER_MAX_HP);
818                 mfd_notify_func(MFD_BIOWARE_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, FALSE);
819             }
820             if (player_struct.hit_points > 0) {
821                 int i;
822                 for (i = 0; i < NUM_DAMAGE_TYPES; i++)
823                     if (player_struct.hit_points_lost[i] > 0) {
824                         int num = player_struct.hit_points_lost[i];
825                         int degrade = exposure_degrade_rates[i];
826                         int deltahp = player_struct.hit_points -
827                                       apply_rate(player_struct.hit_points, -num, last, next, 0, PLAYER_MAX_HP);
828                         degrade = lg_min(100, find_delta(last, next, degrade, HEALTH_RESTORE_SHF));
829                         damage_player((ubyte)deltahp, i + 1, NO_SHIELD_ABSORBTION);
830                         player_struct.hit_points_lost[i] = num * (100 - degrade) / 100;
831                     }
832                 if (player_struct.hit_points_lost[RADIATION_TYPE - 1] > 0)
833                     hud_set(HUD_RADPOISON);
834                 else
835                     hud_unset(HUD_RADPOISON);
836                 if (player_struct.hit_points_lost[BIO_TYPE - 1] > 0)
837                     hud_set(HUD_BIOPOISON);
838                 else
839                     hud_unset(HUD_BIOPOISON);
840             }
841             update_email_ware();
842         }
843         if (player_struct.fatigue > 0 && player_struct.controls[CONTROL_YVEL] <= SPRINT_CONTROL_THRESHOLD &&
844             !EDMS_pelvis_is_climbing()) {
845             int newf = player_struct.fatigue - player_struct.fatigue_regen;
846             player_struct.fatigue_regen += fatigue_accum_rate;
847             if (player_struct.fatigue_regen > player_struct.fatigue_regen_max)
848                 player_struct.fatigue_regen = player_struct.fatigue_regen_max;
849             if (newf <= 0) {
850                 newf = 0;
851             }
852             if (newf != player_struct.fatigue) {
853                 mfd_notify_func(MFD_BIOWARE_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, FALSE);
854                 player_struct.fatigue = newf;
855             }
856         } else
857             player_struct.fatigue_regen = player_struct.fatigue_regen_base;
858 
859         if (QUESTVAR_GET(MISSION_DIFF_QVAR) == 3) {
860             int remain = MISSION_3_TICKS - player_struct.game_time;
861 
862             if ((remain / CIT_CYCLE) <= 30)
863                 secret_render_fx = TIMELIMIT_REND_SFX;
864 
865             if (remain < CIT_CYCLE) {
866                 secret_render_fx = 0;
867                 play_cutscene(ENDGAME_CUTSCENE,TRUE);
868 
869                 // gDeadPlayerQuit = TRUE; // Pretend the player is dead.
870                 // gPlayingGame = FALSE; // Hop out of the game loop.
871             }
872         }
873 
874         player_struct.last_second_update = player_struct.game_time;
875 
876         // what is this code trying to do? ie. r_a -= (0-r_a)-r_a/4; looks like it can increase or decrease?, end <0, so
877         // on
878         if (rad_absorb > 0)
879             rad_absorb -= rand() % (rad_absorb)-rad_absorb / 4;
880         else
881             rad_absorb = 0;
882         if (bio_absorb > 0)
883             bio_absorb -= rand() % (bio_absorb)-bio_absorb / 4;
884         else
885             bio_absorb = 0;
886 
887         check_hazard_regions(MAP_GET_XY(PLAYER_BIN_X, PLAYER_BIN_Y));
888         check_panel_ref(FALSE);
889     }
890 }
891 
892     // ---------------------------------------------
893     // Expose_player exposes the player to damage of a specified type.
894     // if tsecs is non-zero, it is the amount of time in which the damage will go away.
895 
896     // TSECS IS NO LONGER SUPPORTED, ALL EXPOSURE HAS A BUILT-IN DECAY RATE.
897 
898 #define MAX_UBYTE 0xFF
899 
900 int enviro_suit_absorb(int damage, int exposure, ubyte dtype);
901 
expose_player_real(short damage,ubyte type,ushort tsecs)902 void expose_player_real(short damage, ubyte type, ushort tsecs) {
903     int cval = player_struct.hit_points_lost[type - 1];
904     if (damage == 0)
905         return;
906     damage = lg_max(-cval, lg_min(damage, MAX_UBYTE - cval));
907     if (damage > 0 && (type == BIO_TYPE || type == RADIATION_TYPE)) {
908         damage = enviro_suit_absorb(damage, cval, type);
909     }
910     cval += damage;
911     player_struct.hit_points_lost[type - 1] = (ubyte)cval;
912 #ifdef SCHEDULED_DECAY
913     if (tsecs > 0) {
914         SchedEvent ev;
915         SchedExposeData *xd = (SchedExposeData *)&ev.data;
916         int count = 1;
917         ev.timestamp = TICKS2TSTAMP(player_struct.game_time + tsecs * CIT_CYCLE);
918         ev.type = EXPOSE_SCHED_EVENT;
919         xd->damage = -(damage / count); // plus or minus exposure increment
920         xd->type = type;
921         xd->tsecs = tsecs;
922         xd->count = count;
923         schedule_event(&game_seconds_schedule, &ev);
924     }
925 #endif // SCHEDULED_DECAY
926 }
927 
expose_player(byte damage,ubyte type,ushort tsecs)928 void expose_player(byte damage, ubyte type, ushort tsecs) { expose_player_real(damage, type, tsecs); }
929 
930 //-------------------------------------------------------
931 // enviro_suit_absorb()
932 //
933 // returns damage to player after enviro-suit
934 
935 #define ENVIRO_ABSORB_DENOM 5
936 #define ENVIRO_DRAIN_DENOM 1
937 #define ENVIRO_DRAIN_RATE 32
938 
939 // biorhythm vars
940 int bio_absorb = 0;
941 int rad_absorb = 0;
942 
enviro_suit_absorb(int damage,int exposure,ubyte dtype)943 int enviro_suit_absorb(int damage, int exposure, ubyte dtype) {
944     short drain;
945     short absorb;
946     short denom;
947     short energy;
948     short old_edrain_rate = enviro_edrain_rate;
949     ubyte version = player_struct.hardwarez[CPTRIP(ENV_HARD_TRIPLE)];
950 
951     if (dtype == RADIATION_TYPE && version > 0)
952         version--;
953     if (version == 0)
954         return damage;
955 
956     // Absorb all but 1/nth of damage
957     denom = ENVIRO_ABSORB_DENOM + version;
958     absorb = ((damage + exposure) * (denom - 1)) / denom;
959     if (absorb == 0)
960         return damage;
961 
962     // Compute drain for that absorption amount.
963     denom = ENVIRO_DRAIN_DENOM + version + 1;
964     enviro_edrain_rate = exposure * 60 / (ENVIRO_DRAIN_RATE * denom);
965     drain = (rand() % 60 + enviro_edrain_rate) / 60;
966 
967     // drain energy
968     energy = drain_energy(drain);
969     // did we have enough?
970     if (energy < drain) {
971         // if not, recompute absorption.
972         absorb = energy * denom / (denom - 1);
973     }
974     enviro_absorb_rate = lg_min(damage, absorb) >> 1;
975     damage -= lg_min(damage, absorb);
976     switch (dtype) {
977     case BIO_TYPE:
978         bio_absorb = 8 + long_sqrt((int)damage);
979         break;
980     case RADIATION_TYPE:
981         rad_absorb = 8 + long_sqrt((int)damage);
982         break;
983     default:
984         break;
985     }
986     if (enviro_absorb_rate > 0) {
987         if (old_edrain_rate == 0) {
988             uint32_t time = 5u << APPROX_CIT_CYCLE_SHFT;
989             hud_set_time(HUD_ENVIROUSE, time);
990             hud_set_time(HUD_ENERGYUSE, time);
991         }
992     } else
993         hud_unset(HUD_ENVIROUSE);
994     return (byte)damage;
995 }
996