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