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