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 "urquan.h"
21 #include "resinst.h"
22
23 #include "uqm/globdata.h"
24
25 #include <stdlib.h>
26
27 // Core characteristics
28 #define MAX_CREW MAX_CREW_SIZE
29 #define MAX_ENERGY MAX_ENERGY_SIZE
30 #define ENERGY_REGENERATION 1
31 #define ENERGY_WAIT 4
32 #define MAX_THRUST 30
33 #define THRUST_INCREMENT 6
34 #define THRUST_WAIT 6
35 #define TURN_WAIT 4
36 #define SHIP_MASS 10
37
38 // Fusion blast
39 #define WEAPON_ENERGY_COST 6
40 #define WEAPON_WAIT 6
41 #define MISSILE_SPEED DISPLAY_TO_WORLD (20)
42 #define MISSILE_LIFE 20
43 #define MISSILE_HITS 10
44 #define MISSILE_DAMAGE 6
45 #define MISSILE_OFFSET 8
46 #define URQUAN_OFFSET 32
47
48 // Fighters
49 #define SPECIAL_ENERGY_COST 8
50 #define SPECIAL_WAIT 9
51 #define FIGHTER_OFFSET 4
52 #define FIGHTER_SPEED DISPLAY_TO_WORLD (8)
53 #define ONE_WAY_FLIGHT 125
54 #define TRACK_THRESHOLD 6
55 #define FIGHTER_LIFE (ONE_WAY_FLIGHT + ONE_WAY_FLIGHT + 150)
56 #define FIGHTER_HITS 1
57 #define FIGHTER_MASS 0
58 #define FIGHTER_WEAPON_WAIT 8
59 #define FIGHTER_LASER_RANGE DISPLAY_TO_WORLD (40 + FIGHTER_OFFSET)
60
61 static RACE_DESC urquan_desc =
62 {
63 { /* SHIP_INFO */
64 FIRES_FORE | SEEKING_SPECIAL,
65 30, /* Super Melee cost */
66 MAX_CREW, MAX_CREW,
67 MAX_ENERGY, MAX_ENERGY,
68 URQUAN_RACE_STRINGS,
69 URQUAN_ICON_MASK_PMAP_ANIM,
70 URQUAN_MICON_MASK_PMAP_ANIM,
71 NULL, NULL, NULL
72 },
73 { /* FLEET_STUFF */
74 2666 / SPHERE_RADIUS_INCREMENT * 2, /* Initial SoI radius */
75 { /* Known location (center of SoI) */
76 5750, 6000,
77 },
78 },
79 {
80 MAX_THRUST,
81 THRUST_INCREMENT,
82 ENERGY_REGENERATION,
83 WEAPON_ENERGY_COST,
84 SPECIAL_ENERGY_COST,
85 ENERGY_WAIT,
86 TURN_WAIT,
87 THRUST_WAIT,
88 WEAPON_WAIT,
89 SPECIAL_WAIT,
90 SHIP_MASS,
91 },
92 {
93 {
94 URQUAN_BIG_MASK_PMAP_ANIM,
95 URQUAN_MED_MASK_PMAP_ANIM,
96 URQUAN_SML_MASK_PMAP_ANIM,
97 },
98 {
99 FUSION_BIG_MASK_PMAP_ANIM,
100 FUSION_MED_MASK_PMAP_ANIM,
101 FUSION_SML_MASK_PMAP_ANIM,
102 },
103 {
104 FIGHTER_BIG_MASK_PMAP_ANIM,
105 FIGHTER_MED_MASK_PMAP_ANIM,
106 FIGHTER_SML_MASK_PMAP_ANIM,
107 },
108 {
109 URQUAN_CAPTAIN_MASK_PMAP_ANIM,
110 NULL, NULL, NULL, NULL, NULL
111 },
112 URQUAN_VICTORY_SONG,
113 URQUAN_SHIP_SOUNDS,
114 { NULL, NULL, NULL },
115 { NULL, NULL, NULL },
116 { NULL, NULL, NULL },
117 NULL, NULL
118 },
119 {
120 0,
121 MISSILE_SPEED * MISSILE_LIFE,
122 NULL,
123 },
124 (UNINIT_FUNC *) NULL,
125 (PREPROCESS_FUNC *) NULL,
126 (POSTPROCESS_FUNC *) NULL,
127 (INIT_WEAPON_FUNC *) NULL,
128 0,
129 0, /* CodeRef */
130 };
131
132 static COUNT
initialize_fusion(ELEMENT * ShipPtr,HELEMENT FusionArray[])133 initialize_fusion (ELEMENT *ShipPtr, HELEMENT FusionArray[])
134 {
135 STARSHIP *StarShipPtr;
136 MISSILE_BLOCK MissileBlock;
137
138 GetElementStarShip (ShipPtr, &StarShipPtr);
139 MissileBlock.cx = ShipPtr->next.location.x;
140 MissileBlock.cy = ShipPtr->next.location.y;
141 MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon;
142 MissileBlock.face = MissileBlock.index = StarShipPtr->ShipFacing;
143 MissileBlock.sender = ShipPtr->playerNr;
144 MissileBlock.flags = IGNORE_SIMILAR;
145 MissileBlock.pixoffs = URQUAN_OFFSET;
146 MissileBlock.speed = MISSILE_SPEED;
147 MissileBlock.hit_points = MISSILE_HITS;
148 MissileBlock.damage = MISSILE_DAMAGE;
149 MissileBlock.life = MISSILE_LIFE;
150 MissileBlock.preprocess_func = NULL;
151 MissileBlock.blast_offs = MISSILE_OFFSET;
152 FusionArray[0] = initialize_missile (&MissileBlock);
153
154 return (1);
155 }
156
157 static void
fighter_postprocess(ELEMENT * ElementPtr)158 fighter_postprocess (ELEMENT *ElementPtr)
159 {
160 HELEMENT Laser;
161 STARSHIP *StarShipPtr;
162 LASER_BLOCK LaserBlock;
163
164 GetElementStarShip (ElementPtr, &StarShipPtr);
165 LaserBlock.cx = ElementPtr->next.location.x;
166 LaserBlock.cy = ElementPtr->next.location.y;
167 LaserBlock.face = ElementPtr->thrust_wait;
168 LaserBlock.ex = COSINE (FACING_TO_ANGLE (LaserBlock.face), FIGHTER_LASER_RANGE);
169 LaserBlock.ey = SINE (FACING_TO_ANGLE (LaserBlock.face), FIGHTER_LASER_RANGE);
170 LaserBlock.sender = ElementPtr->playerNr;
171 LaserBlock.flags = IGNORE_SIMILAR;
172 LaserBlock.pixoffs = FIGHTER_OFFSET;
173 LaserBlock.color = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x0A), 0x0E);
174 Laser = initialize_laser (&LaserBlock);
175 if (Laser)
176 {
177 ELEMENT *LaserPtr;
178
179 LockElement (Laser, &LaserPtr);
180 SetElementStarShip (LaserPtr, StarShipPtr);
181
182 ProcessSound (SetAbsSoundIndex (
183 /* FIGHTER_ZAP */
184 StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 2), LaserPtr);
185
186 UnlockElement (Laser);
187 PutElement (Laser);
188 }
189
190 ElementPtr->postprocess_func = 0;
191 ElementPtr->thrust_wait = FIGHTER_WEAPON_WAIT;
192 }
193
194 static void
fighter_preprocess(ELEMENT * ElementPtr)195 fighter_preprocess (ELEMENT *ElementPtr)
196 {
197 STARSHIP *StarShipPtr;
198
199 GetElementStarShip (ElementPtr, &StarShipPtr);
200
201 ++StarShipPtr->RaceDescPtr->characteristics.special_wait;
202 if (FIGHTER_LIFE - ElementPtr->life_span > TRACK_THRESHOLD
203 && !(ElementPtr->state_flags & CHANGING))
204 {
205 BOOLEAN Enroute;
206 COUNT orig_facing, facing;
207 SIZE delta_x, delta_y;
208 ELEMENT *eptr;
209
210 Enroute = TRUE;
211
212 delta_x = StarShipPtr->RaceDescPtr->ship_info.crew_level;
213 delta_y = ElementPtr->life_span;
214
215 orig_facing = facing =
216 GetFrameIndex (ElementPtr->current.image.frame);
217 if (((delta_y & 1) || ElementPtr->hTarget
218 || TrackShip (ElementPtr, &facing) >= 0)
219 && (delta_x == 0 || delta_y >= ONE_WAY_FLIGHT))
220 ElementPtr->state_flags |= IGNORE_SIMILAR;
221 else if (delta_x)
222 {
223 LockElement (StarShipPtr->hShip, &eptr);
224 delta_x = eptr->current.location.x
225 - ElementPtr->current.location.x;
226 delta_y = eptr->current.location.y
227 - ElementPtr->current.location.y;
228 UnlockElement (StarShipPtr->hShip);
229 delta_x = WRAP_DELTA_X (delta_x);
230 delta_y = WRAP_DELTA_Y (delta_y);
231 facing = NORMALIZE_FACING (
232 ANGLE_TO_FACING (ARCTAN (delta_x, delta_y))
233 );
234
235 #ifdef NEVER
236 if (delta_x < 0)
237 delta_x = -delta_x;
238 if (delta_y < 0)
239 delta_y = -delta_y;
240 if (delta_x <= LASER_RANGE && delta_y <= LASER_RANGE)
241 #endif /* NEVER */
242 ElementPtr->state_flags &= ~IGNORE_SIMILAR;
243
244 Enroute = FALSE;
245 }
246
247 if (ElementPtr->thrust_wait > 0)
248 --ElementPtr->thrust_wait;
249
250 if (ElementPtr->hTarget)
251 {
252 LockElement (ElementPtr->hTarget, &eptr);
253 delta_x = eptr->current.location.x
254 - ElementPtr->current.location.x;
255 delta_y = eptr->current.location.y
256 - ElementPtr->current.location.y;
257 UnlockElement (ElementPtr->hTarget);
258 delta_x = WRAP_DELTA_X (delta_x);
259 delta_y = WRAP_DELTA_Y (delta_y);
260
261 if (ElementPtr->thrust_wait == 0
262 && abs (delta_x) < FIGHTER_LASER_RANGE * 3 / 4
263 && abs (delta_y) < FIGHTER_LASER_RANGE * 3 / 4
264 && delta_x * delta_x + delta_y * delta_y <
265 (FIGHTER_LASER_RANGE * 3 / 4) * (FIGHTER_LASER_RANGE * 3 / 4))
266 {
267 ElementPtr->thrust_wait =
268 (BYTE)NORMALIZE_FACING (
269 ANGLE_TO_FACING (ARCTAN (delta_x, delta_y))
270 );
271 ElementPtr->postprocess_func = fighter_postprocess;
272 }
273
274 if (Enroute)
275 {
276 facing = GetFrameIndex (eptr->current.image.frame);
277 if (ElementPtr->turn_wait & LEFT)
278 {
279 delta_x += COSINE (FACING_TO_ANGLE (facing - 4),
280 DISPLAY_TO_WORLD (30));
281 delta_y += SINE (FACING_TO_ANGLE (facing - 4),
282 DISPLAY_TO_WORLD (30));
283 }
284 else
285 {
286 delta_x += COSINE (FACING_TO_ANGLE (facing + 4),
287 DISPLAY_TO_WORLD (30));
288 delta_y += SINE (FACING_TO_ANGLE (facing + 4),
289 DISPLAY_TO_WORLD (30));
290 }
291 facing = NORMALIZE_FACING (
292 ANGLE_TO_FACING (ARCTAN (delta_x, delta_y))
293 );
294 }
295 }
296 ElementPtr->state_flags |= CHANGING;
297
298 if (facing != orig_facing)
299 ElementPtr->next.image.frame = SetAbsFrameIndex (
300 ElementPtr->next.image.frame, facing
301 );
302 SetVelocityVector (
303 &ElementPtr->velocity, FIGHTER_SPEED, facing
304 );
305 }
306 }
307
308 static void
fighter_collision(ELEMENT * ElementPtr0,POINT * pPt0,ELEMENT * ElementPtr1,POINT * pPt1)309 fighter_collision (ELEMENT *ElementPtr0, POINT *pPt0,
310 ELEMENT *ElementPtr1, POINT *pPt1)
311 {
312 STARSHIP *StarShipPtr;
313
314 GetElementStarShip (ElementPtr0, &StarShipPtr);
315 if (GRAVITY_MASS (ElementPtr1->mass_points))
316 {
317 HELEMENT hFighterElement;
318
319 hFighterElement = AllocElement ();
320 if (hFighterElement)
321 {
322 COUNT primIndex, travel_facing;
323 SIZE delta_facing;
324 ELEMENT *FighterElementPtr;
325
326 LockElement (hFighterElement, &FighterElementPtr);
327 primIndex = FighterElementPtr->PrimIndex;
328 *FighterElementPtr = *ElementPtr0;
329 FighterElementPtr->PrimIndex = primIndex;
330 (GLOBAL (DisplayArray))[primIndex] =
331 (GLOBAL (DisplayArray))[ElementPtr0->PrimIndex];
332 FighterElementPtr->state_flags &= ~PRE_PROCESS;
333 FighterElementPtr->state_flags |= CHANGING;
334 FighterElementPtr->next = FighterElementPtr->current;
335 travel_facing = GetVelocityTravelAngle (
336 &FighterElementPtr->velocity
337 );
338 delta_facing = NORMALIZE_ANGLE (
339 ARCTAN (pPt1->x - pPt0->x, pPt1->y - pPt0->y)
340 - travel_facing);
341 if (delta_facing == 0)
342 {
343 if (FighterElementPtr->turn_wait & LEFT)
344 travel_facing -= QUADRANT;
345 else
346 travel_facing += QUADRANT;
347 }
348 else if (delta_facing <= HALF_CIRCLE)
349 travel_facing -= QUADRANT;
350 else
351 travel_facing += QUADRANT;
352
353 travel_facing = NORMALIZE_FACING (ANGLE_TO_FACING (
354 NORMALIZE_ANGLE (travel_facing)
355 ));
356 FighterElementPtr->next.image.frame =
357 SetAbsFrameIndex (FighterElementPtr->next.image.frame,
358 travel_facing);
359 SetVelocityVector (&FighterElementPtr->velocity,
360 FIGHTER_SPEED, travel_facing);
361 UnlockElement (hFighterElement);
362
363 PutElement (hFighterElement);
364 }
365
366 ElementPtr0->state_flags |= DISAPPEARING | COLLISION;
367 }
368 else if (ElementPtr0->pParent != ElementPtr1->pParent)
369 {
370 ElementPtr0->blast_offset = 0;
371 weapon_collision (ElementPtr0, pPt0, ElementPtr1, pPt1);
372 ElementPtr0->state_flags |= DISAPPEARING | COLLISION;
373 }
374 else if (ElementPtr1->state_flags & PLAYER_SHIP)
375 {
376 ProcessSound (SetAbsSoundIndex (
377 /* FIGHTERS_RETURN */
378 StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 3), ElementPtr1);
379 DeltaCrew (ElementPtr1, 1);
380 ElementPtr0->state_flags |= DISAPPEARING | COLLISION;
381 }
382
383 if (ElementPtr0->state_flags & DISAPPEARING)
384 {
385 ElementPtr0->state_flags &= ~DISAPPEARING;
386
387 ElementPtr0->hit_points = 0;
388 ElementPtr0->life_span = 0;
389 ElementPtr0->state_flags |= NONSOLID;
390
391 --StarShipPtr->RaceDescPtr->characteristics.special_wait;
392 }
393 }
394
395 static void
spawn_fighters(ELEMENT * ElementPtr)396 spawn_fighters (ELEMENT *ElementPtr)
397 {
398 SIZE i;
399 COUNT facing;
400 SIZE delta_x, delta_y;
401 HELEMENT hFighterElement;
402 STARSHIP *StarShipPtr;
403
404 GetElementStarShip (ElementPtr, &StarShipPtr);
405 facing = StarShipPtr->ShipFacing + ANGLE_TO_FACING (HALF_CIRCLE);
406 delta_x = COSINE (FACING_TO_ANGLE (facing), DISPLAY_TO_WORLD (14));
407 delta_y = SINE (FACING_TO_ANGLE (facing), DISPLAY_TO_WORLD (14));
408
409 i = ElementPtr->crew_level > 2 ? 2 : 1;
410 while (i-- && (hFighterElement = AllocElement ()))
411 {
412 SIZE sx, sy;
413 COUNT fighter_facing;
414 ELEMENT *FighterElementPtr;
415
416 DeltaCrew (ElementPtr, -1);
417
418 PutElement (hFighterElement);
419 LockElement (hFighterElement, &FighterElementPtr);
420 FighterElementPtr->hit_points = FIGHTER_HITS;
421 FighterElementPtr->mass_points = FIGHTER_MASS;
422 FighterElementPtr->thrust_wait = TRACK_THRESHOLD + 1;
423 FighterElementPtr->playerNr = ElementPtr->playerNr;
424 FighterElementPtr->state_flags = APPEARING | FINITE_LIFE
425 | CREW_OBJECT | IGNORE_SIMILAR;
426 FighterElementPtr->life_span = FIGHTER_LIFE;
427 SetPrimType (&(GLOBAL (DisplayArray))[FighterElementPtr->PrimIndex],
428 STAMP_PRIM);
429 {
430 FighterElementPtr->preprocess_func = fighter_preprocess;
431 FighterElementPtr->postprocess_func = 0;
432 FighterElementPtr->collision_func = fighter_collision;
433 FighterElementPtr->death_func = NULL;
434 }
435
436 FighterElementPtr->current.location = ElementPtr->next.location;
437 if (i == 1)
438 {
439 FighterElementPtr->turn_wait = LEFT;
440 fighter_facing = NORMALIZE_FACING (facing + 2);
441 FighterElementPtr->current.location.x += delta_x - delta_y;
442 FighterElementPtr->current.location.y += delta_y + delta_x;
443 }
444 else
445 {
446 FighterElementPtr->turn_wait = RIGHT;
447 fighter_facing = NORMALIZE_FACING (facing - 2);
448 FighterElementPtr->current.location.x += delta_x + delta_y;
449 FighterElementPtr->current.location.y += delta_y - delta_x;
450 }
451 sx = COSINE (FACING_TO_ANGLE (fighter_facing),
452 WORLD_TO_VELOCITY (FIGHTER_SPEED));
453 sy = SINE (FACING_TO_ANGLE (fighter_facing),
454 WORLD_TO_VELOCITY (FIGHTER_SPEED));
455 SetVelocityComponents (&FighterElementPtr->velocity, sx, sy);
456 FighterElementPtr->current.location.x -= VELOCITY_TO_WORLD (sx);
457 FighterElementPtr->current.location.y -= VELOCITY_TO_WORLD (sy);
458
459 FighterElementPtr->current.image.farray = StarShipPtr->RaceDescPtr->ship_data.special;
460 FighterElementPtr->current.image.frame =
461 SetAbsFrameIndex (StarShipPtr->RaceDescPtr->ship_data.special[0],
462 fighter_facing);
463 SetElementStarShip (FighterElementPtr, StarShipPtr);
464 UnlockElement (hFighterElement);
465 }
466 }
467
468 static void
urquan_intelligence(ELEMENT * ShipPtr,EVALUATE_DESC * ObjectsOfConcern,COUNT ConcernCounter)469 urquan_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern,
470 COUNT ConcernCounter)
471 {
472 EVALUATE_DESC *lpEvalDesc;
473 STARSHIP *StarShipPtr;
474
475 GetElementStarShip (ShipPtr, &StarShipPtr);
476
477 ObjectsOfConcern[ENEMY_SHIP_INDEX].MoveState = PURSUE;
478 lpEvalDesc = &ObjectsOfConcern[ENEMY_WEAPON_INDEX];
479 if (lpEvalDesc->ObjectPtr
480 && lpEvalDesc->MoveState == ENTICE
481 && (!(lpEvalDesc->ObjectPtr->state_flags & CREW_OBJECT)
482 || lpEvalDesc->which_turn <= 8)
483 && (!(lpEvalDesc->ObjectPtr->state_flags & FINITE_LIFE)
484 || (lpEvalDesc->ObjectPtr->mass_points >= 4
485 && lpEvalDesc->which_turn == 2
486 && ObjectsOfConcern[ENEMY_SHIP_INDEX].which_turn > 16)))
487 lpEvalDesc->MoveState = PURSUE;
488
489 ship_intelligence (ShipPtr,
490 ObjectsOfConcern, ConcernCounter);
491
492 lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX];
493 {
494 STARSHIP *EnemyStarShipPtr = NULL;
495
496 if (lpEvalDesc->ObjectPtr)
497 GetElementStarShip (lpEvalDesc->ObjectPtr, &EnemyStarShipPtr);
498 if (StarShipPtr->special_counter == 0
499 && lpEvalDesc->ObjectPtr
500 && StarShipPtr->RaceDescPtr->ship_info.crew_level >
501 (StarShipPtr->RaceDescPtr->ship_info.max_crew >> 2)
502 && !(EnemyStarShipPtr->RaceDescPtr->ship_info.ship_flags
503 & POINT_DEFENSE)
504 && (StarShipPtr->RaceDescPtr->characteristics.special_wait < 6
505 || (MANEUVERABILITY (
506 &EnemyStarShipPtr->RaceDescPtr->cyborg_control
507 ) <= SLOW_SHIP
508 && !(EnemyStarShipPtr->cur_status_flags & SHIP_BEYOND_MAX_SPEED))
509 || (lpEvalDesc->which_turn <= 12
510 && (StarShipPtr->ship_input_state & (LEFT | RIGHT))
511 && StarShipPtr->RaceDescPtr->ship_info.energy_level >=
512 (BYTE)(StarShipPtr->RaceDescPtr->ship_info.max_energy >> 1))))
513 StarShipPtr->ship_input_state |= SPECIAL;
514 else
515 StarShipPtr->ship_input_state &= ~SPECIAL;
516 }
517
518 StarShipPtr->RaceDescPtr->characteristics.special_wait = 0;
519 }
520
521 static void
urquan_postprocess(ELEMENT * ElementPtr)522 urquan_postprocess (ELEMENT *ElementPtr)
523 {
524 STARSHIP *StarShipPtr;
525
526 GetElementStarShip (ElementPtr, &StarShipPtr);
527 if ((StarShipPtr->cur_status_flags & SPECIAL)
528 && ElementPtr->crew_level > 1
529 && StarShipPtr->special_counter == 0
530 && DeltaEnergy (ElementPtr, -SPECIAL_ENERGY_COST))
531 {
532 ProcessSound (SetAbsSoundIndex (
533 /* LAUNCH_FIGHTERS */
534 StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1), ElementPtr);
535 spawn_fighters (ElementPtr);
536
537 StarShipPtr->special_counter = SPECIAL_WAIT;
538 }
539 }
540
541 RACE_DESC*
init_urquan(void)542 init_urquan (void)
543 {
544 RACE_DESC *RaceDescPtr;
545
546 urquan_desc.postprocess_func = urquan_postprocess;
547 urquan_desc.init_weapon_func = initialize_fusion;
548 urquan_desc.cyborg_control.intelligence_func = urquan_intelligence;
549
550 RaceDescPtr = &urquan_desc;
551
552 return (RaceDescPtr);
553 }
554
555