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 "ipdisp.h"
20 
21 #include "collide.h"
22 #include "globdata.h"
23 #include "init.h"
24 #include "races.h"
25 #include "process.h"
26 #include "grpinfo.h"
27 #include "encount.h"
28 		// for EncounterGroup, EncounterRace
29 #include "libs/mathlib.h"
30 
31 
32 void
NotifyOthers(COUNT which_race,BYTE target_loc)33 NotifyOthers (COUNT which_race, BYTE target_loc)
34 {
35 	HSHIPFRAG hGroup, hNextGroup;
36 
37 	// NOTE: "Others" includes the group causing the notification too.
38 
39 	for (hGroup = GetHeadLink (&GLOBAL (ip_group_q));
40 			hGroup; hGroup = hNextGroup)
41 	{
42 		IP_GROUP *GroupPtr;
43 
44 		GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup);
45 		hNextGroup = _GetSuccLink (GroupPtr);
46 
47 		if (GroupPtr->race_id != which_race)
48 		{
49 			UnlockIpGroup (&GLOBAL (ip_group_q), hGroup);
50 			continue;
51 		}
52 
53 		if (target_loc == IPNL_INTERCEPT_PLAYER)
54 		{
55 			GroupPtr->task &= ~IGNORE_FLAGSHIP;
56 			// XXX: orbit_pos is abused here to store the previous
57 			//   group destination, before the intercept task.
58 			//   Returned to dest_loc below.
59 			GroupPtr->orbit_pos = GroupPtr->dest_loc;
60 			GroupPtr->dest_loc = IPNL_INTERCEPT_PLAYER;
61 		}
62 		else if (target_loc == IPNL_ALL_CLEAR)
63 		{
64 			GroupPtr->task |= IGNORE_FLAGSHIP;
65 
66 			if (GroupPtr->dest_loc == IPNL_INTERCEPT_PLAYER)
67 			{	// The group was intercepting, so send it back where it came
68 				// XXX: orbit_pos was abused to store the previous
69 				//   group destination, before the intercept task.
70 				GroupPtr->dest_loc = GroupPtr->orbit_pos;
71 				GroupPtr->orbit_pos = NORMALIZE_FACING (TFB_Random ());
72 #ifdef OLD
73 				GroupPtr->dest_loc = (BYTE)(((COUNT)TFB_Random ()
74 						% pSolarSysState->SunDesc[0].NumPlanets) + 1);
75 #endif /* OLD */
76 			}
77 			// If the group wasn't intercepting, it will just continue
78 			// going about its business.
79 
80 			if (!(GroupPtr->task & REFORM_GROUP))
81 			{
82 				if ((GroupPtr->task & ~IGNORE_FLAGSHIP) != EXPLORE)
83 					GroupPtr->group_counter = 0;
84 				else
85 					GroupPtr->group_counter = ((COUNT) TFB_Random ()
86 							% MAX_REVOLUTIONS) << FACING_SHIFT;
87 			}
88 		}
89 		else
90 		{	// Send the group to the location.
91 			// XXX: There is currently no use of such notify that I know of.
92 			GroupPtr->task |= IGNORE_FLAGSHIP;
93 			GroupPtr->dest_loc = target_loc;
94 		}
95 
96 		UnlockIpGroup (&GLOBAL (ip_group_q), hGroup);
97 	}
98 }
99 
100 static SIZE
zoomRadiusForLocation(BYTE location)101 zoomRadiusForLocation (BYTE location)
102 {
103 	if (location == 0)
104 	{	// In outer system view; use current zoom radius
105 		return pSolarSysState->SunDesc[0].radius;
106 	}
107 	else
108 	{	// In inner system view; always max zoom
109 		return MAX_ZOOM_RADIUS;
110 	}
111 }
112 
113 static inline void
adjustDeltaVforZoom(SIZE zoom,SIZE * dx,SIZE * dy)114 adjustDeltaVforZoom (SIZE zoom, SIZE *dx, SIZE *dy)
115 {
116 	if (zoom == MIN_ZOOM_RADIUS)
117 	{
118 		*dx >>= 2;
119 		*dy >>= 2;
120 	}
121 	else if (zoom < MAX_ZOOM_RADIUS)
122 	{
123 		*dx >>= 1;
124 		*dy >>= 1;
125 	}
126 }
127 
128 static BYTE
getFlagshipLocation(void)129 getFlagshipLocation (void)
130 {
131 	if (!playerInInnerSystem ())
132 		return 0;
133 	else
134 		return 1 + planetIndex (pSolarSysState, pSolarSysState->pOrbitalDesc);
135 }
136 
137 static void
ip_group_preprocess(ELEMENT * ElementPtr)138 ip_group_preprocess (ELEMENT *ElementPtr)
139 {
140 #define TRACK_WAIT 5
141 	BYTE task;
142 	BYTE target_loc, group_loc, flagship_loc;
143 	SIZE radius;
144 	POINT dest_pt;
145 	SIZE vdx, vdy;
146 	ELEMENT *EPtr;
147 	IP_GROUP *GroupPtr;
148 
149 	EPtr = ElementPtr;
150 	EPtr->state_flags &= ~(DISAPPEARING | NONSOLID); // "I'm not quite dead"
151 	++EPtr->life_span; // so that it will 'die' again next time
152 
153 	GetElementStarShip (EPtr, &GroupPtr);
154 	group_loc = GroupPtr->sys_loc; // save old location
155 	DisplayArray[EPtr->PrimIndex].Object.Point = GroupPtr->loc;
156 
157 	radius = zoomRadiusForLocation (group_loc);
158 	dest_pt = locationToDisplay (GroupPtr->loc, radius);
159 	EPtr->current.location.x = DISPLAY_TO_WORLD (dest_pt.x)
160 			+ (COORD)(LOG_SPACE_WIDTH >> 1)
161 			- (LOG_SPACE_WIDTH >> (MAX_REDUCTION + 1));
162 	EPtr->current.location.y = DISPLAY_TO_WORLD (dest_pt.y)
163 			+ (COORD)(LOG_SPACE_HEIGHT >> 1)
164 			- (LOG_SPACE_HEIGHT >> (MAX_REDUCTION + 1));
165 
166 	InitIntersectStartPoint (EPtr);
167 
168 	flagship_loc = getFlagshipLocation ();
169 
170 	task = GroupPtr->task;
171 
172 	if ((task & REFORM_GROUP) && --GroupPtr->group_counter == 0)
173 	{	// Finished reforming the group
174 		task &= ~REFORM_GROUP;
175 		GroupPtr->task = task;
176 		if ((task & ~IGNORE_FLAGSHIP) != EXPLORE)
177 			GroupPtr->group_counter = 0;
178 		else
179 			GroupPtr->group_counter = ((COUNT)TFB_Random ()
180 					% MAX_REVOLUTIONS) << FACING_SHIFT;
181 	}
182 
183 	// If fleeing *and* ignoring flagship
184 	if ((task & ~(IGNORE_FLAGSHIP | REFORM_GROUP)) == FLEE
185 			&& (task & IGNORE_FLAGSHIP))
186 	{	// Make fleeing groups non-collidable
187 		EPtr->state_flags |= NONSOLID;
188 	}
189 
190 	target_loc = GroupPtr->dest_loc;
191 	if (!(task & (IGNORE_FLAGSHIP | REFORM_GROUP)))
192 	{
193 		if (target_loc == IPNL_INTERCEPT_PLAYER && task != FLEE)
194 		{
195 			/* if intercepting flagship */
196 			target_loc = flagship_loc;
197 			if (EPtr->thrust_wait > TRACK_WAIT)
198 			{
199 				EPtr->thrust_wait = 0;
200 				ZeroVelocityComponents (&EPtr->velocity);
201 			}
202 		}
203 		else if (group_loc == flagship_loc)
204 		{
205 			long detect_dist;
206 
207 			detect_dist = 1200;
208 			if (group_loc != 0) /* if in planetary views */
209 			{
210 				detect_dist *= (MAX_ZOOM_RADIUS / MIN_ZOOM_RADIUS);
211 				if (GroupPtr->race_id == URQUAN_DRONE_SHIP)
212 					detect_dist <<= 1;
213 			}
214 			vdx = GLOBAL (ip_location.x) - GroupPtr->loc.x;
215 			vdy = GLOBAL (ip_location.y) - GroupPtr->loc.y;
216 			if ((long)vdx * vdx
217 					+ (long)vdy * vdy < (long)detect_dist * detect_dist)
218 			{
219 				EPtr->thrust_wait = 0;
220 				ZeroVelocityComponents (&EPtr->velocity);
221 
222 				NotifyOthers (GroupPtr->race_id, IPNL_INTERCEPT_PLAYER);
223 				task = GroupPtr->task;
224 				target_loc = GroupPtr->dest_loc;
225 				if (target_loc == IPNL_INTERCEPT_PLAYER)
226 					target_loc = flagship_loc;
227 			}
228 		}
229 	}
230 
231 	GetCurrentVelocityComponents (&EPtr->velocity, &vdx, &vdy);
232 
233 	task &= ~IGNORE_FLAGSHIP;
234 #ifdef NEVER
235 	if (task <= FLEE || (task == ON_STATION	&& GroupPtr->dest_loc == 0))
236 #else
237 	if (task <= ON_STATION)
238 #endif /* NEVER */
239 	{
240 		BOOLEAN Transition;
241 		SIZE dx, dy;
242 		SIZE delta_x, delta_y;
243 		COUNT angle;
244 
245 		Transition = FALSE;
246 		if (task == FLEE)
247 		{
248 			dest_pt.x = GroupPtr->loc.x << 1;
249 			dest_pt.y = GroupPtr->loc.y << 1;
250 		}
251 		else if (((task != ON_STATION ||
252 				GroupPtr->dest_loc == IPNL_INTERCEPT_PLAYER)
253 				&& group_loc == target_loc)
254 				|| (task == ON_STATION &&
255 				GroupPtr->dest_loc != IPNL_INTERCEPT_PLAYER
256 				&& group_loc == 0))
257 		{
258 			if (GroupPtr->dest_loc == IPNL_INTERCEPT_PLAYER)
259 				dest_pt = GLOBAL (ip_location);
260 			else
261 			{
262 				COUNT orbit_dist;
263 				POINT org;
264 
265 				if (task != ON_STATION)
266 				{
267 					orbit_dist = ORBIT_RADIUS;
268 					org.x = org.y = 0;
269 				}
270 				else
271 				{
272 					orbit_dist = STATION_RADIUS;
273 					org = planetOuterLocation (target_loc - 1);
274 				}
275 
276 				angle = FACING_TO_ANGLE (GroupPtr->orbit_pos + 1);
277 				dest_pt.x = org.x + COSINE (angle, orbit_dist);
278 				dest_pt.y = org.y + SINE (angle, orbit_dist);
279 				if (GroupPtr->loc.x == dest_pt.x
280 						&& GroupPtr->loc.y == dest_pt.y)
281 				{
282 					BYTE next_loc;
283 
284 					GroupPtr->orbit_pos = NORMALIZE_FACING (
285 							ANGLE_TO_FACING (angle));
286 					angle += FACING_TO_ANGLE (1);
287 					dest_pt.x = org.x + COSINE (angle, orbit_dist);
288 					dest_pt.y = org.y + SINE (angle, orbit_dist);
289 
290 					EPtr->thrust_wait = (BYTE)~0;
291 					if (GroupPtr->group_counter)
292 						--GroupPtr->group_counter;
293 					else if (task == EXPLORE
294 							&& (next_loc = (BYTE)(((COUNT)TFB_Random ()
295 							% pSolarSysState->SunDesc[0].NumPlanets)
296 							+ 1)) != target_loc)
297 					{
298 						EPtr->thrust_wait = 0;
299 						target_loc = next_loc;
300 						GroupPtr->dest_loc = next_loc;
301 					}
302 				}
303 			}
304 		}
305 		else if (group_loc == 0)
306 		{
307 			if (GroupPtr->dest_loc == IPNL_INTERCEPT_PLAYER)
308 				dest_pt = pSolarSysState->SunDesc[0].location;
309 			else
310 				dest_pt = planetOuterLocation (target_loc - 1);
311 		}
312 		else
313 		{
314 			if (task == ON_STATION)
315 				target_loc = 0;
316 
317 			dest_pt.x = GroupPtr->loc.x << 1;
318 			dest_pt.y = GroupPtr->loc.y << 1;
319 		}
320 
321 		delta_x = dest_pt.x - GroupPtr->loc.x;
322 		delta_y = dest_pt.y - GroupPtr->loc.y;
323 		angle = ARCTAN (delta_x, delta_y);
324 
325 		if (EPtr->thrust_wait && EPtr->thrust_wait != (BYTE)~0)
326 			--EPtr->thrust_wait;
327 		else if ((vdx == 0 && vdy == 0)
328 				|| angle != GetVelocityTravelAngle (&EPtr->velocity))
329 		{
330 			SIZE speed;
331 
332 			if (EPtr->thrust_wait &&
333 					GroupPtr->dest_loc != IPNL_INTERCEPT_PLAYER)
334 			{
335 #define ORBIT_SPEED 60
336 				speed = ORBIT_SPEED;
337 				if (task == ON_STATION)
338 					speed >>= 1;
339 			}
340 			else
341 			{
342 				SIZE RaceIPSpeed[] =
343 				{
344 					RACE_IP_SPEED
345 				};
346 
347 				speed = RaceIPSpeed[GroupPtr->race_id];
348 				EPtr->thrust_wait = TRACK_WAIT;
349 			}
350 
351 			vdx = COSINE (angle, speed);
352 			vdy = SINE (angle, speed);
353 			SetVelocityComponents (&EPtr->velocity, vdx, vdy);
354 		}
355 
356 		dx = vdx;
357 		dy = vdy;
358 		if (group_loc == target_loc)
359 		{
360 			if (target_loc == 0)
361 			{
362 				if (task == FLEE)
363 					goto CheckGetAway;
364 			}
365 			else if (target_loc == GroupPtr->dest_loc)
366 			{
367 PartialRevolution:
368 				if ((long)((COUNT)(dx * dx) + (COUNT)(dy * dy))
369 						>= (long)delta_x * delta_x + (long)delta_y * delta_y)
370 				{
371 					GroupPtr->loc = dest_pt;
372 					vdx = 0;
373 					vdy = 0;
374 					ZeroVelocityComponents (&EPtr->velocity);
375 				}
376 			}
377 		}
378 		else
379 		{
380 			if (group_loc == 0)
381 			{	// In outer system
382 				adjustDeltaVforZoom (radius, &dx, &dy);
383 
384 				if (task == ON_STATION && GroupPtr->dest_loc)
385 					goto PartialRevolution;
386 				else if ((long)((COUNT)(dx * dx) + (COUNT)(dy * dy))
387 						>= (long)delta_x * delta_x + (long)delta_y * delta_y)
388 					Transition = TRUE;
389 			}
390 			else
391 			{	// In inner system; also leaving outer CheckGetAway hack
392 CheckGetAway:
393 				dest_pt = locationToDisplay (GroupPtr->loc, radius);
394 				if (dest_pt.x < 0
395 						|| dest_pt.x >= SIS_SCREEN_WIDTH
396 						|| dest_pt.y < 0
397 						|| dest_pt.y >= SIS_SCREEN_HEIGHT)
398 					Transition = TRUE;
399 			}
400 
401 			if (Transition)
402 			{
403 						/* no collisions during transition */
404 				EPtr->state_flags |= NONSOLID;
405 
406 				vdx = 0;
407 				vdy = 0;
408 				ZeroVelocityComponents (&EPtr->velocity);
409 				if (group_loc != 0)
410 				{
411 					GroupPtr->loc = planetOuterLocation (group_loc - 1);
412 					group_loc = 0;
413 					GroupPtr->sys_loc = 0;
414 				}
415 				else if (target_loc == 0)
416 				{
417 					/* Group completely left the star system */
418 					EPtr->life_span = 0;
419 					EPtr->state_flags |= DISAPPEARING | NONSOLID;
420 					GroupPtr->in_system = 0;
421 					return;
422 				}
423 				else
424 				{
425 					POINT entryPt;
426 
427 					if (target_loc == GroupPtr->dest_loc)
428 					{
429 						GroupPtr->orbit_pos = NORMALIZE_FACING (
430 								ANGLE_TO_FACING (angle + HALF_CIRCLE));
431 						GroupPtr->group_counter =
432 								((COUNT)TFB_Random () % MAX_REVOLUTIONS)
433 								<< FACING_SHIFT;
434 					}
435 					// The group enters inner system exactly on the edge of a
436 					// circle with radius = 9/16 * window-dim, which is
437 					// different from how the flagship enters, but similar
438 					// in the way that the group will never show up in any
439 					// of the corners.
440 					entryPt.x = (SIS_SCREEN_WIDTH >> 1) - COSINE (angle,
441 							SIS_SCREEN_WIDTH * 9 / 16);
442 					entryPt.y = (SIS_SCREEN_HEIGHT >> 1) - SINE (angle,
443 							SIS_SCREEN_HEIGHT * 9 / 16);
444 					GroupPtr->loc = displayToLocation (entryPt,
445 							MAX_ZOOM_RADIUS);
446 					group_loc = target_loc;
447 					GroupPtr->sys_loc = target_loc;
448 				}
449 			}
450 		}
451 	}
452 
453 	radius = zoomRadiusForLocation (group_loc);
454 	adjustDeltaVforZoom (radius, &vdx, &vdy);
455 	GroupPtr->loc.x += vdx;
456 	GroupPtr->loc.y += vdy;
457 
458 	dest_pt = locationToDisplay (GroupPtr->loc, radius);
459 	EPtr->next.location.x = DISPLAY_TO_WORLD (dest_pt.x)
460 			+ (COORD)(LOG_SPACE_WIDTH >> 1)
461 			- (LOG_SPACE_WIDTH >> (MAX_REDUCTION + 1));
462 	EPtr->next.location.y = DISPLAY_TO_WORLD (dest_pt.y)
463 			+ (COORD)(LOG_SPACE_HEIGHT >> 1)
464 			- (LOG_SPACE_HEIGHT >> (MAX_REDUCTION + 1));
465 
466 	// Don't draw the group if it's not at flagship location,
467 	// or flash the group while it's reforming
468 	if (group_loc != flagship_loc
469 			|| ((task & REFORM_GROUP)
470 			&& (GroupPtr->group_counter & 1)))
471 	{
472 		SetPrimType (&DisplayArray[EPtr->PrimIndex], NO_PRIM);
473 		EPtr->state_flags |= NONSOLID;
474 	}
475 	else
476 	{
477 		SetPrimType (&DisplayArray[EPtr->PrimIndex], STAMP_PRIM);
478 		if (task & REFORM_GROUP)
479 			 EPtr->state_flags |= NONSOLID;
480 	}
481 
482 	EPtr->state_flags |= CHANGING;
483 }
484 
485 static void
flag_ship_collision(ELEMENT * ElementPtr0,POINT * pPt0,ELEMENT * ElementPtr1,POINT * pPt1)486 flag_ship_collision (ELEMENT *ElementPtr0, POINT *pPt0,
487 		ELEMENT *ElementPtr1, POINT *pPt1)
488 {
489 	if (GLOBAL (CurrentActivity) & START_ENCOUNTER)
490 		return; // ignore the rest of the collisions
491 
492 	if (!(ElementPtr1->state_flags & COLLISION))
493 	{	// The other element's collision has not been processed yet
494 		// Defer starting the encounter until it is.
495 		ElementPtr0->state_flags |= COLLISION | NONSOLID;
496 	}
497 	else
498 	{	// Both element's collisions have now been processed
499 		ElementPtr1->state_flags &= ~COLLISION;
500 		GLOBAL (CurrentActivity) |= START_ENCOUNTER;
501 	}
502 	(void) pPt0;  /* Satisfying compiler (unused parameter) */
503 	(void) pPt1;  /* Satisfying compiler (unused parameter) */
504 }
505 
506 static void
ip_group_collision(ELEMENT * ElementPtr0,POINT * pPt0,ELEMENT * ElementPtr1,POINT * pPt1)507 ip_group_collision (ELEMENT *ElementPtr0, POINT *pPt0,
508 		ELEMENT *ElementPtr1, POINT *pPt1)
509 {
510 	IP_GROUP *GroupPtr;
511 	void *OtherPtr;
512 
513 	if (GLOBAL (CurrentActivity) & START_ENCOUNTER)
514 		return; // ignore the rest of the collisions
515 
516 	GetElementStarShip (ElementPtr0, &GroupPtr);
517 	GetElementStarShip (ElementPtr1, &OtherPtr);
518 	if (OtherPtr)
519 	{	// Collision with another group
520 		// Prevent the groups from coalescing into a single ship icon
521 		if ((ElementPtr0->state_flags & COLLISION)
522 				|| (ElementPtr1->current.location.x == ElementPtr1->next.location.x
523 				&& ElementPtr1->current.location.y == ElementPtr1->next.location.y))
524 		{
525 			ElementPtr0->state_flags &= ~COLLISION;
526 		}
527 		else
528 		{
529 			ElementPtr1->state_flags |= COLLISION;
530 
531 			GroupPtr->loc = DisplayArray[ElementPtr0->PrimIndex].Object.Point;
532 			ElementPtr0->next.location = ElementPtr0->current.location;
533 			InitIntersectEndPoint (ElementPtr0);
534 		}
535 	}
536 	else // if (!OtherPtr)
537 	{	// Collision with a flagship
538 		EncounterGroup = GroupPtr->group_id;
539 
540 		GroupPtr->task |= REFORM_GROUP;
541 		GroupPtr->group_counter = 100;
542 		// Send "all clear" for the time being. After the encounter, if
543 		// the player battles the group, the "intercept" notify will be
544 		// resent.
545 		NotifyOthers (GroupPtr->race_id, IPNL_ALL_CLEAR);
546 
547 		if (!(ElementPtr1->state_flags & COLLISION))
548 		{	// The other element's collision has not been processed yet
549 			// Defer starting the encounter until it is.
550 			ElementPtr0->state_flags |= COLLISION | NONSOLID;
551 		}
552 		else
553 		{	// Both element's collisions have now been processed
554 			ElementPtr1->state_flags &= ~COLLISION;
555 			GLOBAL (CurrentActivity) |= START_ENCOUNTER;
556 		}
557 	}
558 	(void) pPt0;  /* Satisfying compiler (unused parameter) */
559 	(void) pPt1;  /* Satisfying compiler (unused parameter) */
560 }
561 
562 static void
spawn_ip_group(IP_GROUP * GroupPtr)563 spawn_ip_group (IP_GROUP *GroupPtr)
564 {
565 	HELEMENT hIPSHIPElement;
566 
567 	hIPSHIPElement = AllocElement ();
568 	if (hIPSHIPElement)
569 	{
570 		ELEMENT *IPSHIPElementPtr;
571 
572 		LockElement (hIPSHIPElement, &IPSHIPElementPtr);
573 		// Must have mass_points for collisions to work
574 		IPSHIPElementPtr->mass_points = 1;
575 		IPSHIPElementPtr->hit_points = 1;
576 		IPSHIPElementPtr->state_flags =
577 				CHANGING | FINITE_LIFE | IGNORE_VELOCITY;
578 
579 		SetPrimType (&DisplayArray[IPSHIPElementPtr->PrimIndex], STAMP_PRIM);
580 		// XXX: Hack: farray points to FRAME[3] and given FRAME
581 		IPSHIPElementPtr->current.image.farray = &GroupPtr->melee_icon;
582 		IPSHIPElementPtr->current.image.frame = SetAbsFrameIndex (
583 					GroupPtr->melee_icon, 1);
584 			/* preprocessing has a side effect
585 			 * we wish to avoid.  So death_func
586 			 * is used instead, but will achieve
587 			 * same result without the side
588 			 * effect (InitIntersectFrame)
589 			 */
590 		IPSHIPElementPtr->death_func = ip_group_preprocess;
591 		IPSHIPElementPtr->collision_func = ip_group_collision;
592 
593 		{
594 			SIZE radius;
595 			POINT pt;
596 
597 			radius = zoomRadiusForLocation (GroupPtr->sys_loc);
598 			pt = locationToDisplay (GroupPtr->loc, radius);
599 
600 			IPSHIPElementPtr->current.location.x =
601 					DISPLAY_TO_WORLD (pt.x)
602 					+ (COORD)(LOG_SPACE_WIDTH >> 1)
603 					- (LOG_SPACE_WIDTH >> (MAX_REDUCTION + 1));
604 			IPSHIPElementPtr->current.location.y =
605 					DISPLAY_TO_WORLD (pt.y)
606 					+ (COORD)(LOG_SPACE_HEIGHT >> 1)
607 					- (LOG_SPACE_HEIGHT >> (MAX_REDUCTION + 1));
608 		}
609 
610 		SetElementStarShip (IPSHIPElementPtr, GroupPtr);
611 
612 		SetUpElement (IPSHIPElementPtr);
613 		IPSHIPElementPtr->IntersectControl.IntersectStamp.frame =
614 				DecFrameIndex (stars_in_space);
615 
616 		UnlockElement (hIPSHIPElement);
617 
618 		PutElement (hIPSHIPElement);
619 	}
620 }
621 
622 #define FLIP_WAIT 42
623 
624 static void
flag_ship_preprocess(ELEMENT * ElementPtr)625 flag_ship_preprocess (ELEMENT *ElementPtr)
626 {
627 	if (--ElementPtr->thrust_wait == 0)
628 		/* juggle list after flagship */
629 	{
630 		HELEMENT hSuccElement;
631 
632 		if ((hSuccElement = GetSuccElement (ElementPtr))
633 				&& hSuccElement != GetTailElement ())
634 		{
635 			HELEMENT hPredElement;
636 			ELEMENT *TailPtr;
637 
638 			LockElement (GetTailElement (), &TailPtr);
639 			hPredElement = _GetPredLink (TailPtr);
640 			UnlockElement (GetTailElement ());
641 
642 			RemoveElement (hSuccElement);
643 			PutElement (hSuccElement);
644 		}
645 
646 		ElementPtr->thrust_wait = FLIP_WAIT;
647 	}
648 
649 	{
650 		BYTE flagship_loc, ec;
651 		SIZE vdx, vdy, radius;
652 		POINT pt;
653 
654 		GetCurrentVelocityComponents (&GLOBAL (velocity), &vdx, &vdy);
655 
656 		flagship_loc = getFlagshipLocation ();
657 		radius = zoomRadiusForLocation (flagship_loc);
658 		adjustDeltaVforZoom (radius, &vdx, &vdy);
659 
660 		pt = locationToDisplay (GLOBAL (ip_location), radius);
661 		ElementPtr->current.location.x = DISPLAY_TO_WORLD (pt.x)
662 				+ (COORD)(LOG_SPACE_WIDTH >> 1)
663 				- (LOG_SPACE_WIDTH >> (MAX_REDUCTION + 1));
664 		ElementPtr->current.location.y = DISPLAY_TO_WORLD (pt.y)
665 				+ (COORD)(LOG_SPACE_HEIGHT >> 1)
666 				- (LOG_SPACE_HEIGHT >> (MAX_REDUCTION + 1));
667 		InitIntersectStartPoint (ElementPtr);
668 
669 		GLOBAL (ip_location.x) += vdx;
670 		GLOBAL (ip_location.y) += vdy;
671 
672 		pt = locationToDisplay (GLOBAL (ip_location), radius);
673 		ElementPtr->next.location.x = DISPLAY_TO_WORLD (pt.x)
674 				+ (COORD)(LOG_SPACE_WIDTH >> 1)
675 				- (LOG_SPACE_WIDTH >> (MAX_REDUCTION + 1));
676 		ElementPtr->next.location.y = DISPLAY_TO_WORLD (pt.y)
677 				+ (COORD)(LOG_SPACE_HEIGHT >> 1)
678 				- (LOG_SPACE_HEIGHT >> (MAX_REDUCTION + 1));
679 
680 		GLOBAL (ShipStamp.origin) = pt;
681 		ElementPtr->next.image.frame = GLOBAL (ShipStamp.frame);
682 
683 		if (ElementPtr->sys_loc == flagship_loc)
684 		{
685 			if (ElementPtr->state_flags & NONSOLID)
686 				ElementPtr->state_flags &= ~NONSOLID;
687 		}
688 		else /* no collisions during transition */
689 		{
690 			ElementPtr->state_flags |= NONSOLID;
691 			ElementPtr->sys_loc = flagship_loc;
692 		}
693 
694 		if ((ec = GET_GAME_STATE (ESCAPE_COUNTER))
695 				&& !(GLOBAL (CurrentActivity) & START_ENCOUNTER))
696 		{
697 			ElementPtr->state_flags |= NONSOLID;
698 
699 			--ec;
700 			SET_GAME_STATE (ESCAPE_COUNTER, ec);
701 		}
702 
703 		ElementPtr->state_flags |= CHANGING;
704 	}
705 }
706 
707 static void
spawn_flag_ship(void)708 spawn_flag_ship (void)
709 {
710 	HELEMENT hFlagShipElement;
711 
712 	hFlagShipElement = AllocElement ();
713 	if (hFlagShipElement)
714 	{
715 		ELEMENT *FlagShipElementPtr;
716 
717 		LockElement (hFlagShipElement, &FlagShipElementPtr);
718 		FlagShipElementPtr->hit_points = 1;
719 		// Must have mass_points for collisions to work
720 		FlagShipElementPtr->mass_points = 1;
721 		FlagShipElementPtr->sys_loc = getFlagshipLocation ();
722 		FlagShipElementPtr->state_flags = APPEARING | IGNORE_VELOCITY;
723 		if (GET_GAME_STATE (ESCAPE_COUNTER))
724 			FlagShipElementPtr->state_flags |= NONSOLID;
725 		FlagShipElementPtr->life_span = NORMAL_LIFE;
726 		FlagShipElementPtr->thrust_wait = FLIP_WAIT;
727 		SetPrimType (&DisplayArray[FlagShipElementPtr->PrimIndex], STAMP_PRIM);
728 		FlagShipElementPtr->current.image.farray =
729 				&GLOBAL (ShipStamp.frame);
730 		FlagShipElementPtr->current.image.frame =
731 				GLOBAL (ShipStamp.frame);
732 		FlagShipElementPtr->preprocess_func = flag_ship_preprocess;
733 		FlagShipElementPtr->collision_func = flag_ship_collision;
734 
735 		FlagShipElementPtr->current.location.x =
736 				DISPLAY_TO_WORLD (GLOBAL (ShipStamp.origin.x))
737 				+ (COORD)(LOG_SPACE_WIDTH >> 1)
738 				- (LOG_SPACE_WIDTH >> (MAX_REDUCTION + 1));
739 		FlagShipElementPtr->current.location.y =
740 				DISPLAY_TO_WORLD (GLOBAL (ShipStamp.origin.y))
741 				+ (COORD)(LOG_SPACE_HEIGHT >> 1)
742 				- (LOG_SPACE_HEIGHT >> (MAX_REDUCTION + 1));
743 
744 		UnlockElement (hFlagShipElement);
745 
746 		PutElement (hFlagShipElement);
747 	}
748 }
749 
750 void
DoMissions(void)751 DoMissions (void)
752 {
753 	HSHIPFRAG hGroup, hNextGroup;
754 
755 	spawn_flag_ship ();
756 
757 	if (EncounterRace >= 0)
758 	{	// There was a battle. Call in reinforcements.
759 		NotifyOthers (EncounterRace, IPNL_INTERCEPT_PLAYER);
760 		EncounterRace = -1;
761 	}
762 
763 	for (hGroup = GetHeadLink (&GLOBAL (ip_group_q));
764 			hGroup; hGroup = hNextGroup)
765 	{
766 		IP_GROUP *GroupPtr;
767 
768 		GroupPtr = LockIpGroup (&GLOBAL (ip_group_q), hGroup);
769 		hNextGroup = _GetSuccLink (GroupPtr);
770 
771 		if (GroupPtr->in_system)
772 			spawn_ip_group (GroupPtr);
773 
774 		UnlockIpGroup (&GLOBAL (ip_group_q), hGroup);
775 	}
776 }
777 
778