Fix crash if Num_walls=0
[btb/d2x.git] / main / cntrlcen.c
1 /* $Id: cntrlcen.c,v 1.18 2004-12-01 12:48:13 btb Exp $ */
2 /*
3 THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
4 SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
5 END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
6 ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
7 IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
8 SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
9 FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
10 CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
11 AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
12 COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
13 */
14
15 /*
16  *
17  * Code for the control center
18  *
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #include <conf.h>
23 #endif
24
25 #ifdef RCS
26 static char rcsid[] = "$Id: cntrlcen.c,v 1.18 2004-12-01 12:48:13 btb Exp $";
27 #endif
28
29 #ifdef WINDOWS
30 #include "desw.h"
31 #endif
32
33 #include <stdlib.h>
34 #include <stdio.h>
35 #if !defined(_WIN32) && !defined(macintosh)
36 #include <unistd.h>
37 #endif
38
39 #include "pstypes.h"
40 #include "error.h"
41 #include "mono.h"
42
43 #include "inferno.h"
44 #include "cntrlcen.h"
45 #include "game.h"
46 #include "laser.h"
47 #include "gameseq.h"
48 #include "ai.h"
49 #ifdef NETWORK
50 #include "multi.h"
51 #endif
52 #include "wall.h"
53 #include "object.h"
54 #include "robot.h"
55 #include "vclip.h"
56 #include "fireball.h"
57 #include "endlevel.h"
58
59 //@@vms_vector controlcen_gun_points[MAX_CONTROLCEN_GUNS];
60 //@@vms_vector controlcen_gun_dirs[MAX_CONTROLCEN_GUNS];
61
62 reactor Reactors[MAX_REACTORS];
63 int Num_reactors=0;
64
65 control_center_triggers ControlCenterTriggers;
66
67 int     N_controlcen_guns;
68 int     Control_center_been_hit;
69 int     Control_center_player_been_seen;
70 int     Control_center_next_fire_time;
71 int     Control_center_present;
72
73 vms_vector      Gun_pos[MAX_CONTROLCEN_GUNS], Gun_dir[MAX_CONTROLCEN_GUNS];
74
75 void do_countdown_frame();
76
77 //      -----------------------------------------------------------------------------
78 //return the position & orientation of a gun on the control center object
79 void calc_controlcen_gun_point(vms_vector *gun_point,vms_vector *gun_dir,object *obj,int gun_num)
80 {
81         reactor *reactor;
82         vms_matrix m;
83
84         Assert(obj->type == OBJ_CNTRLCEN);
85         Assert(obj->render_type==RT_POLYOBJ);
86
87         reactor = &Reactors[obj->id];
88
89         Assert(gun_num < reactor->n_guns);
90
91         //instance gun position & orientation
92
93         vm_copy_transpose_matrix(&m,&obj->orient);
94
95         vm_vec_rotate(gun_point,&reactor->gun_points[gun_num],&m);
96         vm_vec_add2(gun_point,&obj->pos);
97         vm_vec_rotate(gun_dir,&reactor->gun_dirs[gun_num],&m);
98 }
99
100 //      -----------------------------------------------------------------------------
101 //      Look at control center guns, find best one to fire at *objp.
102 //      Return best gun number (one whose direction dotted with vector to player is largest).
103 //      If best gun has negative dot, return -1, meaning no gun is good.
104 int calc_best_gun(int num_guns, vms_vector *gun_pos, vms_vector *gun_dir, vms_vector *objpos)
105 {
106         int     i;
107         fix     best_dot;
108         int     best_gun;
109
110         best_dot = -F1_0*2;
111         best_gun = -1;
112
113         for (i=0; i<num_guns; i++) {
114                 fix                     dot;
115                 vms_vector      gun_vec;
116
117                 vm_vec_sub(&gun_vec, objpos, &gun_pos[i]);
118                 vm_vec_normalize_quick(&gun_vec);
119                 dot = vm_vec_dot(&gun_dir[i], &gun_vec);
120
121                 if (dot > best_dot) {
122                         best_dot = dot;
123                         best_gun = i;
124                 }
125         }
126
127         Assert(best_gun != -1);         // Contact Mike.  This is impossible.  Or maybe you're getting an unnormalized vector somewhere.
128
129         if (best_dot < 0)
130                 return -1;
131         else
132                 return best_gun;
133
134 }
135
136 extern fix Player_time_of_death;                //      object.c
137
138 int     Dead_controlcen_object_num=-1;
139
140 //how long to blow up on insane
141 int Base_control_center_explosion_time=DEFAULT_CONTROL_CENTER_EXPLOSION_TIME;
142
143 int Control_center_destroyed = 0;
144 fix Countdown_timer=0;
145 int Countdown_seconds_left=0, Total_countdown_time=0;           //in whole seconds
146
147 int     Alan_pavlish_reactor_times[NDL] = {90, 60, 45, 35, 30};
148
149 //      -----------------------------------------------------------------------------
150 //      Called every frame.  If control center been destroyed, then actually do something.
151 void do_controlcen_dead_frame(void)
152 {
153         if ((Dead_controlcen_object_num != -1) && (Countdown_seconds_left > 0))
154                 if (d_rand() < FrameTime*4)
155                         create_small_fireball_on_object(&Objects[Dead_controlcen_object_num], F1_0, 1);
156
157         if (Control_center_destroyed && !Endlevel_sequence)
158                 do_countdown_frame();
159 }
160
161 #define COUNTDOWN_VOICE_TIME fl2f(12.75)
162
163 void do_countdown_frame()
164 {
165         fix     old_time;
166         int     fc, div_scale;
167
168         if (!Control_center_destroyed)  return;
169
170         if (!is_D2_OEM && !is_MAC_SHARE && !is_SHAREWARE)   // get countdown in OEM and SHAREWARE only
171         {
172                 // On last level, we don't want a countdown.
173                 if (PLAYING_BUILTIN_MISSION && Current_level_num == Last_level)
174                 {
175                         if (!(Game_mode & GM_MULTI))
176                                 return;
177                         if (Game_mode & GM_MULTI_ROBOTS)
178                                 return;
179                 }
180         }
181
182         //      Control center destroyed, rock the player's ship.
183         fc = Countdown_seconds_left;
184         if (fc > 16)
185                 fc = 16;
186
187         //      At Trainee, decrease rocking of ship by 4x.
188         div_scale = 1;
189         if (Difficulty_level == 0)
190                 div_scale = 4;
191
192         ConsoleObject->mtype.phys_info.rotvel.x += (fixmul(d_rand() - 16384, 3*F1_0/16 + (F1_0*(16-fc))/32))/div_scale;
193         ConsoleObject->mtype.phys_info.rotvel.z += (fixmul(d_rand() - 16384, 3*F1_0/16 + (F1_0*(16-fc))/32))/div_scale;
194         //      Hook in the rumble sound effect here.
195
196         old_time = Countdown_timer;
197         Countdown_timer -= RealFrameTime;
198         Countdown_seconds_left = f2i(Countdown_timer + F1_0*7/8);
199
200         if ( (old_time > COUNTDOWN_VOICE_TIME ) && (Countdown_timer <= COUNTDOWN_VOICE_TIME) )  {
201                 digi_play_sample( SOUND_COUNTDOWN_13_SECS, F3_0 );
202         }
203         if ( f2i(old_time + F1_0*7/8) != Countdown_seconds_left )       {
204                 if ( (Countdown_seconds_left>=0) && (Countdown_seconds_left<10) )
205                         digi_play_sample( SOUND_COUNTDOWN_0_SECS+Countdown_seconds_left, F3_0 );
206                 if ( Countdown_seconds_left==Total_countdown_time-1)
207                         digi_play_sample( SOUND_COUNTDOWN_29_SECS, F3_0 );
208         }                                               
209
210         if (Countdown_timer > 0) {
211                 fix size,old_size;
212                 size = (i2f(Total_countdown_time)-Countdown_timer) / fl2f(0.65);
213                 old_size = (i2f(Total_countdown_time)-old_time) / fl2f(0.65);
214                 if (size != old_size && (Countdown_seconds_left < (Total_countdown_time-5) ))           {                       // Every 2 seconds!
215                         //@@if (Dead_controlcen_object_num != -1) {
216                         //@@    vms_vector vp;  //,v,c;
217                         //@@    compute_segment_center(&vp, &Segments[Objects[Dead_controlcen_object_num].segnum]);
218                         //@@    object_create_explosion( Objects[Dead_controlcen_object_num].segnum, &vp, size*10, VCLIP_SMALL_EXPLOSION);
219                         //@@}
220
221                         digi_play_sample( SOUND_CONTROL_CENTER_WARNING_SIREN, F3_0 );
222                 }
223         }  else {
224                 int flash_value;
225
226                 if (old_time > 0)
227                         digi_play_sample( SOUND_MINE_BLEW_UP, F1_0 );
228
229                 flash_value = f2i(-Countdown_timer * (64 / 4)); // 4 seconds to total whiteness
230                 PALETTE_FLASH_SET(flash_value,flash_value,flash_value);
231
232                 if (PaletteBlueAdd > 64 )       {
233                 WINDOS(
234                         dd_gr_set_current_canvas(NULL),
235                         gr_set_current_canvas( NULL )
236                 );
237                 WINDOS(
238                         dd_gr_clear_canvas(BM_XRGB(31,31,31)),
239                         gr_clear_canvas(BM_XRGB(31,31,31))
240                 );                                                                                                              //make screen all white to match palette effect
241                         reset_cockpit();                                                                //force cockpit redraw next time
242                         reset_palette_add();                                                    //restore palette for death message
243                         //controlcen->MaxCapacity = Fuelcen_max_amount;
244                         //gauge_message( "Control Center Reset" );
245                         DoPlayerDead();         //kill_player();
246                 }                                                                                                                                                               
247         }
248 }
249
250 //      -----------------------------------------------------------------------------
251 //      Called when control center gets destroyed.
252 //      This code is common to whether control center is implicitly imbedded in a boss,
253 //      or is an object of its own.
254 //      if objp == NULL that means the boss was the control center and don't set Dead_controlcen_object_num
255 void do_controlcen_destroyed_stuff(object *objp)
256 {
257         int     i;
258
259    if ((Game_mode & GM_MULTI_ROBOTS) && Control_center_destroyed)
260     return; // Don't allow resetting if control center and boss on same level
261
262         // Must toggle walls whether it is a boss or control center.
263         for (i=0;i<ControlCenterTriggers.num_links;i++)
264                 wall_toggle(&Segments[ControlCenterTriggers.seg[i]], ControlCenterTriggers.side[i]);
265
266         // And start the countdown stuff.
267         Control_center_destroyed = 1;
268
269         //      If a secret level, delete secret.sgc to indicate that we can't return to our secret level.
270         if (Current_level_num < 0) {
271                 int     rval;
272                 #ifndef MACINTOSH
273                 rval = !PHYSFS_delete("secret.sgc");
274                 #else
275                 rval = !PHYSFS_delete("Players/secret.sgc");
276                 #endif
277                 mprintf((0, "Deleting secret.sgc, return value = %i\n", rval));
278         }
279
280         if (Base_control_center_explosion_time != DEFAULT_CONTROL_CENTER_EXPLOSION_TIME)
281                 Total_countdown_time = Base_control_center_explosion_time + Base_control_center_explosion_time * (NDL-Difficulty_level-1)/2;
282         else
283                 Total_countdown_time = Alan_pavlish_reactor_times[Difficulty_level];
284
285         Countdown_timer = i2f(Total_countdown_time);
286
287         if (!Control_center_present || objp==NULL) {
288                 //Assert(objp == NULL);
289                 return;
290         }
291
292         //Assert(objp != NULL);
293
294         Dead_controlcen_object_num = objp-Objects;
295 }
296
297 int     Last_time_cc_vis_check = 0;
298
299 //      -----------------------------------------------------------------------------
300 //do whatever this thing does in a frame
301 void do_controlcen_frame(object *obj)
302 {
303         int                     best_gun_num;
304
305         //      If a boss level, then Control_center_present will be 0.
306         if (!Control_center_present)
307                 return;
308
309 #ifndef NDEBUG
310         if (!Robot_firing_enabled || (Game_suspended & SUSP_ROBOTS))
311                 return;
312 #else
313         if (!Robot_firing_enabled)
314                 return;
315 #endif
316
317         if (!(Control_center_been_hit || Control_center_player_been_seen)) {
318                 if (!(FrameCount % 8)) {                //      Do every so often...
319                         vms_vector      vec_to_player;
320                         fix                     dist_to_player;
321                         int                     i;
322                         segment         *segp = &Segments[obj->segnum];
323
324                         // This is a hack.  Since the control center is not processed by
325                         // ai_do_frame, it doesn't know to deal with cloaked dudes.  It
326                         // seems to work in single-player mode because it is actually using
327                         // the value of Believed_player_position that was set by the last
328                         // person to go through ai_do_frame.  But since a no-robots game
329                         // never goes through ai_do_frame, I'm making it so the control
330                         // center can spot cloaked dudes.
331
332                         if (Game_mode & GM_MULTI)
333                                 Believed_player_pos = Objects[Players[Player_num].objnum].pos;
334
335                         //      Hack for special control centers which are isolated and not reachable because the
336                         //      real control center is inside the boss.
337                         for (i=0; i<MAX_SIDES_PER_SEGMENT; i++)
338                                 if (IS_CHILD(segp->children[i]))
339                                         break;
340                         if (i == MAX_SIDES_PER_SEGMENT)
341                                 return;
342
343                         vm_vec_sub(&vec_to_player, &ConsoleObject->pos, &obj->pos);
344                         dist_to_player = vm_vec_normalize_quick(&vec_to_player);
345                         if (dist_to_player < F1_0*200) {
346                                 Control_center_player_been_seen = player_is_visible_from_object(obj, &obj->pos, 0, &vec_to_player);
347                                 Control_center_next_fire_time = 0;
348                         }
349                 }                       
350
351                 return;
352         }
353
354         //      Periodically, make the reactor fall asleep if player not visible.
355         if (Control_center_been_hit || Control_center_player_been_seen) {
356                 if ((Last_time_cc_vis_check + F1_0*5 < GameTime) || (Last_time_cc_vis_check > GameTime)) {
357                         vms_vector      vec_to_player;
358                         fix                     dist_to_player;
359
360                         vm_vec_sub(&vec_to_player, &ConsoleObject->pos, &obj->pos);
361                         dist_to_player = vm_vec_normalize_quick(&vec_to_player);
362                         Last_time_cc_vis_check = GameTime;
363                         if (dist_to_player < F1_0*120) {
364                                 Control_center_player_been_seen = player_is_visible_from_object(obj, &obj->pos, 0, &vec_to_player);
365                                 if (!Control_center_player_been_seen)
366                                         Control_center_been_hit = 0;
367                         }
368                 }
369
370         }
371
372         if ((Control_center_next_fire_time < 0) && !(Player_is_dead && (GameTime > Player_time_of_death+F1_0*2))) {
373                 if (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED)
374                         best_gun_num = calc_best_gun(N_controlcen_guns, Gun_pos, Gun_dir, &Believed_player_pos);
375                 else
376                         best_gun_num = calc_best_gun(N_controlcen_guns, Gun_pos, Gun_dir, &ConsoleObject->pos);
377
378                 if (best_gun_num != -1) {
379                         int                     rand_prob, count;
380                         vms_vector      vec_to_goal;
381                         fix                     dist_to_player;
382                         fix                     delta_fire_time;
383
384                         if (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED) {
385                                 vm_vec_sub(&vec_to_goal, &Believed_player_pos, &Gun_pos[best_gun_num]);
386                                 dist_to_player = vm_vec_normalize_quick(&vec_to_goal);
387                         } else {
388                                 vm_vec_sub(&vec_to_goal, &ConsoleObject->pos, &Gun_pos[best_gun_num]);
389                                 dist_to_player = vm_vec_normalize_quick(&vec_to_goal);
390                         }
391
392                         if (dist_to_player > F1_0*300)
393                         {
394                                 Control_center_been_hit = 0;
395                                 Control_center_player_been_seen = 0;
396                                 return;
397                         }
398         
399                         #ifdef NETWORK
400                         if (Game_mode & GM_MULTI)
401                                 multi_send_controlcen_fire(&vec_to_goal, best_gun_num, obj-Objects);    
402                         #endif
403                         Laser_create_new_easy( &vec_to_goal, &Gun_pos[best_gun_num], obj-Objects, CONTROLCEN_WEAPON_NUM, 1);
404
405                         //      some of time, based on level, fire another thing, not directly at player, so it might hit him if he's constantly moving.
406                         rand_prob = F1_0/(abs(Current_level_num)/4+2);
407                         count = 0;
408                         while ((d_rand() > rand_prob) && (count < 4)) {
409                                 vms_vector      randvec;
410
411                                 make_random_vector(&randvec);
412                                 vm_vec_scale_add2(&vec_to_goal, &randvec, F1_0/6);
413                                 vm_vec_normalize_quick(&vec_to_goal);
414                                 #ifdef NETWORK
415                                 if (Game_mode & GM_MULTI)
416                                         multi_send_controlcen_fire(&vec_to_goal, best_gun_num, obj-Objects);
417                                 #endif
418                                 Laser_create_new_easy( &vec_to_goal, &Gun_pos[best_gun_num], obj-Objects, CONTROLCEN_WEAPON_NUM, 0);
419                                 count++;
420                         }
421
422                         delta_fire_time = (NDL - Difficulty_level) * F1_0/4;
423                         if (Difficulty_level == 0)
424                                 delta_fire_time += F1_0/2;
425
426                         if (Game_mode & GM_MULTI) // slow down rate of fire in multi player
427                                 delta_fire_time *= 2;
428
429                         Control_center_next_fire_time = delta_fire_time;
430
431                 }
432         } else
433                 Control_center_next_fire_time -= FrameTime;
434
435 }
436
437 int Reactor_strength=-1;                //-1 mean not set by designer
438
439 //      -----------------------------------------------------------------------------
440 //      This must be called at the start of each level.
441 //      If this level contains a boss and mode != multiplayer, don't do control center stuff.  (Ghost out control center object.)
442 //      If this level contains a boss and mode == multiplayer, do control center stuff.
443 void init_controlcen_for_level(void)
444 {
445         int             i;
446         object  *objp;
447         int             cntrlcen_objnum=-1, boss_objnum=-1;
448
449         for (i=0; i<=Highest_object_index; i++) {
450                 objp = &Objects[i];
451                 if (objp->type == OBJ_CNTRLCEN)
452                 {
453                         if (cntrlcen_objnum != -1)
454                                 mprintf((1, "Warning: Two or more control centers including %i and %i\n", i, cntrlcen_objnum));
455                         else
456                                 cntrlcen_objnum = i;
457                 }
458
459                 if ((objp->type == OBJ_ROBOT) && (Robot_info[objp->id].boss_flag)) {
460 //                      mprintf((0, "Found boss robot %d.\n", objp->id));
461                         if (boss_objnum != -1)
462                                 mprintf((1, "Warning: Two or more bosses including %i and %i\n", i, boss_objnum));
463                         else
464                                 boss_objnum = i;
465                 }
466         }
467
468 #ifndef NDEBUG
469         if (cntrlcen_objnum == -1) {
470                 mprintf((1, "Warning: No control center.\n"));
471                 return;
472         }
473 #endif
474
475         if ( (boss_objnum != -1) && !((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_ROBOTS)) ) {
476                 if (cntrlcen_objnum != -1) {
477 //                      mprintf((0, "Ghosting control center\n"));
478                         Objects[cntrlcen_objnum].type = OBJ_GHOST;
479                         Objects[cntrlcen_objnum].render_type = RT_NONE;
480                         Control_center_present = 0;
481                 }
482         } else {
483                 //      Compute all gun positions.
484                 objp = &Objects[cntrlcen_objnum];
485                 N_controlcen_guns = Reactors[objp->id].n_guns;
486                 for (i=0; i<N_controlcen_guns; i++)
487                         calc_controlcen_gun_point(&Gun_pos[i], &Gun_dir[i], objp, i);
488                 Control_center_present = 1;
489
490                 if (Reactor_strength == -1) {           //use old defaults
491                         //      Boost control center strength at higher levels.
492                         if (Current_level_num >= 0)
493                                 objp->shields = F1_0*200 + (F1_0*200/4) * Current_level_num;
494                         else
495                                 objp->shields = F1_0*200 - Current_level_num*F1_0*150;
496                 }
497                 else {
498                         objp->shields = i2f(Reactor_strength);
499                 }
500
501         }
502
503         //      Say the control center has not yet been hit.
504         Control_center_been_hit = 0;
505         Control_center_player_been_seen = 0;
506         Control_center_next_fire_time = 0;
507         
508         Dead_controlcen_object_num = -1;
509 }
510
511 void special_reactor_stuff(void)
512 {
513         mprintf((0, "Mucking with reactor countdown time.\n"));
514         if (Control_center_destroyed) {
515                 Countdown_timer += i2f(Base_control_center_explosion_time + (NDL-1-Difficulty_level)*Base_control_center_explosion_time/(NDL-1));
516                 Total_countdown_time = f2i(Countdown_timer)+2;  //      Will prevent "Self destruct sequence activated" message from replaying.
517         }
518 }
519
520 #ifndef FAST_FILE_IO
521 /*
522  * reads n reactor structs from a CFILE
523  */
524 extern int reactor_read_n(reactor *r, int n, CFILE *fp)
525 {
526         int i, j;
527
528         for (i = 0; i < n; i++) {
529                 r[i].model_num = cfile_read_int(fp);
530                 r[i].n_guns = cfile_read_int(fp);
531                 for (j = 0; j < MAX_CONTROLCEN_GUNS; j++)
532                         cfile_read_vector(&(r[i].gun_points[j]), fp);
533                 for (j = 0; j < MAX_CONTROLCEN_GUNS; j++)
534                         cfile_read_vector(&(r[i].gun_dirs[j]), fp);
535         }
536         return i;
537 }
538
539 /*
540  * reads a control_center_triggers structure from a CFILE
541  */
542 extern int control_center_triggers_read_n(control_center_triggers *cct, int n, CFILE *fp)
543 {
544         int i, j;
545
546         for (i = 0; i < n; i++)
547         {
548                 cct->num_links = cfile_read_short(fp);
549                 for (j = 0; j < MAX_CONTROLCEN_LINKS; j++)
550                         cct->seg[j] = cfile_read_short(fp);
551                 for (j = 0; j < MAX_CONTROLCEN_LINKS; j++)
552                         cct->side[j] = cfile_read_short(fp);
553         }
554         return i;
555 }
556 #endif