1 /* $Id: cntrlcen.c,v 1.14 2003-11-26 12:26:29 btb Exp $ */
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.
17 * Code for the control center
20 * Revision 1.2 1995/10/17 13:12:13 allender
21 * added param to ai call
23 * Revision 1.1 1995/05/16 15:23:27 allender
26 * Revision 2.1 1995/03/21 14:40:25 john
27 * Ifdef'd out the NETWORK code.
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.
33 * Revision 1.22 1995/02/11 01:56:14 mike
34 * robots don't fire cheat.
36 * Revision 1.21 1995/02/05 13:39:39 mike
37 * fix stupid bug in control center firing timing.
39 * Revision 1.20 1995/02/03 17:41:21 mike
40 * fix control cen next fire time in multiplayer.
42 * Revision 1.19 1995/01/29 13:46:41 mike
43 * adapt to new create_small_fireball_on_object prototype.
45 * Revision 1.18 1995/01/18 16:12:13 mike
46 * Make control center aware of a cloaked playerr when he fires.
48 * Revision 1.17 1995/01/12 12:53:44 rob
49 * Trying to fix a bug with having cntrlcen in robotarchy games.
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.
55 * Revision 1.15 1994/12/01 11:34:33 mike
56 * fix control center shield strength in multiplayer team games.
58 * Revision 1.14 1994/11/30 15:44:29 mike
59 * make cntrlcen harder at higher levels.
61 * Revision 1.13 1994/11/29 22:26:23 yuan
64 * Revision 1.12 1994/11/27 23:12:31 matt
65 * Made changes for new mprintf calling convention
67 * Revision 1.11 1994/11/23 17:29:38 mike
68 * deal with peculiarities going between net and regular game on boss level.
70 * Revision 1.10 1994/11/18 18:27:15 rob
71 * Fixed some bugs with the last version.
73 * Revision 1.9 1994/11/18 17:13:59 mike
74 * special case handling for level 8.
76 * Revision 1.8 1994/11/15 12:45:28 mike
77 * don't let cntrlcen know where a cloaked player is.
79 * Revision 1.7 1994/11/08 12:18:37 mike
80 * small explosions on control center.
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'.
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.
92 * Revision 1.4 1994/10/20 15:17:30 mike
93 * Hack for control center inside boss robot.
95 * Revision 1.3 1994/10/20 09:47:46 mike
98 * Revision 1.2 1994/10/17 21:35:09 matt
99 * Added support for new Control Center/Main Reactor
101 * Revision 1.1 1994/10/17 20:24:01 matt
112 static char rcsid[] = "$Id: cntrlcen.c,v 1.14 2003-11-26 12:26:29 btb Exp $";
130 #include "cntrlcen.h"
142 #include "fireball.h"
143 #include "endlevel.h"
145 //@@vms_vector controlcen_gun_points[MAX_CONTROLCEN_GUNS];
146 //@@vms_vector controlcen_gun_dirs[MAX_CONTROLCEN_GUNS];
148 reactor Reactors[MAX_REACTORS];
151 control_center_triggers ControlCenterTriggers;
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;
159 vms_vector Gun_pos[MAX_CONTROLCEN_GUNS], Gun_dir[MAX_CONTROLCEN_GUNS];
161 void do_countdown_frame();
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)
170 Assert(obj->type == OBJ_CNTRLCEN);
171 Assert(obj->render_type==RT_POLYOBJ);
173 reactor = &Reactors[obj->id];
175 Assert(gun_num < reactor->n_guns);
177 //instance gun position & orientation
179 vm_copy_transpose_matrix(&m,&obj->orient);
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);
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)
199 for (i=0; i<num_guns; i++) {
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);
207 if (dot > best_dot) {
213 Assert(best_gun != -1); // Contact Mike. This is impossible. Or maybe you're getting an unnormalized vector somewhere.
222 extern fix Player_time_of_death; // object.c
224 int Dead_controlcen_object_num=-1;
226 //how long to blow up on insane
227 int Base_control_center_explosion_time=DEFAULT_CONTROL_CENTER_EXPLOSION_TIME;
229 int Control_center_destroyed = 0;
230 fix Countdown_timer=0;
231 int Countdown_seconds_left=0, Total_countdown_time=0; //in whole seconds
233 int Alan_pavlish_reactor_times[NDL] = {90, 60, 45, 35, 30};
235 // -----------------------------------------------------------------------------
236 // Called every frame. If control center been destroyed, then actually do something.
237 void do_controlcen_dead_frame(void)
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);
243 if (Control_center_destroyed && !Endlevel_sequence)
244 do_countdown_frame();
247 #define COUNTDOWN_VOICE_TIME fl2f(12.75)
249 void do_countdown_frame()
254 if (!Control_center_destroyed) return;
256 if (!is_D2_OEM && !is_MAC_SHARE && !is_SHAREWARE) // get countdown in OEM and SHAREWARE only
258 // On last level, we don't want a countdown.
259 if ((Current_mission_num == Builtin_mission_num) && (Current_level_num == Last_level))
261 if (!(Game_mode & GM_MULTI))
263 if (Game_mode & GM_MULTI_ROBOTS)
268 // Control center destroyed, rock the player's ship.
269 fc = Countdown_seconds_left;
273 // At Trainee, decrease rocking of ship by 4x.
275 if (Difficulty_level == 0)
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.
282 old_time = Countdown_timer;
283 Countdown_timer -= RealFrameTime;
284 Countdown_seconds_left = f2i(Countdown_timer + F1_0*7/8);
286 if ( (old_time > COUNTDOWN_VOICE_TIME ) && (Countdown_timer <= COUNTDOWN_VOICE_TIME) ) {
287 digi_play_sample( SOUND_COUNTDOWN_13_SECS, F3_0 );
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 );
296 if (Countdown_timer > 0) {
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);
307 digi_play_sample( SOUND_CONTROL_CENTER_WARNING_SIREN, F3_0 );
313 digi_play_sample( SOUND_MINE_BLEW_UP, F1_0 );
315 flash_value = f2i(-Countdown_timer * (64 / 4)); // 4 seconds to total whiteness
316 PALETTE_FLASH_SET(flash_value,flash_value,flash_value);
318 if (PaletteBlueAdd > 64 ) {
320 dd_gr_set_current_canvas(NULL),
321 gr_set_current_canvas( NULL )
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();
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)
345 if ((Game_mode & GM_MULTI_ROBOTS) && Control_center_destroyed)
346 return; // Don't allow resetting if control center and boss on same level
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]);
352 // And start the countdown stuff.
353 Control_center_destroyed = 1;
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) {
359 rval = cfile_delete("secret.sgc");
361 rval = cfile_delete(":Players:secret.sgc");
363 mprintf((0, "Deleting secret.sgc, return value = %i\n", rval));
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;
369 Total_countdown_time = Alan_pavlish_reactor_times[Difficulty_level];
371 Countdown_timer = i2f(Total_countdown_time);
373 if (!Control_center_present || objp==NULL) {
374 //Assert(objp == NULL);
378 //Assert(objp != NULL);
380 Dead_controlcen_object_num = objp-Objects;
383 int Last_time_cc_vis_check = 0;
385 // -----------------------------------------------------------------------------
386 //do whatever this thing does in a frame
387 void do_controlcen_frame(object *obj)
391 // If a boss level, then Control_center_present will be 0.
392 if (!Control_center_present)
396 if (!Robot_firing_enabled || (Game_suspended & SUSP_ROBOTS))
399 if (!Robot_firing_enabled)
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;
408 segment *segp = &Segments[obj->segnum];
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.
418 if (Game_mode & GM_MULTI)
419 Believed_player_pos = Objects[Players[Player_num].objnum].pos;
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]))
426 if (i == MAX_SIDES_PER_SEGMENT)
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;
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;
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;
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);
462 best_gun_num = calc_best_gun(N_controlcen_guns, Gun_pos, Gun_dir, &ConsoleObject->pos);
464 if (best_gun_num != -1) {
465 int rand_prob, count;
466 vms_vector vec_to_goal;
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);
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);
478 if (dist_to_player > F1_0*300)
480 Control_center_been_hit = 0;
481 Control_center_player_been_seen = 0;
486 if (Game_mode & GM_MULTI)
487 multi_send_controlcen_fire(&vec_to_goal, best_gun_num, obj-Objects);
489 Laser_create_new_easy( &vec_to_goal, &Gun_pos[best_gun_num], obj-Objects, CONTROLCEN_WEAPON_NUM, 1);
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);
494 while ((d_rand() > rand_prob) && (count < 4)) {
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);
501 if (Game_mode & GM_MULTI)
502 multi_send_controlcen_fire(&vec_to_goal, best_gun_num, obj-Objects);
504 Laser_create_new_easy( &vec_to_goal, &Gun_pos[best_gun_num], obj-Objects, CONTROLCEN_WEAPON_NUM, 0);
508 delta_fire_time = (NDL - Difficulty_level) * F1_0/4;
509 if (Difficulty_level == 0)
510 delta_fire_time += F1_0/2;
512 if (Game_mode & GM_MULTI) // slow down rate of fire in multi player
513 delta_fire_time *= 2;
515 Control_center_next_fire_time = delta_fire_time;
519 Control_center_next_fire_time -= FrameTime;
523 int Reactor_strength=-1; //-1 mean not set by designer
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)
533 int cntrlcen_objnum=-1, boss_objnum=-1;
535 for (i=0; i<=Highest_object_index; i++) {
537 if (objp->type == OBJ_CNTRLCEN)
539 if (cntrlcen_objnum != -1)
540 mprintf((1, "Warning: Two or more control centers including %i and %i\n", i, cntrlcen_objnum));
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));
555 if (cntrlcen_objnum == -1) {
556 mprintf((1, "Warning: No control center.\n"));
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;
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;
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;
581 objp->shields = F1_0*200 - Current_level_num*F1_0*150;
584 objp->shields = i2f(Reactor_strength);
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;
594 Dead_controlcen_object_num = -1;
597 void special_reactor_stuff(void)
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.
608 * reads n reactor structs from a CFILE
610 extern int reactor_read_n(reactor *r, int n, CFILE *fp)
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);
626 * reads a control_center_triggers structure from a CFILE
628 extern int control_center_triggers_read_n(control_center_triggers *cct, int n, CFILE *fp)
632 for (i = 0; i < n; i++)
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);