1 //Copyright Paul Reiche, Fred Ford. 1992-2002
2 
3 /*
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  */
18 
19 #include "../ship.h"
20 #include "blackurq.h"
21 #include "resinst.h"
22 
23 #include "uqm/globdata.h"
24 
25 // Core characteristics
26 #define MAX_CREW MAX_CREW_SIZE
27 #define MAX_ENERGY MAX_ENERGY_SIZE
28 #define ENERGY_REGENERATION 1
29 #define ENERGY_WAIT 4
30 #define MAX_THRUST 30
31 #define THRUST_INCREMENT 6
32 #define TURN_WAIT 4
33 #define THRUST_WAIT 6
34 #define SHIP_MASS 10
35 
36 // Buzzsaw
37 #define WEAPON_ENERGY_COST 6
38 #define WEAPON_WAIT 6
39 #define MISSILE_OFFSET 9
40 #define KOHR_AH_OFFSET 28
41 #define MISSILE_SPEED 64
42 #define MISSILE_LIFE 64
43 		/* actually, it's as long as you hold the button down.*/
44 #define MISSILE_HITS 10
45 #define MISSILE_DAMAGE 4
46 #define SAW_RATE 0
47 #define MAX_SAWS 8
48 #define ACTIVATE_RANGE 224
49 		/* Originally SPACE_WIDTH - the distance within which
50 		 * stationary sawblades will home */
51 #define TRACK_WAIT 4
52 #define FRAGMENT_SPEED MISSILE_SPEED
53 #define FRAGMENT_LIFE 10
54 #define FRAGMENT_RANGE (FRAGMENT_LIFE * FRAGMENT_SPEED)
55 
56 // F.R.I.E.D.
57 #define SPECIAL_ENERGY_COST (MAX_ENERGY_SIZE / 2)
58 #define SPECIAL_WAIT 9
59 #define GAS_OFFSET 2
60 #define GAS_SPEED 16
61 #define GAS_RATE 2 /* Controls animation of the gas cloud decay - the decay
62                     * animation advances one frame every GAS_RATE frames. */
63 #define GAS_HITS 100
64 #define GAS_DAMAGE 3
65 #define GAS_ALT_DAMAGE 50
66 #define NUM_GAS_CLOUDS 16
67 
68 static RACE_DESC black_urquan_desc =
69 {
70 	{ /* SHIP_INFO */
71 		FIRES_FORE,
72 		30, /* Super Melee cost */
73 		MAX_CREW, MAX_CREW,
74 		MAX_ENERGY, MAX_ENERGY,
75 		KOHR_AH_RACE_STRINGS,
76 		KOHR_AH_ICON_MASK_PMAP_ANIM,
77 		KOHR_AH_MICON_MASK_PMAP_ANIM,
78 		NULL, NULL, NULL
79 	},
80 	{ /* FLEET_STUFF */
81 		2666 / SPHERE_RADIUS_INCREMENT * 2, /* Initial SoI radius */
82 		{ /* Known location (center of SoI) */
83 			6000, 6250,
84 		},
85 	},
86 	{
87 		MAX_THRUST,
88 		THRUST_INCREMENT,
89 		ENERGY_REGENERATION,
90 		WEAPON_ENERGY_COST,
91 		SPECIAL_ENERGY_COST,
92 		ENERGY_WAIT,
93 		TURN_WAIT,
94 		THRUST_WAIT,
95 		WEAPON_WAIT,
96 		SPECIAL_WAIT,
97 		SHIP_MASS,
98 	},
99 	{
100 		{
101 			KOHR_AH_BIG_MASK_PMAP_ANIM,
102 			KOHR_AH_MED_MASK_PMAP_ANIM,
103 			KOHR_AH_SML_MASK_PMAP_ANIM,
104 		},
105 		{
106 			BUZZSAW_BIG_MASK_PMAP_ANIM,
107 			BUZZSAW_MED_MASK_PMAP_ANIM,
108 			BUZZSAW_SML_MASK_PMAP_ANIM,
109 		},
110 		{
111 			GAS_BIG_MASK_PMAP_ANIM,
112 			GAS_MED_MASK_PMAP_ANIM,
113 			GAS_SML_MASK_PMAP_ANIM,
114 		},
115 		{
116 			KOHR_AH_CAPTAIN_MASK_PMAP_ANIM,
117 			NULL, NULL, NULL, NULL, NULL
118 		},
119 		KOHR_AH_VICTORY_SONG,
120 		KOHR_AH_SHIP_SOUNDS,
121 		{ NULL, NULL, NULL },
122 		{ NULL, NULL, NULL },
123 		{ NULL, NULL, NULL },
124 		NULL, NULL
125 	},
126 	{
127 		0,
128 		CLOSE_RANGE_WEAPON,
129 		NULL,
130 	},
131 	(UNINIT_FUNC *) NULL,
132 	(PREPROCESS_FUNC *) NULL,
133 	(POSTPROCESS_FUNC *) NULL,
134 	(INIT_WEAPON_FUNC *) NULL,
135 	0,
136 	0, /* CodeRef */
137 };
138 
139 static void
spin_preprocess(ELEMENT * ElementPtr)140 spin_preprocess (ELEMENT *ElementPtr)
141 {
142 	ELEMENT *ShipPtr;
143 	STARSHIP *StarShipPtr;
144 
145 	GetElementStarShip (ElementPtr, &StarShipPtr);
146 	LockElement (StarShipPtr->hShip, &ShipPtr);
147 	if (ShipPtr->crew_level
148 			&& ++StarShipPtr->RaceDescPtr->characteristics.special_wait > MAX_SAWS)
149 	{
150 		ElementPtr->life_span = 1;
151 		ElementPtr->state_flags |= DISAPPEARING;
152 	}
153 	else
154 	{
155 		++ElementPtr->life_span;
156 		if (ElementPtr->turn_wait)
157 			--ElementPtr->turn_wait;
158 		else
159 		{
160 #define LAST_SPIN_INDEX 1
161 			if (GetFrameIndex (
162 					ElementPtr->current.image.frame
163 					) < LAST_SPIN_INDEX)
164 				ElementPtr->next.image.frame =
165 						IncFrameIndex (ElementPtr->current.image.frame);
166 			else
167 				ElementPtr->next.image.frame =
168 						SetAbsFrameIndex (ElementPtr->current.image.frame, 0);
169 			ElementPtr->state_flags |= CHANGING;
170 
171 			ElementPtr->turn_wait = SAW_RATE;
172 		}
173 	}
174 	UnlockElement (StarShipPtr->hShip);
175 }
176 
177 static void
buzztrack_preprocess(ELEMENT * ElementPtr)178 buzztrack_preprocess (ELEMENT *ElementPtr)
179 {
180 	if (ElementPtr->thrust_wait)
181 		--ElementPtr->thrust_wait;
182 	else
183 	{
184 		COUNT facing = 0;
185 
186 		if (ElementPtr->hTarget == 0
187 				&& TrackShip (ElementPtr, &facing) < 0)
188 		{
189 			ZeroVelocityComponents (&ElementPtr->velocity);
190 		}
191 		else
192 		{
193 			SIZE delta_x, delta_y;
194 			ELEMENT *eptr;
195 
196 			LockElement (ElementPtr->hTarget, &eptr);
197 			delta_x = eptr->current.location.x
198 					- ElementPtr->current.location.x;
199 			delta_y = eptr->current.location.y
200 					- ElementPtr->current.location.y;
201 			UnlockElement (ElementPtr->hTarget);
202 			delta_x = WRAP_DELTA_X (delta_x);
203 			delta_y = WRAP_DELTA_Y (delta_y);
204 			facing = NORMALIZE_FACING (
205 					ANGLE_TO_FACING (ARCTAN (delta_x, delta_y))
206 					);
207 
208 			if (delta_x < 0)
209 				delta_x = -delta_x;
210 			if (delta_y < 0)
211 				delta_y = -delta_y;
212 			delta_x = WORLD_TO_DISPLAY (delta_x);
213 			delta_y = WORLD_TO_DISPLAY (delta_y);
214 			if (delta_x >= ACTIVATE_RANGE
215 					|| delta_y >= ACTIVATE_RANGE
216 					|| (DWORD)((UWORD)delta_x * delta_x)
217 					+ (DWORD)((UWORD)delta_y * delta_y) >=
218 					(DWORD)ACTIVATE_RANGE * ACTIVATE_RANGE)
219 			{
220 				ZeroVelocityComponents (&ElementPtr->velocity);
221 			}
222 			else
223 			{
224 				ElementPtr->thrust_wait = TRACK_WAIT;
225 				SetVelocityVector (&ElementPtr->velocity,
226 						DISPLAY_TO_WORLD (2), facing);
227 			}
228 		}
229 	}
230 
231 	spin_preprocess (ElementPtr);
232 }
233 
234 static void
decelerate_preprocess(ELEMENT * ElementPtr)235 decelerate_preprocess (ELEMENT *ElementPtr)
236 {
237 	SIZE dx, dy;
238 
239 	GetCurrentVelocityComponents (&ElementPtr->velocity, &dx, &dy);
240 	dx /= 2;
241 	dy /= 2;
242 	SetVelocityComponents (&ElementPtr->velocity, dx, dy);
243 	if (dx == 0 && dy == 0)
244 	{
245 		ElementPtr->preprocess_func = buzztrack_preprocess;
246 	}
247 
248 	spin_preprocess (ElementPtr);
249 }
250 
251 static void
splinter_preprocess(ELEMENT * ElementPtr)252 splinter_preprocess (ELEMENT *ElementPtr)
253 {
254 	ElementPtr->next.image.frame =
255 			IncFrameIndex (ElementPtr->current.image.frame);
256 	ElementPtr->state_flags |= CHANGING;
257 }
258 
259 static void
buzzsaw_collision(ELEMENT * ElementPtr0,POINT * pPt0,ELEMENT * ElementPtr1,POINT * pPt1)260 buzzsaw_collision (ELEMENT *ElementPtr0, POINT *pPt0,
261 		ELEMENT *ElementPtr1, POINT *pPt1)
262 {
263 	weapon_collision (ElementPtr0, pPt0, ElementPtr1, pPt1);
264 
265 	if (ElementPtr0->state_flags & DISAPPEARING)
266 	{
267 		ElementPtr0->state_flags &= ~DISAPPEARING;
268 		ElementPtr0->state_flags |= NONSOLID | CHANGING;
269 		ElementPtr0->life_span = 5;
270 		ElementPtr0->next.image.frame =
271 				SetAbsFrameIndex (ElementPtr0->current.image.frame, 2);
272 
273 		ElementPtr0->preprocess_func = splinter_preprocess;
274 	}
275 }
276 
277 static void
buzzsaw_preprocess(ELEMENT * ElementPtr)278 buzzsaw_preprocess (ELEMENT *ElementPtr)
279 {
280 	STARSHIP *StarShipPtr;
281 
282 	GetElementStarShip (ElementPtr, &StarShipPtr);
283 	if (!(StarShipPtr->cur_status_flags & WEAPON))
284 	{
285 		ElementPtr->life_span >>= 1;
286 		ElementPtr->preprocess_func = decelerate_preprocess;
287 	}
288 
289 	spin_preprocess (ElementPtr);
290 }
291 
292 static void
buzzsaw_postprocess(ELEMENT * ElementPtr)293 buzzsaw_postprocess (ELEMENT *ElementPtr)
294 {
295 	HELEMENT hElement;
296 
297 	ElementPtr->postprocess_func = 0;
298 	hElement = AllocElement ();
299 	if (hElement)
300 	{
301 		COUNT primIndex;
302 		ELEMENT *ListElementPtr;
303 		STARSHIP *StarShipPtr;
304 
305 		LockElement (hElement, &ListElementPtr);
306 		primIndex = ListElementPtr->PrimIndex;
307 		*ListElementPtr = *ElementPtr;
308 		ListElementPtr->PrimIndex = primIndex;
309 		(GLOBAL (DisplayArray))[primIndex] =
310 				(GLOBAL (DisplayArray))[ElementPtr->PrimIndex];
311 		ListElementPtr->current = ListElementPtr->next;
312 		InitIntersectStartPoint (ListElementPtr);
313 		InitIntersectEndPoint (ListElementPtr);
314 		ListElementPtr->state_flags = (ListElementPtr->state_flags
315 				& ~(PRE_PROCESS | CHANGING | APPEARING))
316 				| POST_PROCESS;
317 		UnlockElement (hElement);
318 
319 		GetElementStarShip (ElementPtr, &StarShipPtr);
320 		LockElement (StarShipPtr->hShip, &ListElementPtr);
321 		InsertElement (hElement, GetSuccElement (ListElementPtr));
322 		UnlockElement (StarShipPtr->hShip);
323 
324 		ElementPtr->life_span = 0;
325 	}
326 }
327 
328 static COUNT
initialize_buzzsaw(ELEMENT * ShipPtr,HELEMENT SawArray[])329 initialize_buzzsaw (ELEMENT *ShipPtr, HELEMENT SawArray[])
330 {
331 	STARSHIP *StarShipPtr;
332 	MISSILE_BLOCK MissileBlock;
333 
334 	GetElementStarShip (ShipPtr, &StarShipPtr);
335 	MissileBlock.cx = ShipPtr->next.location.x;
336 	MissileBlock.cy = ShipPtr->next.location.y;
337 	MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
338 	MissileBlock.face = StarShipPtr->ShipFacing;
339 	MissileBlock.index = 0;
340 	MissileBlock.sender = ShipPtr->playerNr;
341 	MissileBlock.flags = IGNORE_SIMILAR;
342 	MissileBlock.pixoffs = KOHR_AH_OFFSET;
343 	MissileBlock.speed = MISSILE_SPEED;
344 	MissileBlock.hit_points = MISSILE_HITS;
345 	MissileBlock.damage = MISSILE_DAMAGE;
346 	MissileBlock.life = MISSILE_LIFE;
347 	MissileBlock.preprocess_func = buzzsaw_preprocess;
348 	MissileBlock.blast_offs = MISSILE_OFFSET;
349 	SawArray[0] = initialize_missile (&MissileBlock);
350 
351 	if (SawArray[0])
352 	{
353 		ELEMENT *SawPtr;
354 
355 		LockElement (SawArray[0], &SawPtr);
356 		SawPtr->turn_wait = SAW_RATE;
357 		SawPtr->thrust_wait = 0;
358 		SawPtr->postprocess_func = buzzsaw_postprocess;
359 		SawPtr->collision_func = buzzsaw_collision;
360 		UnlockElement (SawArray[0]);
361 	}
362 
363 	return (1);
364 }
365 
366 static void
black_urquan_intelligence(ELEMENT * ShipPtr,EVALUATE_DESC * ObjectsOfConcern,COUNT ConcernCounter)367 black_urquan_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
368 		COUNT ConcernCounter)
369 {
370 	EVALUATE_DESC *lpEvalDesc;
371 	STARSHIP *StarShipPtr;
372 
373 	lpEvalDesc = &ObjectsOfConcern[ENEMY_WEAPON_INDEX];
374 	if (lpEvalDesc->ObjectPtr
375 			&& lpEvalDesc->MoveState == ENTICE
376 			&& (lpEvalDesc->ObjectPtr->state_flags & CREW_OBJECT)
377 			&& lpEvalDesc->which_turn <= 8)
378 		lpEvalDesc->MoveState = PURSUE;
379 
380 	ship_intelligence (ShipPtr,
381 			ObjectsOfConcern, ConcernCounter);
382 
383 	GetElementStarShip (ShipPtr, &StarShipPtr);
384 	StarShipPtr->ship_input_state &= ~SPECIAL;
385 
386 	if (StarShipPtr->special_counter == 0
387 			&& StarShipPtr->RaceDescPtr->ship_info.energy_level >= SPECIAL_ENERGY_COST
388 			&& lpEvalDesc->ObjectPtr
389 			&& lpEvalDesc->which_turn <= 8)
390 		StarShipPtr->ship_input_state |= SPECIAL;
391 
392 	lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX];
393 	if (lpEvalDesc->ObjectPtr)
394 	{
395 		HELEMENT h, hNext;
396 		ELEMENT *BuzzSawPtr;
397 
398 		h = (StarShipPtr->old_status_flags & WEAPON) ?
399 				GetSuccElement (ShipPtr) : (HELEMENT)0;
400 		for (; h; h = hNext)
401 		{
402 			LockElement (h, &BuzzSawPtr);
403 			hNext = GetSuccElement (BuzzSawPtr);
404 			if (!(BuzzSawPtr->state_flags & NONSOLID)
405 					&& BuzzSawPtr->next.image.farray == StarShipPtr->RaceDescPtr->ship_data.weapon
406 					&& BuzzSawPtr->life_span > MISSILE_LIFE * 3 / 4
407 					&& elementsOfSamePlayer (BuzzSawPtr, ShipPtr))
408 			{
409 				{
410 					//COUNT which_turn;
411 
412 					if (!PlotIntercept (BuzzSawPtr,
413 							lpEvalDesc->ObjectPtr, BuzzSawPtr->life_span,
414 							FRAGMENT_RANGE / 2))
415 						StarShipPtr->ship_input_state &= ~WEAPON;
416 					else if (StarShipPtr->weapon_counter == 0)
417 						StarShipPtr->ship_input_state |= WEAPON;
418 
419 					UnlockElement (h);
420 					break;
421 				}
422 				hNext = 0;
423 			}
424 			UnlockElement (h);
425 		}
426 
427 		if (h == 0)
428 		{
429 			if (StarShipPtr->old_status_flags & WEAPON)
430 				StarShipPtr->ship_input_state &= ~WEAPON;
431 			else if (StarShipPtr->weapon_counter == 0
432 					&& ship_weapons (ShipPtr, lpEvalDesc->ObjectPtr, FRAGMENT_RANGE / 2))
433 				StarShipPtr->ship_input_state |= WEAPON;
434 
435 			if (StarShipPtr->special_counter == 0
436 					&& !(StarShipPtr->ship_input_state & WEAPON)
437 					&& lpEvalDesc->which_turn <= 8
438 					&& (StarShipPtr->ship_input_state & (LEFT | RIGHT))
439 					&& StarShipPtr->RaceDescPtr->ship_info.energy_level >=
440 					SPECIAL_ENERGY_COST)
441 				StarShipPtr->ship_input_state |= SPECIAL;
442 		}
443 	}
444 }
445 
446 static void
gas_cloud_preprocess(ELEMENT * ElementPtr)447 gas_cloud_preprocess (ELEMENT *ElementPtr)
448 {
449 	if (ElementPtr->turn_wait)
450 		--ElementPtr->turn_wait;
451 	else
452 	{
453 		ElementPtr->next.image.frame =
454 				IncFrameIndex (ElementPtr->current.image.frame);
455 		ElementPtr->state_flags |= CHANGING;
456 
457 		ElementPtr->turn_wait = GAS_RATE;
458 	}
459 }
460 
461 static void
gas_cloud_collision(ELEMENT * ElementPtr0,POINT * pPt0,ELEMENT * ElementPtr1,POINT * pPt1)462 gas_cloud_collision (ELEMENT *ElementPtr0, POINT *pPt0,
463 		ELEMENT *ElementPtr1, POINT *pPt1)
464 {
465 	if (ElementPtr1->state_flags & PLAYER_SHIP)
466 		ElementPtr0->mass_points = GAS_DAMAGE;
467 	else
468 		ElementPtr0->mass_points = GAS_ALT_DAMAGE;
469 
470 	weapon_collision (ElementPtr0, pPt0, ElementPtr1, pPt1);
471 }
472 
473 static void
spawn_gas_cloud(ELEMENT * ElementPtr)474 spawn_gas_cloud (ELEMENT *ElementPtr)
475 {
476 	SIZE dx, dy;
477 	STARSHIP *StarShipPtr;
478 	MISSILE_BLOCK MissileBlock;
479 
480 	GetElementStarShip (ElementPtr, &StarShipPtr);
481 	MissileBlock.cx = ElementPtr->next.location.x;
482 	MissileBlock.cy = ElementPtr->next.location.y;
483 	MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.special;
484 	MissileBlock.index = 0;
485 	MissileBlock.sender = ElementPtr->playerNr;
486 	MissileBlock.flags = IGNORE_SIMILAR;
487 	MissileBlock.pixoffs = 20;
488 	MissileBlock.speed = GAS_SPEED;
489 	MissileBlock.hit_points = GAS_HITS;
490 	MissileBlock.damage = GAS_DAMAGE;
491 	MissileBlock.life =
492 			GetFrameCount (MissileBlock.farray[0]) * (GAS_RATE + 1) - 1;
493 	MissileBlock.preprocess_func = gas_cloud_preprocess;
494 	MissileBlock.blast_offs = GAS_OFFSET;
495 
496 	GetCurrentVelocityComponents (&ElementPtr->velocity, &dx, &dy);
497 	for (MissileBlock.face = 0;
498 			MissileBlock.face < ANGLE_TO_FACING (FULL_CIRCLE);
499 			MissileBlock.face +=
500 			(ANGLE_TO_FACING (FULL_CIRCLE) / NUM_GAS_CLOUDS))
501 	{
502 		HELEMENT hGasCloud;
503 
504 		hGasCloud = initialize_missile (&MissileBlock);
505 		if (hGasCloud)
506 		{
507 			ELEMENT *GasCloudPtr;
508 
509 			LockElement (hGasCloud, &GasCloudPtr);
510 			SetElementStarShip (GasCloudPtr, StarShipPtr);
511 			GasCloudPtr->hTarget = 0;
512 			GasCloudPtr->turn_wait = GAS_RATE - 1;
513 			GasCloudPtr->collision_func = gas_cloud_collision;
514 			DeltaVelocityComponents (&GasCloudPtr->velocity, dx, dy);
515 			UnlockElement (hGasCloud);
516 			PutElement (hGasCloud);
517 		}
518 	}
519 }
520 
521 static void
black_urquan_postprocess(ELEMENT * ElementPtr)522 black_urquan_postprocess (ELEMENT *ElementPtr)
523 {
524 	STARSHIP *StarShipPtr;
525 
526 	GetElementStarShip (ElementPtr, &StarShipPtr);
527 	if ((StarShipPtr->cur_status_flags & SPECIAL)
528 			&& StarShipPtr->special_counter == 0
529 			&& DeltaEnergy (ElementPtr, -SPECIAL_ENERGY_COST))
530 	{
531 		spawn_gas_cloud (ElementPtr);
532 
533 		ProcessSound (SetAbsSoundIndex (
534 				StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1), ElementPtr);
535 
536 		StarShipPtr->special_counter = SPECIAL_WAIT;
537 	}
538 }
539 
540 static void
black_urquan_preprocess(ELEMENT * ElementPtr)541 black_urquan_preprocess (ELEMENT *ElementPtr)
542 {
543 	STARSHIP *StarShipPtr;
544 
545 	GetElementStarShip (ElementPtr, &StarShipPtr);
546 			/* no spinning disks */
547 	StarShipPtr->RaceDescPtr->characteristics.special_wait = 0;
548 	if (StarShipPtr->weapon_counter == 0
549 			&& (StarShipPtr->cur_status_flags
550 			& StarShipPtr->old_status_flags & WEAPON))
551 		++StarShipPtr->weapon_counter;
552 }
553 
554 RACE_DESC*
init_black_urquan(void)555 init_black_urquan (void)
556 {
557 	RACE_DESC *RaceDescPtr;
558 
559 	black_urquan_desc.preprocess_func = black_urquan_preprocess;
560 	black_urquan_desc.postprocess_func = black_urquan_postprocess;
561 	black_urquan_desc.init_weapon_func = initialize_buzzsaw;
562 	black_urquan_desc.cyborg_control.intelligence_func = black_urquan_intelligence;
563 
564 	RaceDescPtr = &black_urquan_desc;
565 
566 	return (RaceDescPtr);
567 }
568