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