]> icculus.org git repositories - btb/d2x.git/blob - main/cntrlcen.c
allow arbitrary resolutions to be specified on command line (d1x r1.2, r1.29, r1...
[btb/d2x.git] / main / cntrlcen.c
1 /* $Id: cntrlcen.c,v 1.14 2003-11-26 12:26:29 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  * Old Log:
20  * Revision 1.2  1995/10/17  13:12:13  allender
21  * added param to ai call
22  *
23  * Revision 1.1  1995/05/16  15:23:27  allender
24  * Initial revision
25  *
26  * Revision 2.1  1995/03/21  14:40:25  john
27  * Ifdef'd out the NETWORK code.
28  *
29  * Revision 2.0  1995/02/27  11:31:25  john
30  * New version 2.0, which has no anonymous unions, builds with
31  * Watcom 10.0, and doesn't require parsing BITMAPS.TBL.
32  *
33  * Revision 1.22  1995/02/11  01:56:14  mike
34  * robots don't fire cheat.
35  *
36  * Revision 1.21  1995/02/05  13:39:39  mike
37  * fix stupid bug in control center firing timing.
38  *
39  * Revision 1.20  1995/02/03  17:41:21  mike
40  * fix control cen next fire time in multiplayer.
41  *
42  * Revision 1.19  1995/01/29  13:46:41  mike
43  * adapt to new create_small_fireball_on_object prototype.
44  *
45  * Revision 1.18  1995/01/18  16:12:13  mike
46  * Make control center aware of a cloaked playerr when he fires.
47  *
48  * Revision 1.17  1995/01/12  12:53:44  rob
49  * Trying to fix a bug with having cntrlcen in robotarchy games.
50  *
51  * Revision 1.16  1994/12/11  12:37:22  mike
52  * make control center smarter about firing at cloaked player, don't fire through self, though
53  * it still looks that way due to prioritization problems.
54  *
55  * Revision 1.15  1994/12/01  11:34:33  mike
56  * fix control center shield strength in multiplayer team games.
57  *
58  * Revision 1.14  1994/11/30  15:44:29  mike
59  * make cntrlcen harder at higher levels.
60  *
61  * Revision 1.13  1994/11/29  22:26:23  yuan
62  * Fixed boss bug.
63  *
64  * Revision 1.12  1994/11/27  23:12:31  matt
65  * Made changes for new mprintf calling convention
66  *
67  * Revision 1.11  1994/11/23  17:29:38  mike
68  * deal with peculiarities going between net and regular game on boss level.
69  *
70  * Revision 1.10  1994/11/18  18:27:15  rob
71  * Fixed some bugs with the last version.
72  *
73  * Revision 1.9  1994/11/18  17:13:59  mike
74  * special case handling for level 8.
75  *
76  * Revision 1.8  1994/11/15  12:45:28  mike
77  * don't let cntrlcen know where a cloaked player is.
78  *
79  * Revision 1.7  1994/11/08  12:18:37  mike
80  * small explosions on control center.
81  *
82  * Revision 1.6  1994/11/02  17:59:18  rob
83  * Changed control centers so they can find people in network games.
84  * Side effect of this is that control centers can find cloaked players.
85  * (see in-code comments for explanation).
86  * Also added network hooks so control center shots 'sync up'.
87  *
88  * Revision 1.5  1994/10/22  14:13:21  mike
89  * Make control center stop firing shortly after player dies.
90  * Fix bug: If play from editor and die, tries to initialize non-control center object.
91  *
92  * Revision 1.4  1994/10/20  15:17:30  mike
93  * Hack for control center inside boss robot.
94  *
95  * Revision 1.3  1994/10/20  09:47:46  mike
96  * lots stuff.
97  *
98  * Revision 1.2  1994/10/17  21:35:09  matt
99  * Added support for new Control Center/Main Reactor
100  *
101  * Revision 1.1  1994/10/17  20:24:01  matt
102  * Initial revision
103  *
104  *
105  */
106
107 #ifdef HAVE_CONFIG_H
108 #include <conf.h>
109 #endif
110
111 #ifdef RCS
112 static char rcsid[] = "$Id: cntrlcen.c,v 1.14 2003-11-26 12:26:29 btb Exp $";
113 #endif
114
115 #ifdef WINDOWS
116 #include "desw.h"
117 #endif
118
119 #include <stdlib.h>
120 #include <stdio.h>
121 #ifndef _WIN32
122 #include <unistd.h>
123 #endif
124
125 #include "pstypes.h"
126 #include "error.h"
127 #include "mono.h"
128
129 #include "inferno.h"
130 #include "cntrlcen.h"
131 #include "game.h"
132 #include "laser.h"
133 #include "gameseq.h"
134 #include "ai.h"
135 #ifdef NETWORK
136 #include "multi.h"
137 #endif
138 #include "wall.h"
139 #include "object.h"
140 #include "robot.h"
141 #include "vclip.h"
142 #include "fireball.h"
143 #include "endlevel.h"
144
145 //@@vms_vector controlcen_gun_points[MAX_CONTROLCEN_GUNS];
146 //@@vms_vector controlcen_gun_dirs[MAX_CONTROLCEN_GUNS];
147
148 reactor Reactors[MAX_REACTORS];
149 int Num_reactors=0;
150
151 control_center_triggers ControlCenterTriggers;
152
153 int     N_controlcen_guns;
154 int     Control_center_been_hit;
155 int     Control_center_player_been_seen;
156 int     Control_center_next_fire_time;
157 int     Control_center_present;
158
159 vms_vector      Gun_pos[MAX_CONTROLCEN_GUNS], Gun_dir[MAX_CONTROLCEN_GUNS];
160
161 void do_countdown_frame();
162
163 //      -----------------------------------------------------------------------------
164 //return the position & orientation of a gun on the control center object
165 void calc_controlcen_gun_point(vms_vector *gun_point,vms_vector *gun_dir,object *obj,int gun_num)
166 {
167         reactor *reactor;
168         vms_matrix m;
169
170         Assert(obj->type == OBJ_CNTRLCEN);
171         Assert(obj->render_type==RT_POLYOBJ);
172
173         reactor = &Reactors[obj->id];
174
175         Assert(gun_num < reactor->n_guns);
176
177         //instance gun position & orientation
178
179         vm_copy_transpose_matrix(&m,&obj->orient);
180
181         vm_vec_rotate(gun_point,&reactor->gun_points[gun_num],&m);
182         vm_vec_add2(gun_point,&obj->pos);
183         vm_vec_rotate(gun_dir,&reactor->gun_dirs[gun_num],&m);
184 }
185
186 //      -----------------------------------------------------------------------------
187 //      Look at control center guns, find best one to fire at *objp.
188 //      Return best gun number (one whose direction dotted with vector to player is largest).
189 //      If best gun has negative dot, return -1, meaning no gun is good.
190 int calc_best_gun(int num_guns, vms_vector *gun_pos, vms_vector *gun_dir, vms_vector *objpos)
191 {
192         int     i;
193         fix     best_dot;
194         int     best_gun;
195
196         best_dot = -F1_0*2;
197         best_gun = -1;
198
199         for (i=0; i<num_guns; i++) {
200                 fix                     dot;
201                 vms_vector      gun_vec;
202
203                 vm_vec_sub(&gun_vec, objpos, &gun_pos[i]);
204                 vm_vec_normalize_quick(&gun_vec);
205                 dot = vm_vec_dot(&gun_dir[i], &gun_vec);
206
207                 if (dot > best_dot) {
208                         best_dot = dot;
209                         best_gun = i;
210                 }
211         }
212
213         Assert(best_gun != -1);         // Contact Mike.  This is impossible.  Or maybe you're getting an unnormalized vector somewhere.
214
215         if (best_dot < 0)
216                 return -1;
217         else
218                 return best_gun;
219
220 }
221
222 extern fix Player_time_of_death;                //      object.c
223
224 int     Dead_controlcen_object_num=-1;
225
226 //how long to blow up on insane
227 int Base_control_center_explosion_time=DEFAULT_CONTROL_CENTER_EXPLOSION_TIME;
228
229 int Control_center_destroyed = 0;
230 fix Countdown_timer=0;
231 int Countdown_seconds_left=0, Total_countdown_time=0;           //in whole seconds
232
233 int     Alan_pavlish_reactor_times[NDL] = {90, 60, 45, 35, 30};
234
235 //      -----------------------------------------------------------------------------
236 //      Called every frame.  If control center been destroyed, then actually do something.
237 void do_controlcen_dead_frame(void)
238 {
239         if ((Dead_controlcen_object_num != -1) && (Countdown_seconds_left > 0))
240                 if (d_rand() < FrameTime*4)
241                         create_small_fireball_on_object(&Objects[Dead_controlcen_object_num], F1_0, 1);
242
243         if (Control_center_destroyed && !Endlevel_sequence)
244                 do_countdown_frame();
245 }
246
247 #define COUNTDOWN_VOICE_TIME fl2f(12.75)
248
249 void do_countdown_frame()
250 {
251         fix     old_time;
252         int     fc, div_scale;
253
254         if (!Control_center_destroyed)  return;
255
256         if (!is_D2_OEM && !is_MAC_SHARE && !is_SHAREWARE)   // get countdown in OEM and SHAREWARE only
257         {
258                 // On last level, we don't want a countdown.
259                 if ((Current_mission_num == Builtin_mission_num) && (Current_level_num == Last_level))
260                 {
261                         if (!(Game_mode & GM_MULTI))
262                                 return;
263                         if (Game_mode & GM_MULTI_ROBOTS)
264                                 return;
265                 }
266         }
267
268         //      Control center destroyed, rock the player's ship.
269         fc = Countdown_seconds_left;
270         if (fc > 16)
271                 fc = 16;
272
273         //      At Trainee, decrease rocking of ship by 4x.
274         div_scale = 1;
275         if (Difficulty_level == 0)
276                 div_scale = 4;
277
278         ConsoleObject->mtype.phys_info.rotvel.x += (fixmul(d_rand() - 16384, 3*F1_0/16 + (F1_0*(16-fc))/32))/div_scale;
279         ConsoleObject->mtype.phys_info.rotvel.z += (fixmul(d_rand() - 16384, 3*F1_0/16 + (F1_0*(16-fc))/32))/div_scale;
280         //      Hook in the rumble sound effect here.
281
282         old_time = Countdown_timer;
283         Countdown_timer -= RealFrameTime;
284         Countdown_seconds_left = f2i(Countdown_timer + F1_0*7/8);
285
286         if ( (old_time > COUNTDOWN_VOICE_TIME ) && (Countdown_timer <= COUNTDOWN_VOICE_TIME) )  {
287                 digi_play_sample( SOUND_COUNTDOWN_13_SECS, F3_0 );
288         }
289         if ( f2i(old_time + F1_0*7/8) != Countdown_seconds_left )       {
290                 if ( (Countdown_seconds_left>=0) && (Countdown_seconds_left<10) )
291                         digi_play_sample( SOUND_COUNTDOWN_0_SECS+Countdown_seconds_left, F3_0 );
292                 if ( Countdown_seconds_left==Total_countdown_time-1)
293                         digi_play_sample( SOUND_COUNTDOWN_29_SECS, F3_0 );
294         }                                               
295
296         if (Countdown_timer > 0) {
297                 fix size,old_size;
298                 size = (i2f(Total_countdown_time)-Countdown_timer) / fl2f(0.65);
299                 old_size = (i2f(Total_countdown_time)-old_time) / fl2f(0.65);
300                 if (size != old_size && (Countdown_seconds_left < (Total_countdown_time-5) ))           {                       // Every 2 seconds!
301                         //@@if (Dead_controlcen_object_num != -1) {
302                         //@@    vms_vector vp;  //,v,c;
303                         //@@    compute_segment_center(&vp, &Segments[Objects[Dead_controlcen_object_num].segnum]);
304                         //@@    object_create_explosion( Objects[Dead_controlcen_object_num].segnum, &vp, size*10, VCLIP_SMALL_EXPLOSION);
305                         //@@}
306
307                         digi_play_sample( SOUND_CONTROL_CENTER_WARNING_SIREN, F3_0 );
308                 }
309         }  else {
310                 int flash_value;
311
312                 if (old_time > 0)
313                         digi_play_sample( SOUND_MINE_BLEW_UP, F1_0 );
314
315                 flash_value = f2i(-Countdown_timer * (64 / 4)); // 4 seconds to total whiteness
316                 PALETTE_FLASH_SET(flash_value,flash_value,flash_value);
317
318                 if (PaletteBlueAdd > 64 )       {
319                 WINDOS(
320                         dd_gr_set_current_canvas(NULL),
321                         gr_set_current_canvas( NULL )
322                 );
323                 WINDOS(
324                         dd_gr_clear_canvas(BM_XRGB(31,31,31)),
325                         gr_clear_canvas(BM_XRGB(31,31,31))
326                 );                                                                                                              //make screen all white to match palette effect
327                         reset_cockpit();                                                                //force cockpit redraw next time
328                         reset_palette_add();                                                    //restore palette for death message
329                         //controlcen->MaxCapacity = Fuelcen_max_amount;
330                         //gauge_message( "Control Center Reset" );
331                         DoPlayerDead();         //kill_player();
332                 }                                                                                                                                                               
333         }
334 }
335
336 //      -----------------------------------------------------------------------------
337 //      Called when control center gets destroyed.
338 //      This code is common to whether control center is implicitly imbedded in a boss,
339 //      or is an object of its own.
340 //      if objp == NULL that means the boss was the control center and don't set Dead_controlcen_object_num
341 void do_controlcen_destroyed_stuff(object *objp)
342 {
343         int     i;
344
345    if ((Game_mode & GM_MULTI_ROBOTS) && Control_center_destroyed)
346     return; // Don't allow resetting if control center and boss on same level
347
348         // Must toggle walls whether it is a boss or control center.
349         for (i=0;i<ControlCenterTriggers.num_links;i++)
350                 wall_toggle(&Segments[ControlCenterTriggers.seg[i]], ControlCenterTriggers.side[i]);
351
352         // And start the countdown stuff.
353         Control_center_destroyed = 1;
354
355         //      If a secret level, delete secret.sgc to indicate that we can't return to our secret level.
356         if (Current_level_num < 0) {
357                 int     rval;
358                 #ifndef MACINTOSH
359                 rval = cfile_delete("secret.sgc");
360                 #else
361                 rval = cfile_delete(":Players:secret.sgc");
362                 #endif
363                 mprintf((0, "Deleting secret.sgc, return value = %i\n", rval));
364         }
365
366         if (Base_control_center_explosion_time != DEFAULT_CONTROL_CENTER_EXPLOSION_TIME)
367                 Total_countdown_time = Base_control_center_explosion_time + Base_control_center_explosion_time * (NDL-Difficulty_level-1)/2;
368         else
369                 Total_countdown_time = Alan_pavlish_reactor_times[Difficulty_level];
370
371         Countdown_timer = i2f(Total_countdown_time);
372
373         if (!Control_center_present || objp==NULL) {
374                 //Assert(objp == NULL);
375                 return;
376         }
377
378         //Assert(objp != NULL);
379
380         Dead_controlcen_object_num = objp-Objects;
381 }
382
383 int     Last_time_cc_vis_check = 0;
384
385 //      -----------------------------------------------------------------------------
386 //do whatever this thing does in a frame
387 void do_controlcen_frame(object *obj)
388 {
389         int                     best_gun_num;
390
391         //      If a boss level, then Control_center_present will be 0.
392         if (!Control_center_present)
393                 return;
394
395 #ifndef NDEBUG
396         if (!Robot_firing_enabled || (Game_suspended & SUSP_ROBOTS))
397                 return;
398 #else
399         if (!Robot_firing_enabled)
400                 return;
401 #endif
402
403         if (!(Control_center_been_hit || Control_center_player_been_seen)) {
404                 if (!(FrameCount % 8)) {                //      Do every so often...
405                         vms_vector      vec_to_player;
406                         fix                     dist_to_player;
407                         int                     i;
408                         segment         *segp = &Segments[obj->segnum];
409
410                         // This is a hack.  Since the control center is not processed by
411                         // ai_do_frame, it doesn't know to deal with cloaked dudes.  It
412                         // seems to work in single-player mode because it is actually using
413                         // the value of Believed_player_position that was set by the last
414                         // person to go through ai_do_frame.  But since a no-robots game
415                         // never goes through ai_do_frame, I'm making it so the control
416                         // center can spot cloaked dudes.
417
418                         if (Game_mode & GM_MULTI)
419                                 Believed_player_pos = Objects[Players[Player_num].objnum].pos;
420
421                         //      Hack for special control centers which are isolated and not reachable because the
422                         //      real control center is inside the boss.
423                         for (i=0; i<MAX_SIDES_PER_SEGMENT; i++)
424                                 if (IS_CHILD(segp->children[i]))
425                                         break;
426                         if (i == MAX_SIDES_PER_SEGMENT)
427                                 return;
428
429                         vm_vec_sub(&vec_to_player, &ConsoleObject->pos, &obj->pos);
430                         dist_to_player = vm_vec_normalize_quick(&vec_to_player);
431                         if (dist_to_player < F1_0*200) {
432                                 Control_center_player_been_seen = player_is_visible_from_object(obj, &obj->pos, 0, &vec_to_player);
433                                 Control_center_next_fire_time = 0;
434                         }
435                 }                       
436
437                 return;
438         }
439
440         //      Periodically, make the reactor fall asleep if player not visible.
441         if (Control_center_been_hit || Control_center_player_been_seen) {
442                 if ((Last_time_cc_vis_check + F1_0*5 < GameTime) || (Last_time_cc_vis_check > GameTime)) {
443                         vms_vector      vec_to_player;
444                         fix                     dist_to_player;
445
446                         vm_vec_sub(&vec_to_player, &ConsoleObject->pos, &obj->pos);
447                         dist_to_player = vm_vec_normalize_quick(&vec_to_player);
448                         Last_time_cc_vis_check = GameTime;
449                         if (dist_to_player < F1_0*120) {
450                                 Control_center_player_been_seen = player_is_visible_from_object(obj, &obj->pos, 0, &vec_to_player);
451                                 if (!Control_center_player_been_seen)
452                                         Control_center_been_hit = 0;
453                         }
454                 }
455
456         }
457
458         if ((Control_center_next_fire_time < 0) && !(Player_is_dead && (GameTime > Player_time_of_death+F1_0*2))) {
459                 if (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED)
460                         best_gun_num = calc_best_gun(N_controlcen_guns, Gun_pos, Gun_dir, &Believed_player_pos);
461                 else
462                         best_gun_num = calc_best_gun(N_controlcen_guns, Gun_pos, Gun_dir, &ConsoleObject->pos);
463
464                 if (best_gun_num != -1) {
465                         int                     rand_prob, count;
466                         vms_vector      vec_to_goal;
467                         fix                     dist_to_player;
468                         fix                     delta_fire_time;
469
470                         if (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED) {
471                                 vm_vec_sub(&vec_to_goal, &Believed_player_pos, &Gun_pos[best_gun_num]);
472                                 dist_to_player = vm_vec_normalize_quick(&vec_to_goal);
473                         } else {
474                                 vm_vec_sub(&vec_to_goal, &ConsoleObject->pos, &Gun_pos[best_gun_num]);
475                                 dist_to_player = vm_vec_normalize_quick(&vec_to_goal);
476                         }
477
478                         if (dist_to_player > F1_0*300)
479                         {
480                                 Control_center_been_hit = 0;
481                                 Control_center_player_been_seen = 0;
482                                 return;
483                         }
484         
485                         #ifdef NETWORK
486                         if (Game_mode & GM_MULTI)
487                                 multi_send_controlcen_fire(&vec_to_goal, best_gun_num, obj-Objects);    
488                         #endif
489                         Laser_create_new_easy( &vec_to_goal, &Gun_pos[best_gun_num], obj-Objects, CONTROLCEN_WEAPON_NUM, 1);
490
491                         //      some of time, based on level, fire another thing, not directly at player, so it might hit him if he's constantly moving.
492                         rand_prob = F1_0/(abs(Current_level_num)/4+2);
493                         count = 0;
494                         while ((d_rand() > rand_prob) && (count < 4)) {
495                                 vms_vector      randvec;
496
497                                 make_random_vector(&randvec);
498                                 vm_vec_scale_add2(&vec_to_goal, &randvec, F1_0/6);
499                                 vm_vec_normalize_quick(&vec_to_goal);
500                                 #ifdef NETWORK
501                                 if (Game_mode & GM_MULTI)
502                                         multi_send_controlcen_fire(&vec_to_goal, best_gun_num, obj-Objects);
503                                 #endif
504                                 Laser_create_new_easy( &vec_to_goal, &Gun_pos[best_gun_num], obj-Objects, CONTROLCEN_WEAPON_NUM, 0);
505                                 count++;
506                         }
507
508                         delta_fire_time = (NDL - Difficulty_level) * F1_0/4;
509                         if (Difficulty_level == 0)
510                                 delta_fire_time += F1_0/2;
511
512                         if (Game_mode & GM_MULTI) // slow down rate of fire in multi player
513                                 delta_fire_time *= 2;
514
515                         Control_center_next_fire_time = delta_fire_time;
516
517                 }
518         } else
519                 Control_center_next_fire_time -= FrameTime;
520
521 }
522
523 int Reactor_strength=-1;                //-1 mean not set by designer
524
525 //      -----------------------------------------------------------------------------
526 //      This must be called at the start of each level.
527 //      If this level contains a boss and mode != multiplayer, don't do control center stuff.  (Ghost out control center object.)
528 //      If this level contains a boss and mode == multiplayer, do control center stuff.
529 void init_controlcen_for_level(void)
530 {
531         int             i;
532         object  *objp;
533         int             cntrlcen_objnum=-1, boss_objnum=-1;
534
535         for (i=0; i<=Highest_object_index; i++) {
536                 objp = &Objects[i];
537                 if (objp->type == OBJ_CNTRLCEN)
538                 {
539                         if (cntrlcen_objnum != -1)
540                                 mprintf((1, "Warning: Two or more control centers including %i and %i\n", i, cntrlcen_objnum));
541                         else
542                                 cntrlcen_objnum = i;
543                 }
544
545                 if ((objp->type == OBJ_ROBOT) && (Robot_info[objp->id].boss_flag)) {
546 //                      mprintf((0, "Found boss robot %d.\n", objp->id));
547                         if (boss_objnum != -1)
548                                 mprintf((1, "Warning: Two or more bosses including %i and %i\n", i, boss_objnum));
549                         else
550                                 boss_objnum = i;
551                 }
552         }
553
554 #ifndef NDEBUG
555         if (cntrlcen_objnum == -1) {
556                 mprintf((1, "Warning: No control center.\n"));
557                 return;
558         }
559 #endif
560
561         if ( (boss_objnum != -1) && !((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_ROBOTS)) ) {
562                 if (cntrlcen_objnum != -1) {
563 //                      mprintf((0, "Ghosting control center\n"));
564                         Objects[cntrlcen_objnum].type = OBJ_GHOST;
565                         Objects[cntrlcen_objnum].render_type = RT_NONE;
566                         Control_center_present = 0;
567                 }
568         } else {
569                 //      Compute all gun positions.
570                 objp = &Objects[cntrlcen_objnum];
571                 N_controlcen_guns = Reactors[objp->id].n_guns;
572                 for (i=0; i<N_controlcen_guns; i++)
573                         calc_controlcen_gun_point(&Gun_pos[i], &Gun_dir[i], objp, i);
574                 Control_center_present = 1;
575
576                 if (Reactor_strength == -1) {           //use old defaults
577                         //      Boost control center strength at higher levels.
578                         if (Current_level_num >= 0)
579                                 objp->shields = F1_0*200 + (F1_0*200/4) * Current_level_num;
580                         else
581                                 objp->shields = F1_0*200 - Current_level_num*F1_0*150;
582                 }
583                 else {
584                         objp->shields = i2f(Reactor_strength);
585                 }
586
587         }
588
589         //      Say the control center has not yet been hit.
590         Control_center_been_hit = 0;
591         Control_center_player_been_seen = 0;
592         Control_center_next_fire_time = 0;
593         
594         Dead_controlcen_object_num = -1;
595 }
596
597 void special_reactor_stuff(void)
598 {
599         mprintf((0, "Mucking with reactor countdown time.\n"));
600         if (Control_center_destroyed) {
601                 Countdown_timer += i2f(Base_control_center_explosion_time + (NDL-1-Difficulty_level)*Base_control_center_explosion_time/(NDL-1));
602                 Total_countdown_time = f2i(Countdown_timer)+2;  //      Will prevent "Self destruct sequence activated" message from replaying.
603         }
604 }
605
606 #ifndef FAST_FILE_IO
607 /*
608  * reads n reactor structs from a CFILE
609  */
610 extern int reactor_read_n(reactor *r, int n, CFILE *fp)
611 {
612         int i, j;
613
614         for (i = 0; i < n; i++) {
615                 r[i].model_num = cfile_read_int(fp);
616                 r[i].n_guns = cfile_read_int(fp);
617                 for (j = 0; j < MAX_CONTROLCEN_GUNS; j++)
618                         cfile_read_vector(&(r[i].gun_points[j]), fp);
619                 for (j = 0; j < MAX_CONTROLCEN_GUNS; j++)
620                         cfile_read_vector(&(r[i].gun_dirs[j]), fp);
621         }
622         return i;
623 }
624
625 /*
626  * reads a control_center_triggers structure from a CFILE
627  */
628 extern int control_center_triggers_read_n(control_center_triggers *cct, int n, CFILE *fp)
629 {
630         int i, j;
631
632         for (i = 0; i < n; i++)
633         {
634                 cct->num_links = cfile_read_short(fp);
635                 for (j = 0; j < MAX_CONTROLCEN_LINKS; j++)
636                         cct->seg[j] = cfile_read_short(fp);
637                 for (j = 0; j < MAX_CONTROLCEN_LINKS; j++)
638                         cct->side[j] = cfile_read_short(fp);
639         }
640         return i;
641 }
642 #endif