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 "sis.h"
20 
21 #include "colors.h"
22 #include "races.h"
23 #include "starmap.h"
24 #include "units.h"
25 #include "menustat.h"
26 		// for DrawMenuStateStrings()
27 #include "gamestr.h"
28 #include "options.h"
29 #include "battle.h"
30 		// For BATTLE_FRAME_RATE
31 #include "element.h"
32 #include "setup.h"
33 #include "state.h"
34 #include "flash.h"
35 #include "libs/graphics/gfx_common.h"
36 #include "libs/tasklib.h"
37 #include "libs/alarm.h"
38 #include "libs/log.h"
39 
40 #include <stdio.h>
41 
42 static StatMsgMode curMsgMode = SMM_DEFAULT;
43 
44 static const UNICODE *describeWeapon (BYTE moduleType);
45 
46 void
RepairSISBorder(void)47 RepairSISBorder (void)
48 {
49 	RECT r;
50 	CONTEXT OldContext;
51 
52 	OldContext = SetContext (ScreenContext);
53 
54 	BatchGraphics ();
55 
56 	// Left border
57 	r.corner.x = SIS_ORG_X - 1;
58 	r.corner.y = SIS_ORG_Y - 1;
59 	r.extent.width = 1;
60 	r.extent.height = SIS_SCREEN_HEIGHT + 2;
61 	SetContextForeGroundColor (SIS_LEFT_BORDER_COLOR);
62 	DrawFilledRectangle (&r);
63 
64 	// Right border
65 	SetContextForeGroundColor (SIS_BOTTOM_RIGHT_BORDER_COLOR);
66 	r.corner.x += (SIS_SCREEN_WIDTH + 2) - 1;
67 	DrawFilledRectangle (&r);
68 
69 	// Bottom border
70 	r.corner.x = SIS_ORG_X - 1;
71 	r.corner.y += (SIS_SCREEN_HEIGHT + 2) - 1;
72 	r.extent.width = SIS_SCREEN_WIDTH + 2;
73 	r.extent.height = 1;
74 	DrawFilledRectangle (&r);
75 
76 	UnbatchGraphics ();
77 
78 	SetContext (OldContext);
79 }
80 
81 void
ClearSISRect(BYTE ClearFlags)82 ClearSISRect (BYTE ClearFlags)
83 {
84 	RECT r;
85 	Color OldColor;
86 	CONTEXT OldContext;
87 
88 	OldContext = SetContext (StatusContext);
89 	OldColor = SetContextForeGroundColor (
90 			BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x0A), 0x08));
91 
92 	r.corner.x = 2;
93 	r.extent.width = STATUS_WIDTH - 4;
94 
95 	BatchGraphics ();
96 	if (ClearFlags & DRAW_SIS_DISPLAY)
97 	{
98 		DeltaSISGauges (UNDEFINED_DELTA, UNDEFINED_DELTA, UNDEFINED_DELTA);
99 	}
100 
101 	if (ClearFlags & CLEAR_SIS_RADAR)
102 	{
103 		DrawMenuStateStrings ((BYTE)~0, 1);
104 #ifdef NEVER
105 		r.corner.x = RADAR_X - 1;
106 		r.corner.y = RADAR_Y - 1;
107 		r.extent.width = RADAR_WIDTH + 2;
108 		r.extent.height = RADAR_HEIGHT + 2;
109 
110 		DrawStarConBox (&r, 1,
111 				BUILD_COLOR (MAKE_RGB15 (0x10, 0x10, 0x10), 0x19),
112 				BUILD_COLOR (MAKE_RGB15 (0x08, 0x08, 0x08), 0x1F),
113 				TRUE, BUILD_COLOR (MAKE_RGB15 (0x00, 0x0E, 0x00), 0x6C));
114 #endif /* NEVER */
115 	}
116 	UnbatchGraphics ();
117 
118 	SetContextForeGroundColor (OldColor);
119 	SetContext (OldContext);
120 }
121 
122 // Draw the SIS title. This is the field at the top of the screen, on the
123 // right hand side, containing the coordinates in HyperSpace, or the planet
124 // name in IP.
125 void
DrawSISTitle(UNICODE * pStr)126 DrawSISTitle (UNICODE *pStr)
127 {
128 	TEXT t;
129 	CONTEXT OldContext;
130 	RECT r;
131 
132 	t.baseline.x = SIS_TITLE_WIDTH >> 1;
133 	t.baseline.y = SIS_TITLE_HEIGHT - 2;
134 	t.align = ALIGN_CENTER;
135 	t.pStr = pStr;
136 	t.CharCount = (COUNT)~0;
137 
138 	OldContext = SetContext (OffScreenContext);
139 	r.corner.x = SIS_ORG_X + SIS_SCREEN_WIDTH - SIS_TITLE_BOX_WIDTH + 1;
140 	r.corner.y = SIS_ORG_Y - SIS_TITLE_HEIGHT;
141 	r.extent.width = SIS_TITLE_WIDTH;
142 	r.extent.height = SIS_TITLE_HEIGHT - 1;
143 	SetContextFGFrame (Screen);
144 	SetContextClipRect (&r);
145 	SetContextFont (TinyFont);
146 
147 	BatchGraphics ();
148 
149 	// Background color
150 	SetContextBackGroundColor (SIS_TITLE_BACKGROUND_COLOR);
151 	ClearDrawable ();
152 
153 	// Text color
154 	SetContextForeGroundColor (SIS_TITLE_TEXT_COLOR);
155 	font_DrawText (&t);
156 
157 	UnbatchGraphics ();
158 
159 	SetContextClipRect (NULL);
160 
161 	SetContext (OldContext);
162 }
163 
164 void
DrawHyperCoords(POINT universe)165 DrawHyperCoords (POINT universe)
166 {
167 	UNICODE buf[100];
168 
169 	snprintf (buf, sizeof buf, "%03u.%01u : %03u.%01u",
170 			universe.x / 10, universe.x % 10,
171 			universe.y / 10, universe.y % 10);
172 
173 	DrawSISTitle (buf);
174 }
175 
176 void
DrawSISMessage(const UNICODE * pStr)177 DrawSISMessage (const UNICODE *pStr)
178 {
179 	DrawSISMessageEx (pStr, -1, -1, DSME_NONE);
180 }
181 
182 // See sis.h for the allowed flags.
183 BOOLEAN
DrawSISMessageEx(const UNICODE * pStr,SIZE CurPos,SIZE ExPos,COUNT flags)184 DrawSISMessageEx (const UNICODE *pStr, SIZE CurPos, SIZE ExPos, COUNT flags)
185 {
186 	UNICODE buf[256];
187 	CONTEXT OldContext;
188 	TEXT t;
189 	RECT r;
190 
191 	OldContext = SetContext (OffScreenContext);
192 	// prepare the context
193 	r.corner.x = SIS_ORG_X + 1;
194 	r.corner.y = SIS_ORG_Y - SIS_MESSAGE_HEIGHT;
195 	r.extent.width = SIS_MESSAGE_WIDTH;
196 	r.extent.height = SIS_MESSAGE_HEIGHT - 1;
197 	SetContextFGFrame (Screen);
198 	SetContextClipRect (&r);
199 
200 	BatchGraphics ();
201 	SetContextBackGroundColor (SIS_MESSAGE_BACKGROUND_COLOR);
202 
203 	if (pStr == 0)
204 	{
205 		switch (LOBYTE (GLOBAL (CurrentActivity)))
206 		{
207 			default:
208 			case IN_ENCOUNTER:
209 				pStr = "";
210 				break;
211 			case IN_LAST_BATTLE:
212 			case IN_INTERPLANETARY:
213 				GetClusterName (CurStarDescPtr, buf);
214 				pStr = buf;
215 				break;
216 			case IN_HYPERSPACE:
217 				if (inHyperSpace ())
218 				{
219 					pStr = GAME_STRING (NAVIGATION_STRING_BASE);
220 							// "HyperSpace"
221 				}
222 				else
223 				{
224 					pStr = GAME_STRING (NAVIGATION_STRING_BASE + 1);
225 							// "QuasiSpace"
226 				}
227 				break;
228 		}
229 
230 	}
231 
232 	if (!(flags & DSME_MYCOLOR))
233 		SetContextForeGroundColor (SIS_MESSAGE_TEXT_COLOR);
234 
235 	t.baseline.y = SIS_MESSAGE_HEIGHT - 2;
236 	t.pStr = pStr;
237 	t.CharCount = (COUNT)~0;
238 	SetContextFont (TinyFont);
239 
240 	if (flags & DSME_CLEARFR)
241 		SetFlashRect (NULL);
242 
243 	if (CurPos < 0 && ExPos < 0)
244 	{	// normal state
245 		ClearDrawable ();
246 		t.baseline.x = SIS_MESSAGE_WIDTH >> 1;
247 		t.align = ALIGN_CENTER;
248 		font_DrawText (&t);
249 	}
250 	else
251 	{	// editing state
252 		int i;
253 		RECT text_r;
254 		// XXX: 128 is currently safe, but it would be better to specify
255 		//   the size to TextRect()
256 		BYTE char_deltas[128];
257 		BYTE *pchar_deltas;
258 
259 		t.baseline.x = 3;
260 		t.align = ALIGN_LEFT;
261 
262 		TextRect (&t, &text_r, char_deltas);
263 		if (text_r.extent.width + t.baseline.x + 2 >= r.extent.width)
264 		{	// the text does not fit the input box size and so
265 			// will not fit when displayed later
266 			// disallow the change
267 			UnbatchGraphics ();
268 			SetContextClipRect (NULL);
269 			SetContext (OldContext);
270 			return (FALSE);
271 		}
272 
273 		ClearDrawable ();
274 
275 		if (CurPos >= 0 && CurPos <= t.CharCount)
276 		{	// calc and draw the cursor
277 			RECT cur_r = text_r;
278 
279 			for (i = CurPos, pchar_deltas = char_deltas; i > 0; --i)
280 				cur_r.corner.x += (SIZE)*pchar_deltas++;
281 			if (CurPos < t.CharCount) /* end of line */
282 				--cur_r.corner.x;
283 
284 			if (flags & DSME_BLOCKCUR)
285 			{	// Use block cursor for keyboardless systems
286 				if (CurPos == t.CharCount)
287 				{	// cursor at end-line -- use insertion point
288 					cur_r.extent.width = 1;
289 				}
290 				else if (CurPos + 1 == t.CharCount)
291 				{	// extra pixel for last char margin
292 					cur_r.extent.width = (SIZE)*pchar_deltas + 2;
293 				}
294 				else
295 				{	// normal mid-line char
296 					cur_r.extent.width = (SIZE)*pchar_deltas + 1;
297 				}
298 			}
299 			else
300 			{	// Insertion point cursor
301 				cur_r.extent.width = 1;
302 			}
303 
304 			cur_r.corner.y = 0;
305 			cur_r.extent.height = r.extent.height;
306 			SetContextForeGroundColor (SIS_MESSAGE_CURSOR_COLOR);
307 			DrawFilledRectangle (&cur_r);
308 		}
309 
310 		SetContextForeGroundColor (SIS_MESSAGE_TEXT_COLOR);
311 
312 		if (ExPos >= 0 && ExPos < t.CharCount)
313 		{	// handle extra characters
314 			t.CharCount = ExPos;
315 			font_DrawText (&t);
316 
317 			// print extra chars
318 			SetContextForeGroundColor (SIS_MESSAGE_EXTRA_TEXT_COLOR);
319 			for (i = ExPos, pchar_deltas = char_deltas; i > 0; --i)
320 				t.baseline.x += (SIZE)*pchar_deltas++;
321 			t.pStr = skipUTF8Chars (t.pStr, ExPos);
322 			t.CharCount = (COUNT)~0;
323 			font_DrawText (&t);
324 		}
325 		else
326 		{	// just print the text
327 			font_DrawText (&t);
328 		}
329 	}
330 
331 	if (flags & DSME_SETFR)
332 	{
333 		r.corner.x = 0;
334 		r.corner.y = 0;
335 		SetFlashRect (&r);
336 	}
337 
338 	UnbatchGraphics ();
339 
340 	SetContextClipRect (NULL);
341 	SetContext (OldContext);
342 
343 	return (TRUE);
344 }
345 
346 void
DateToString(char * buf,size_t bufLen,BYTE month_index,BYTE day_index,COUNT year_index)347 DateToString (char *buf, size_t bufLen,
348 		BYTE month_index, BYTE day_index, COUNT year_index)
349 {
350 	snprintf (buf, bufLen, "%s %02d" STR_MIDDLE_DOT "%04d",
351 			GAME_STRING (MONTHS_STRING_BASE + month_index - 1),
352 			day_index, year_index);
353 }
354 
355 void
GetStatusMessageRect(RECT * r)356 GetStatusMessageRect (RECT *r)
357 {
358 	r->corner.x = 2;
359 	r->corner.y = 130;
360 	r->extent.width = STATUS_MESSAGE_WIDTH;
361 	r->extent.height = STATUS_MESSAGE_HEIGHT;
362 }
363 
364 void
DrawStatusMessage(const UNICODE * pStr)365 DrawStatusMessage (const UNICODE *pStr)
366 {
367 	RECT r;
368 	RECT ctxRect;
369 	TEXT t;
370 	UNICODE buf[128];
371 	CONTEXT OldContext;
372 
373 	OldContext = SetContext (StatusContext);
374 	GetContextClipRect (&ctxRect);
375 	// XXX: Technically, this does not need OffScreenContext. The only reason
376 	//   it is used is to avoid preserving StatusContext settings.
377 	SetContext (OffScreenContext);
378 	SetContextFGFrame (Screen);
379 	GetStatusMessageRect (&r);
380 	r.corner.x += ctxRect.corner.x;
381 	r.corner.y += ctxRect.corner.y;
382 	SetContextClipRect (&r);
383 
384 	BatchGraphics ();
385 	SetContextBackGroundColor (STATUS_MESSAGE_BACKGROUND_COLOR);
386 	ClearDrawable ();
387 
388 	if (!pStr)
389 	{
390 		if (curMsgMode == SMM_CREDITS)
391 		{
392 			snprintf (buf, sizeof buf, "%u %s", MAKE_WORD (
393 					GET_GAME_STATE (MELNORME_CREDIT0),
394 					GET_GAME_STATE (MELNORME_CREDIT1)
395 					), GAME_STRING (STATUS_STRING_BASE + 0)); // "Cr"
396 		}
397 		else if (curMsgMode == SMM_RES_UNITS)
398 		{
399 			if (GET_GAME_STATE (CHMMR_BOMB_STATE) < 2)
400 			{
401 				snprintf (buf, sizeof buf, "%u %s", GLOBAL_SIS (ResUnits),
402 						GAME_STRING (STATUS_STRING_BASE + 1)); // "RU"
403 			}
404 			else
405 			{
406 				snprintf (buf, sizeof buf, "%s %s",
407 						(optWhichMenu == OPT_PC) ?
408 							GAME_STRING (STATUS_STRING_BASE + 2)
409 							: STR_INFINITY_SIGN, // "UNLIMITED"
410 						GAME_STRING (STATUS_STRING_BASE + 1)); // "RU"
411 			}
412 		}
413 		else
414 		{	// Just a date
415 			DateToString (buf, sizeof buf,
416 					GLOBAL (GameClock.month_index),
417 					GLOBAL (GameClock.day_index),
418 					GLOBAL (GameClock.year_index));
419 		}
420 		pStr = buf;
421 	}
422 
423 	t.baseline.x = STATUS_MESSAGE_WIDTH >> 1;
424 	t.baseline.y = STATUS_MESSAGE_HEIGHT - 1;
425 	t.align = ALIGN_CENTER;
426 	t.pStr = pStr;
427 	t.CharCount = (COUNT)~0;
428 
429 	SetContextFont (TinyFont);
430 	SetContextForeGroundColor (STATUS_MESSAGE_TEXT_COLOR);
431 	font_DrawText (&t);
432 	UnbatchGraphics ();
433 
434 	SetContextClipRect (NULL);
435 
436 	SetContext (OldContext);
437 }
438 
439 StatMsgMode
SetStatusMessageMode(StatMsgMode newMode)440 SetStatusMessageMode (StatMsgMode newMode)
441 {
442 	StatMsgMode oldMode = curMsgMode;
443 	curMsgMode = newMode;
444 	return oldMode;
445 }
446 
447 void
DrawCaptainsName(void)448 DrawCaptainsName (void)
449 {
450 	RECT r;
451 	TEXT t;
452 	CONTEXT OldContext;
453 	FONT OldFont;
454 	Color OldColor;
455 
456 	OldContext = SetContext (StatusContext);
457 	OldFont = SetContextFont (TinyFont);
458 	OldColor = SetContextForeGroundColor (CAPTAIN_NAME_BACKGROUND_COLOR);
459 
460 	r.corner.x = 2 + 1;
461 	r.corner.y = 10;
462 	r.extent.width = SHIP_NAME_WIDTH - 2;
463 	r.extent.height = SHIP_NAME_HEIGHT;
464 	DrawFilledRectangle (&r);
465 
466 	t.baseline.x = (STATUS_WIDTH >> 1) - 1;
467 	t.baseline.y = r.corner.y + 6;
468 	t.align = ALIGN_CENTER;
469 	t.pStr = GLOBAL_SIS (CommanderName);
470 	t.CharCount = (COUNT)~0;
471 	SetContextForeGroundColor (CAPTAIN_NAME_TEXT_COLOR);
472 	font_DrawText (&t);
473 
474 	SetContextForeGroundColor (OldColor);
475 	SetContextFont (OldFont);
476 	SetContext (OldContext);
477 }
478 
479 void
DrawFlagshipName(BOOLEAN InStatusArea)480 DrawFlagshipName (BOOLEAN InStatusArea)
481 {
482 	RECT r;
483 	TEXT t;
484 	FONT OldFont;
485 	Color OldColor;
486 	CONTEXT OldContext;
487 	FRAME OldFontEffect;
488 	UNICODE buf[250];
489 
490 	if (InStatusArea)
491 	{
492 		OldContext = SetContext (StatusContext);
493 		OldFont = SetContextFont (StarConFont);
494 
495 		r.corner.x = 2;
496 		r.corner.y = 20;
497 		r.extent.width = SHIP_NAME_WIDTH;
498 		r.extent.height = SHIP_NAME_HEIGHT;
499 
500 		t.pStr = GLOBAL_SIS (ShipName);
501 	}
502 	else
503 	{
504 		OldContext = SetContext (SpaceContext);
505 		OldFont = SetContextFont (MicroFont);
506 
507 		r.corner.x = 0;
508 		r.corner.y = 1;
509 		r.extent.width = SIS_SCREEN_WIDTH;
510 		r.extent.height = SHIP_NAME_HEIGHT;
511 
512 		t.pStr = buf;
513 		snprintf (buf, sizeof buf, "%s %s",
514 				GAME_STRING (NAMING_STRING_BASE + 1), GLOBAL_SIS (ShipName));
515 		// XXX: this will not work with UTF-8 strings
516 		strupr (buf);
517 	}
518 	OldFontEffect = SetContextFontEffect (NULL);
519 	OldColor = SetContextForeGroundColor (FLAGSHIP_NAME_BACKGROUND_COLOR);
520 	DrawFilledRectangle (&r);
521 
522 	t.baseline.x = r.corner.x + (r.extent.width >> 1);
523 	t.baseline.y = r.corner.y + (SHIP_NAME_HEIGHT - InStatusArea);
524 	t.align = ALIGN_CENTER;
525 	t.CharCount = (COUNT)~0;
526 	if (optWhichFonts == OPT_PC)
527 		SetContextFontEffect (SetAbsFrameIndex (FontGradFrame,
528 				InStatusArea ? 0 : 3));
529 	else
530 		SetContextForeGroundColor (THREEDO_FLAGSHIP_NAME_TEXT_COLOR);
531 
532 	font_DrawText (&t);
533 
534 	SetContextFontEffect (OldFontEffect);
535 	SetContextForeGroundColor (OldColor);
536 	SetContextFont (OldFont);
537 	SetContext (OldContext);
538 }
539 
540 void
DrawFlagshipStats(void)541 DrawFlagshipStats (void)
542 {
543 	RECT r;
544 	TEXT t;
545 	FONT OldFont;
546 	Color OldColor;
547 	FRAME OldFontEffect;
548 	CONTEXT OldContext;
549 	UNICODE buf[128];
550 	SIZE leading;
551 	BYTE i;
552 	BYTE energy_regeneration, energy_wait, turn_wait;
553 	COUNT max_thrust;
554 	DWORD fuel;
555 
556 	/* collect stats */
557 #define ENERGY_REGENERATION 1
558 #define ENERGY_WAIT 10
559 #define MAX_THRUST 10
560 #define TURN_WAIT 17
561 	energy_regeneration = ENERGY_REGENERATION;
562 	energy_wait = ENERGY_WAIT;
563 	max_thrust = MAX_THRUST;
564 	turn_wait = TURN_WAIT;
565 	fuel = 10 * FUEL_TANK_SCALE;
566 
567 	for (i = 0; i < NUM_MODULE_SLOTS; i++)
568 	{
569 		switch (GLOBAL_SIS (ModuleSlots[i])) {
570 			case FUEL_TANK:
571 				fuel += FUEL_TANK_CAPACITY;
572 				break;
573 			case HIGHEFF_FUELSYS:
574 				fuel += HEFUEL_TANK_CAPACITY;
575 				break;
576 			case DYNAMO_UNIT:
577 				energy_wait -= 2;
578 				if (energy_wait < 4)
579 					energy_wait = 4;
580 				break;
581 			case SHIVA_FURNACE:
582 				energy_regeneration++;
583 				break;
584 		}
585 	}
586 
587 	for (i = 0; i < NUM_DRIVE_SLOTS; ++i)
588 		if (GLOBAL_SIS (DriveSlots[i]) == FUSION_THRUSTER)
589 			max_thrust += 2;
590 
591 	for (i = 0; i < NUM_JET_SLOTS; ++i)
592 		if (GLOBAL_SIS (JetSlots[i]) == TURNING_JETS)
593 			turn_wait -= 2;
594 	/* END collect stats */
595 
596 	OldContext = SetContext (SpaceContext);
597 	OldFont = SetContextFont (StarConFont);
598 	OldFontEffect = SetContextFontEffect (NULL);
599 	GetContextFontLeading (&leading);
600 
601 	/* we need room to play.  full screen width, 4 lines tall */
602 	r.corner.x = 0;
603 	r.corner.y = SIS_SCREEN_HEIGHT - (4 * leading);
604 	r.extent.width = SIS_SCREEN_WIDTH;
605 	r.extent.height = (4 * leading);
606 
607 	OldColor = SetContextForeGroundColor (BLACK_COLOR);
608 	DrawFilledRectangle (&r);
609 
610 	/*
611 	   now that we've cleared out our playground, compensate for the
612 	   fact that the leading is way more than is generally needed.
613 	*/
614 	leading -= 3;
615 	t.baseline.x = SIS_SCREEN_WIDTH / 6; //wild-assed guess, but it worked
616 	t.baseline.y = r.corner.y + leading + 3;
617 	t.align = ALIGN_RIGHT;
618 	t.CharCount = (COUNT)~0;
619 
620 	SetContextFontEffect (SetAbsFrameIndex (FontGradFrame, 4));
621 
622 	t.pStr = GAME_STRING (FLAGSHIP_STRING_BASE + 0); // "nose:"
623 	font_DrawText (&t);
624 	t.baseline.y += leading;
625 	t.pStr = GAME_STRING (FLAGSHIP_STRING_BASE + 1); // "spread:"
626 	font_DrawText (&t);
627 	t.baseline.y += leading;
628 	t.pStr = GAME_STRING (FLAGSHIP_STRING_BASE + 2); // "side:"
629 	font_DrawText (&t);
630 	t.baseline.y += leading;
631 	t.pStr = GAME_STRING (FLAGSHIP_STRING_BASE + 3); // "tail:"
632 	font_DrawText (&t);
633 
634 	t.baseline.x += 5;
635 	t.baseline.y = r.corner.y + leading + 3;
636 	t.align = ALIGN_LEFT;
637 	t.pStr = buf;
638 
639 	snprintf (buf, sizeof buf, "%-7.7s",
640 			describeWeapon (GLOBAL_SIS (ModuleSlots[15])));
641 	font_DrawText (&t);
642 	t.baseline.y += leading;
643 	snprintf (buf, sizeof buf,
644 			"%-7.7s", describeWeapon (GLOBAL_SIS (ModuleSlots[14])));
645 	font_DrawText (&t);
646 	t.baseline.y += leading;
647 	snprintf (buf, sizeof buf,
648 			"%-7.7s", describeWeapon (GLOBAL_SIS (ModuleSlots[13])));
649 	font_DrawText (&t);
650 	t.baseline.y += leading;
651 	snprintf (buf, sizeof buf,
652 			"%-7.7s", describeWeapon (GLOBAL_SIS (ModuleSlots[0])));
653 	font_DrawText (&t);
654 
655 	t.baseline.x = r.extent.width - 25;
656 	t.baseline.y = r.corner.y + leading + 3;
657 	t.align = ALIGN_RIGHT;
658 
659 	SetContextFontEffect (SetAbsFrameIndex (FontGradFrame, 5));
660 
661 	t.pStr = GAME_STRING (FLAGSHIP_STRING_BASE + 4); // "maximum velocity:"
662 	font_DrawText (&t);
663 	t.baseline.y += leading;
664 	t.pStr = GAME_STRING (FLAGSHIP_STRING_BASE + 5); // "turning rate:"
665 	font_DrawText (&t);
666 	t.baseline.y += leading;
667 	t.pStr = GAME_STRING (FLAGSHIP_STRING_BASE + 6); // "combat energy:"
668 	font_DrawText (&t);
669 	t.baseline.y += leading;
670 	t.pStr = GAME_STRING (FLAGSHIP_STRING_BASE + 7); // "maximum fuel:"
671 	font_DrawText (&t);
672 
673 	t.baseline.x = r.extent.width - 2;
674 	t.baseline.y = r.corner.y + leading + 3;
675 	t.pStr = buf;
676 
677 	snprintf (buf, sizeof buf, "%4u", max_thrust * 4);
678 	font_DrawText (&t);
679 	t.baseline.y += leading;
680 	snprintf (buf, sizeof buf, "%4u", 1 + TURN_WAIT - turn_wait);
681 	font_DrawText (&t);
682 	t.baseline.y += leading;
683 	{
684 		unsigned int energy_per_10_sec =
685 				(((100 * ONE_SECOND * energy_regeneration) /
686 				((1 + energy_wait) * BATTLE_FRAME_RATE)) + 5) / 10;
687 		snprintf (buf, sizeof buf, "%2u.%1u",
688 				energy_per_10_sec / 10, energy_per_10_sec % 10);
689 	}
690 	font_DrawText (&t);
691 	t.baseline.y += leading;
692 	snprintf (buf, sizeof buf, "%4u", (fuel / FUEL_TANK_SCALE));
693 	font_DrawText (&t);
694 
695 	SetContextFontEffect (OldFontEffect);
696 	SetContextForeGroundColor (OldColor);
697 	SetContextFont (OldFont);
698 	SetContext (OldContext);
699 }
700 
701 static const UNICODE *
describeWeapon(BYTE moduleType)702 describeWeapon (BYTE moduleType)
703 {
704 	switch (moduleType)
705 	{
706 		case GUN_WEAPON:
707 			return GAME_STRING (FLAGSHIP_STRING_BASE + 8); // "gun"
708 		case BLASTER_WEAPON:
709 			return GAME_STRING (FLAGSHIP_STRING_BASE + 9); // "blaster"
710 		case CANNON_WEAPON:
711 			return GAME_STRING (FLAGSHIP_STRING_BASE + 10); // "cannon"
712 		case BOMB_MODULE_0:
713 		case BOMB_MODULE_1:
714 		case BOMB_MODULE_2:
715 		case BOMB_MODULE_3:
716 		case BOMB_MODULE_4:
717 		case BOMB_MODULE_5:
718 			return GAME_STRING (FLAGSHIP_STRING_BASE + 11); // "n/a"
719 		default:
720 			return GAME_STRING (FLAGSHIP_STRING_BASE + 12); // "none"
721 	}
722 }
723 
724 void
DrawLanders(void)725 DrawLanders (void)
726 {
727 	BYTE i;
728 	SIZE width;
729 	RECT r;
730 	STAMP s;
731 	CONTEXT OldContext;
732 
733 	OldContext = SetContext (StatusContext);
734 
735 	s.frame = IncFrameIndex (FlagStatFrame);
736 	GetFrameRect (s.frame, &r);
737 
738 	i = GLOBAL_SIS (NumLanders);
739 	r.corner.x = (STATUS_WIDTH >> 1) - r.corner.x;
740 	s.origin.x = r.corner.x - (((r.extent.width * i) + (2 * (i - 1))) >> 1);
741 	s.origin.y = 29;
742 
743 	width = r.extent.width + 2;
744 	r.extent.width = (r.extent.width * MAX_LANDERS)
745 			+ (2 * (MAX_LANDERS - 1)) + 2;
746 	r.corner.x -= r.extent.width >> 1;
747 	r.corner.y += s.origin.y;
748 	SetContextForeGroundColor (BLACK_COLOR);
749 	DrawFilledRectangle (&r);
750 	while (i--)
751 	{
752 		DrawStamp (&s);
753 		s.origin.x += width;
754 	}
755 
756 	SetContext (OldContext);
757 }
758 
759 // Draw the storage bays, below the picture of the flagship.
760 void
DrawStorageBays(BOOLEAN Refresh)761 DrawStorageBays (BOOLEAN Refresh)
762 {
763 	BYTE i;
764 	RECT r;
765 	CONTEXT OldContext;
766 
767 	OldContext = SetContext (StatusContext);
768 
769 	r.extent.width = 2;
770 	r.extent.height = 4;
771 	r.corner.y = 123;
772 	if (Refresh)
773 	{
774 		r.extent.width = NUM_MODULE_SLOTS * (r.extent.width + 1);
775 		r.corner.x = (STATUS_WIDTH >> 1) - (r.extent.width >> 1);
776 
777 		SetContextForeGroundColor (BLACK_COLOR);
778 		DrawFilledRectangle (&r);
779 		r.extent.width = 2;
780 	}
781 
782 	i = (BYTE)CountSISPieces (STORAGE_BAY);
783 	if (i)
784 	{
785 		COUNT j;
786 
787 		r.corner.x = (STATUS_WIDTH >> 1)
788 				- ((i * (r.extent.width + 1)) >> 1);
789 		SetContextForeGroundColor (STORAGE_BAY_FULL_COLOR);
790 		for (j = GLOBAL_SIS (TotalElementMass);
791 				j >= STORAGE_BAY_CAPACITY; j -= STORAGE_BAY_CAPACITY)
792 		{
793 			DrawFilledRectangle (&r);
794 			r.corner.x += r.extent.width + 1;
795 
796 			--i;
797 		}
798 
799 		r.extent.height = (4 * j + (STORAGE_BAY_CAPACITY - 1)) /
800 				STORAGE_BAY_CAPACITY;
801 		if (r.extent.height)
802 		{
803 			r.corner.y += 4 - r.extent.height;
804 			DrawFilledRectangle (&r);
805 			r.extent.height = 4 - r.extent.height;
806 			if (r.extent.height)
807 			{
808 				r.corner.y = 123;
809 				SetContextForeGroundColor (STORAGE_BAY_EMPTY_COLOR);
810 				DrawFilledRectangle (&r);
811 			}
812 			r.corner.x += r.extent.width + 1;
813 
814 			--i;
815 		}
816 		r.extent.height = 4;
817 
818 		SetContextForeGroundColor (STORAGE_BAY_EMPTY_COLOR);
819 		while (i--)
820 		{
821 			DrawFilledRectangle (&r);
822 			r.corner.x += r.extent.width + 1;
823 		}
824 	}
825 
826 	SetContext (OldContext);
827 }
828 
829 void
GetGaugeRect(RECT * pRect,BOOLEAN IsCrewRect)830 GetGaugeRect (RECT *pRect, BOOLEAN IsCrewRect)
831 {
832 	pRect->extent.width = 24;
833 	pRect->corner.x = (STATUS_WIDTH >> 1) - (pRect->extent.width >> 1);
834 	pRect->extent.height = 5;
835 	pRect->corner.y = IsCrewRect ? 117 : 38;
836 }
837 
838 static void
DrawPC_SIS(void)839 DrawPC_SIS (void)
840 {
841 	TEXT t;
842 	RECT r;
843 
844 	GetGaugeRect (&r, FALSE);
845 	t.baseline.x = STATUS_WIDTH >> 1;
846 	t.baseline.y = r.corner.y - 1;
847 	t.align = ALIGN_CENTER;
848 	t.CharCount = (COUNT)~0;
849 	SetContextFont (TinyFont);
850 	SetContextForeGroundColor (BLACK_COLOR);
851 
852 	r.corner.y -= 6;
853 	r.corner.x--;
854 	r.extent.width += 2;
855 	DrawFilledRectangle (&r);
856 
857 	SetContextFontEffect (SetAbsFrameIndex (FontGradFrame, 1));
858 	t.pStr = GAME_STRING (STATUS_STRING_BASE + 3); // "FUEL"
859 	font_DrawText (&t);
860 
861 	r.corner.y += 79;
862 	t.baseline.y += 79;
863 	DrawFilledRectangle (&r);
864 
865 	SetContextFontEffect (SetAbsFrameIndex (FontGradFrame, 2));
866 	t.pStr = GAME_STRING (STATUS_STRING_BASE + 4); // "CREW"
867 	font_DrawText (&t);
868 	SetContextFontEffect (NULL);
869 
870 	// Background of text "CAPTAIN".
871 	r.corner.x = 2 + 1;
872 	r.corner.y = 3;
873 	r.extent.width = 58;
874 	r.extent.height = 7;
875 	SetContextForeGroundColor (PC_CAPTAIN_STRING_BACKGROUND_COLOR);
876 	DrawFilledRectangle (&r);
877 
878 	// Text "CAPTAIN".
879 	SetContextForeGroundColor (PC_CAPTAIN_STRING_TEXT_COLOR);
880 	t.baseline.y = r.corner.y + 6;
881 	t.pStr = GAME_STRING (STATUS_STRING_BASE + 5); // "CAPTAIN"
882 	font_DrawText (&t);
883 }
884 
885 static void
DrawThrusters(void)886 DrawThrusters (void)
887 {
888 	STAMP s;
889 	COUNT i;
890 
891 	s.origin.x = 1;
892 	s.origin.y = 0;
893 	for (i = 0; i < NUM_DRIVE_SLOTS; ++i)
894 	{
895 		BYTE which_piece = GLOBAL_SIS (DriveSlots[i]);
896 		if (which_piece < EMPTY_SLOT)
897 		{
898 			s.frame = SetAbsFrameIndex (FlagStatFrame, which_piece + 1 + 0);
899 			DrawStamp (&s);
900 			s.frame = IncFrameIndex (s.frame);
901 			DrawStamp (&s);
902 		}
903 
904 		s.origin.y -= 3;
905 	}
906 }
907 
908 static void
DrawTurningJets(void)909 DrawTurningJets (void)
910 {
911 	STAMP s;
912 	COUNT i;
913 
914 	s.origin.x = 1;
915 	s.origin.y = 0;
916 	for (i = 0; i < NUM_JET_SLOTS; ++i)
917 	{
918 		BYTE which_piece = GLOBAL_SIS (JetSlots[i]);
919 		if (which_piece < EMPTY_SLOT)
920 		{
921 			s.frame = SetAbsFrameIndex (FlagStatFrame, which_piece + 1 + 1);
922 			DrawStamp (&s);
923 			s.frame = IncFrameIndex (s.frame);
924 			DrawStamp (&s);
925 		}
926 
927 		s.origin.y -= 3;
928 	}
929 }
930 
931 static void
DrawModules(void)932 DrawModules (void)
933 {
934 	STAMP s;
935 	COUNT i;
936 
937 	s.origin.x = 1; // This properly centers the modules.
938 	s.origin.y = 1;
939 	for (i = 0; i < NUM_MODULE_SLOTS; ++i)
940 	{
941 		BYTE which_piece = GLOBAL_SIS (ModuleSlots[i]);
942 		if (which_piece < EMPTY_SLOT)
943 		{
944 			s.frame = SetAbsFrameIndex (FlagStatFrame, which_piece + 1 + 2);
945 			DrawStamp (&s);
946 		}
947 
948 		s.origin.y -= 3;
949 	}
950 }
951 
952 static void
DrawSupportShips(void)953 DrawSupportShips (void)
954 {
955 	HSHIPFRAG hStarShip;
956 	HSHIPFRAG hNextShip;
957 	const POINT *pship_pos;
958 	const POINT ship_pos[MAX_BUILT_SHIPS] =
959 	{
960 		SUPPORT_SHIP_PTS
961 	};
962 
963 	for (hStarShip = GetHeadLink (&GLOBAL (built_ship_q)),
964 			pship_pos = ship_pos;
965 			hStarShip; hStarShip = hNextShip, ++pship_pos)
966 	{
967 		SHIP_FRAGMENT *StarShipPtr;
968 		STAMP s;
969 
970 		StarShipPtr = LockShipFrag (&GLOBAL (built_ship_q), hStarShip);
971 		hNextShip = _GetSuccLink (StarShipPtr);
972 
973 		s.origin = *pship_pos;
974 		s.frame = StarShipPtr->icons;
975 		DrawStamp (&s);
976 
977 		UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip);
978 	}
979 }
980 
981 static void
DeltaSISGauges_crewDelta(SIZE crew_delta)982 DeltaSISGauges_crewDelta (SIZE crew_delta)
983 {
984 	if (crew_delta == 0)
985 		return;
986 
987 	if (crew_delta != UNDEFINED_DELTA)
988 	{
989 		COUNT CrewCapacity;
990 
991 		if (crew_delta < 0
992 				&& GLOBAL_SIS (CrewEnlisted) <= (COUNT)-crew_delta)
993 			GLOBAL_SIS (CrewEnlisted) = 0;
994 		else
995 		{
996 			GLOBAL_SIS (CrewEnlisted) += crew_delta;
997 			CrewCapacity = GetCrewPodCapacity ();
998 			if (GLOBAL_SIS (CrewEnlisted) > CrewCapacity)
999 				GLOBAL_SIS (CrewEnlisted) = CrewCapacity;
1000 		}
1001 	}
1002 
1003 	{
1004 		TEXT t;
1005 		UNICODE buf[60];
1006 		RECT r;
1007 
1008 		snprintf (buf, sizeof buf, "%u", GLOBAL_SIS (CrewEnlisted));
1009 
1010 		GetGaugeRect (&r, TRUE);
1011 
1012 		t.baseline.x = STATUS_WIDTH >> 1;
1013 		t.baseline.y = r.corner.y + r.extent.height;
1014 		t.align = ALIGN_CENTER;
1015 		t.pStr = buf;
1016 		t.CharCount = (COUNT)~0;
1017 
1018 		SetContextForeGroundColor (BLACK_COLOR);
1019 		DrawFilledRectangle (&r);
1020 		SetContextForeGroundColor (
1021 				BUILD_COLOR (MAKE_RGB15 (0x00, 0x0E, 0x00), 0x6C));
1022 		font_DrawText (&t);
1023 	}
1024 }
1025 
1026 static void
DeltaSISGauges_fuelDelta(SIZE fuel_delta)1027 DeltaSISGauges_fuelDelta (SIZE fuel_delta)
1028 {
1029 	COUNT old_coarse_fuel;
1030 	COUNT new_coarse_fuel;
1031 
1032 	if (fuel_delta == 0)
1033 		return;
1034 
1035 	if (fuel_delta == UNDEFINED_DELTA)
1036 		old_coarse_fuel = (COUNT)~0;
1037 	else
1038 	{
1039 
1040 		old_coarse_fuel = (COUNT)(
1041 				GLOBAL_SIS (FuelOnBoard) / FUEL_TANK_SCALE);
1042 		if (fuel_delta < 0
1043 				&& GLOBAL_SIS (FuelOnBoard) <= (DWORD)-fuel_delta)
1044 		{
1045 			GLOBAL_SIS (FuelOnBoard) = 0;
1046 		}
1047 		else
1048 		{
1049 			DWORD FuelCapacity = GetFuelTankCapacity ();
1050 			GLOBAL_SIS (FuelOnBoard) += fuel_delta;
1051 			if (GLOBAL_SIS (FuelOnBoard) > FuelCapacity)
1052 				GLOBAL_SIS (FuelOnBoard) = FuelCapacity;
1053 		}
1054 	}
1055 
1056 	new_coarse_fuel = (COUNT)(
1057 			GLOBAL_SIS (FuelOnBoard) / FUEL_TANK_SCALE);
1058 	if (new_coarse_fuel != old_coarse_fuel)
1059 	{
1060 		TEXT t;
1061 		UNICODE buf[60];
1062 		RECT r;
1063 
1064 		snprintf (buf, sizeof buf, "%u", new_coarse_fuel);
1065 
1066 		GetGaugeRect (&r, FALSE);
1067 
1068 		t.baseline.x = STATUS_WIDTH >> 1;
1069 		t.baseline.y = r.corner.y + r.extent.height;
1070 		t.align = ALIGN_CENTER;
1071 		t.pStr = buf;
1072 		t.CharCount = (COUNT)~0;
1073 
1074 		SetContextForeGroundColor (BLACK_COLOR);
1075 		DrawFilledRectangle (&r);
1076 		SetContextForeGroundColor (
1077 				BUILD_COLOR (MAKE_RGB15 (0x13, 0x00, 0x00), 0x2C));
1078 		font_DrawText (&t);
1079 	}
1080 }
1081 
1082 static void
DeltaSISGauges_resunitDelta(SIZE resunit_delta)1083 DeltaSISGauges_resunitDelta (SIZE resunit_delta)
1084 {
1085 	if (resunit_delta == 0)
1086 		return;
1087 
1088 	if (resunit_delta != UNDEFINED_DELTA)
1089 	{
1090 		if (resunit_delta < 0
1091 				&& GLOBAL_SIS (ResUnits) <= (DWORD)-resunit_delta)
1092 			GLOBAL_SIS (ResUnits) = 0;
1093 		else
1094 			GLOBAL_SIS (ResUnits) += resunit_delta;
1095 
1096 		assert (curMsgMode == SMM_RES_UNITS);
1097 	}
1098 	else
1099 	{
1100 		RECT r;
1101 
1102 		r.corner.x = 2;
1103 		r.corner.y = 130;
1104 		r.extent.width = STATUS_MESSAGE_WIDTH;
1105 		r.extent.height = STATUS_MESSAGE_HEIGHT;
1106 		SetContextForeGroundColor (
1107 				BUILD_COLOR (MAKE_RGB15 (0x00, 0x08, 0x00), 0x6E));
1108 		DrawFilledRectangle (&r);
1109 	}
1110 
1111 	DrawStatusMessage (NULL);
1112 }
1113 
1114 void
DeltaSISGauges(SIZE crew_delta,SIZE fuel_delta,int resunit_delta)1115 DeltaSISGauges (SIZE crew_delta, SIZE fuel_delta, int resunit_delta)
1116 {
1117 	CONTEXT OldContext;
1118 
1119 	if (crew_delta == 0 && fuel_delta == 0 && resunit_delta == 0)
1120 		return;
1121 
1122 	OldContext = SetContext (StatusContext);
1123 
1124 	BatchGraphics ();
1125 	if (crew_delta == UNDEFINED_DELTA)
1126 	{
1127 		STAMP s;
1128 		s.origin.x = 0;
1129 		s.origin.y = 0;
1130 		s.frame = FlagStatFrame;
1131 		DrawStamp (&s);
1132 
1133 		if (optWhichFonts == OPT_PC)
1134 			DrawPC_SIS();
1135 
1136 		DrawThrusters ();
1137 		DrawTurningJets ();
1138 		DrawModules ();
1139 
1140 		DrawSupportShips ();
1141 	}
1142 
1143 	SetContextFont (TinyFont);
1144 
1145 	DeltaSISGauges_crewDelta (crew_delta);
1146 	DeltaSISGauges_fuelDelta (fuel_delta);
1147 
1148 	if (crew_delta == UNDEFINED_DELTA)
1149 	{
1150 		DrawFlagshipName (TRUE);
1151 		DrawCaptainsName ();
1152 		DrawLanders ();
1153 		DrawStorageBays (FALSE);
1154 	}
1155 
1156 	DeltaSISGauges_resunitDelta (resunit_delta);
1157 
1158 	UnbatchGraphics ();
1159 
1160 	SetContext (OldContext);
1161 }
1162 
1163 
1164 ////////////////////////////////////////////////////////////////////////////
1165 // Crew
1166 ////////////////////////////////////////////////////////////////////////////
1167 
1168 // Get the total amount of crew aboard the SIS.
1169 COUNT
GetCrewCount(void)1170 GetCrewCount (void)
1171 {
1172 	return GLOBAL_SIS (CrewEnlisted);
1173 }
1174 
1175 // Get the number of crew which fit in a module of a specified type.
1176 COUNT
GetModuleCrewCapacity(BYTE moduleType)1177 GetModuleCrewCapacity (BYTE moduleType)
1178 {
1179 	if (moduleType == CREW_POD)
1180 		return CREW_POD_CAPACITY;
1181 
1182 	return 0;
1183 }
1184 
1185 // Gets the amount of crew which currently fit in the ship's crew pods.
1186 COUNT
GetCrewPodCapacity(void)1187 GetCrewPodCapacity (void)
1188 {
1189 	COUNT capacity = 0;
1190 	COUNT slotI;
1191 
1192 	for (slotI = 0; slotI < NUM_MODULE_SLOTS; slotI++)
1193 	{
1194 		BYTE moduleType = GLOBAL_SIS (ModuleSlots[slotI]);
1195 		capacity += GetModuleCrewCapacity (moduleType);
1196 	}
1197 
1198 	return capacity;
1199 }
1200 
1201 // Find the slot number of the crew pod and "seat" number in that crew pod,
1202 // where the Nth crew member would be located.
1203 // If the crew member does not fit, false is returned, and *slotNr and
1204 // *seatNr are unchanged.
1205 static bool
GetCrewPodForCrewMember(COUNT crewNr,COUNT * slotNr,COUNT * seatNr)1206 GetCrewPodForCrewMember (COUNT crewNr, COUNT *slotNr, COUNT *seatNr)
1207 {
1208 	COUNT slotI;
1209 	COUNT capacity = 0;
1210 
1211 	slotI = NUM_MODULE_SLOTS;
1212 	while (slotI--) {
1213 		BYTE moduleType = GLOBAL_SIS (ModuleSlots[slotI]);
1214 		COUNT moduleCapacity = GetModuleCrewCapacity (moduleType);
1215 
1216 		if (crewNr < capacity + moduleCapacity)
1217 		{
1218 			*slotNr = slotI;
1219 			*seatNr = crewNr - capacity;
1220 			return true;
1221 		}
1222 		capacity += moduleCapacity;
1223 	}
1224 
1225 	return false;
1226 }
1227 
1228 // Get the point where to draw the next crew member,
1229 // set the foreground color to the color for that crew member,
1230 // and return GetCrewPodCapacity ().
1231 // TODO: Split of the parts of this function into separate functions.
1232 COUNT
GetCPodCapacity(POINT * ppt)1233 GetCPodCapacity (POINT *ppt)
1234 {
1235 	COUNT crewCount;
1236 	COUNT slotNr;
1237 	COUNT seatNr;
1238 
1239 	COUNT rowNr;
1240 	COUNT colNr;
1241 
1242 	static const Color crewRows[] = PC_CREW_COLOR_TABLE;
1243 
1244 	crewCount = GetCrewCount ();
1245 	if (!GetCrewPodForCrewMember (crewCount, &slotNr, &seatNr))
1246 	{
1247 		// Crew does not fit. *ppt is unchanged.
1248 		return GetCrewPodCapacity ();
1249 	}
1250 
1251 	rowNr = seatNr / CREW_PER_ROW;
1252 	colNr = seatNr % CREW_PER_ROW;
1253 
1254 	if (optWhichFonts == OPT_PC)
1255 		SetContextForeGroundColor (crewRows[rowNr]);
1256 	else
1257 		SetContextForeGroundColor (THREEDO_CREW_COLOR);
1258 
1259 	ppt->x = 27 + (slotNr * SHIP_PIECE_OFFSET) - (colNr * 2);
1260 	ppt->y = 34 - (rowNr * 2);
1261 
1262 	return GetCrewPodCapacity ();
1263 }
1264 
1265 
1266 ////////////////////////////////////////////////////////////////////////////
1267 // Storage bays
1268 ////////////////////////////////////////////////////////////////////////////
1269 
1270 // Get the total amount of minerals aboard the SIS.
1271 static COUNT
GetElementMass(void)1272 GetElementMass (void)
1273 {
1274 	return GLOBAL_SIS (TotalElementMass);
1275 }
1276 
1277 // Get the number of crew which fit in a module of a specified type.
1278 COUNT
GetModuleStorageCapacity(BYTE moduleType)1279 GetModuleStorageCapacity (BYTE moduleType)
1280 {
1281 	if (moduleType == STORAGE_BAY)
1282 		return STORAGE_BAY_CAPACITY;
1283 
1284 	return 0;
1285 }
1286 
1287 // Gets the amount of minerals which currently fit in the ship's storage.
1288 COUNT
GetStorageBayCapacity(void)1289 GetStorageBayCapacity (void)
1290 {
1291 	COUNT capacity = 0;
1292 	COUNT slotI;
1293 
1294 	for (slotI = 0; slotI < NUM_MODULE_SLOTS; slotI++)
1295 	{
1296 		BYTE moduleType = GLOBAL_SIS (ModuleSlots[slotI]);
1297 		capacity += GetModuleStorageCapacity (moduleType);
1298 	}
1299 
1300 	return capacity;
1301 }
1302 
1303 // Find the slot number of the storage bay and "storage cell" number in that
1304 // storage bay, where the N-1th mineral unit would be located.
1305 // If the mineral unit does not fit, false is returned, and *slotNr and
1306 // *cellNr are unchanged.
1307 static bool
GetStorageCellForMineralUnit(COUNT unitNr,COUNT * slotNr,COUNT * cellNr)1308 GetStorageCellForMineralUnit (COUNT unitNr, COUNT *slotNr, COUNT *cellNr)
1309 {
1310 	COUNT slotI;
1311 	COUNT capacity = 0;
1312 
1313 	slotI = NUM_MODULE_SLOTS;
1314 	while (slotI--) {
1315 		BYTE moduleType = GLOBAL_SIS (ModuleSlots[slotI]);
1316 		COUNT moduleCapacity = GetModuleStorageCapacity (moduleType);
1317 
1318 		if (unitNr <= capacity + moduleCapacity)
1319 		{
1320 			*slotNr = slotI;
1321 			*cellNr = unitNr - capacity;
1322 			return true;
1323 		}
1324 		capacity += moduleCapacity;
1325 	}
1326 
1327 	return false;
1328 }
1329 
1330 // Get the point where to draw the next mineral unit,
1331 // set the foreground color to the color for that mineral unit,
1332 // and return GetStorageBayCapacity ().
1333 // TODO: Split of the parts of this function into separate functions.
1334 COUNT
GetSBayCapacity(POINT * ppt)1335 GetSBayCapacity (POINT *ppt)
1336 {
1337 	COUNT massCount;
1338 	COUNT slotNr;
1339 	COUNT cellNr;
1340 
1341 	COUNT rowNr;
1342 	COUNT colNr;
1343 
1344 	static const Color colorBars[] = STORAGE_BAY_COLOR_TABLE;
1345 
1346 	massCount = GetElementMass ();
1347 	if (!GetStorageCellForMineralUnit (massCount, &slotNr, &cellNr))
1348 	{
1349 		// Crew does not fit. *ppt is unchanged.
1350 		return GetStorageBayCapacity ();
1351 	}
1352 
1353 	rowNr = cellNr / SBAY_MASS_PER_ROW;
1354 	colNr = cellNr % SBAY_MASS_PER_ROW;
1355 
1356 	if (rowNr == 0)
1357 		SetContextForeGroundColor (BLACK_COLOR);
1358 	else
1359 	{
1360 		rowNr--;
1361 		SetContextForeGroundColor (colorBars[rowNr]);
1362 	}
1363 
1364 	ppt->x = 19 + (slotNr * SHIP_PIECE_OFFSET);
1365 	ppt->y = 34 - (rowNr * 2);
1366 
1367 	return GetStorageBayCapacity ();
1368 }
1369 
1370 
1371 ////////////////////////////////////////////////////////////////////////////
1372 // Fuel tanks
1373 ////////////////////////////////////////////////////////////////////////////
1374 
1375 // Get the total amount of fuel aboard the SIS.
1376 static DWORD
GetFuelTotal(void)1377 GetFuelTotal (void)
1378 {
1379 	return GLOBAL_SIS (FuelOnBoard);
1380 }
1381 
1382 // Get the amount of fuel which fits in a module of a specified type.
1383 DWORD
GetModuleFuelCapacity(BYTE moduleType)1384 GetModuleFuelCapacity (BYTE moduleType)
1385 {
1386 	if (moduleType == FUEL_TANK)
1387 		return FUEL_TANK_CAPACITY;
1388 
1389 	if (moduleType == HIGHEFF_FUELSYS)
1390 		return HEFUEL_TANK_CAPACITY;
1391 
1392 	return 0;
1393 }
1394 
1395 // Gets the amount of fuel which currently fits in the ship's fuel tanks.
1396 DWORD
GetFuelTankCapacity(void)1397 GetFuelTankCapacity (void)
1398 {
1399 	DWORD capacity = FUEL_RESERVE;
1400 	COUNT slotI;
1401 
1402 	for (slotI = 0; slotI < NUM_MODULE_SLOTS; slotI++)
1403 	{
1404 		BYTE moduleType = GLOBAL_SIS (ModuleSlots[slotI]);
1405 		capacity += GetModuleFuelCapacity (moduleType);
1406 	}
1407 
1408 	return capacity;
1409 }
1410 
1411 // Find the slot number of the fuel cell and "compartment" number in that
1412 // crew pod, where the Nth unit of fuel would be located.
1413 // If the unit does not fit, false is returned, and *slotNr and
1414 // *compartmentNr are unchanged.
1415 // Pre: unitNr >= FUEL_RESERER
1416 static bool
GetFuelTankForFuelUnit(DWORD unitNr,COUNT * slotNr,DWORD * compartmentNr)1417 GetFuelTankForFuelUnit (DWORD unitNr, COUNT *slotNr, DWORD *compartmentNr)
1418 {
1419 	COUNT slotI;
1420 	DWORD capacity = FUEL_RESERVE;
1421 
1422 	assert (unitNr >= FUEL_RESERVE);
1423 
1424 	slotI = NUM_MODULE_SLOTS;
1425 	while (slotI--) {
1426 		BYTE moduleType = GLOBAL_SIS (ModuleSlots[slotI]);
1427 
1428 		capacity += GetModuleFuelCapacity (moduleType);
1429 		if (unitNr < capacity)
1430 		{
1431 			*slotNr = slotI;
1432 			*compartmentNr = capacity - unitNr;
1433 			return true;
1434 		}
1435 	}
1436 
1437 	return false;
1438 }
1439 
1440 // Get the point where to draw the next fuel unit,
1441 // set the foreground color to the color for that unit,
1442 // and return GetFuelTankCapacity ().
1443 // TODO: Split of the parts of this function into separate functions.
1444 DWORD
GetFTankCapacity(POINT * ppt)1445 GetFTankCapacity (POINT *ppt)
1446 {
1447 	DWORD capacity;
1448 	DWORD fuelAmount;
1449 	COUNT slotNr;
1450 	DWORD compartmentNr;
1451 	BYTE moduleType;
1452 	DWORD volume;
1453 
1454 	COUNT rowNr;
1455 
1456 	static const Color fuelColors[] = FUEL_COLOR_TABLE;
1457 
1458 	capacity = GetFuelTankCapacity ();
1459 	fuelAmount = GetFuelTotal ();
1460 	if (fuelAmount < FUEL_RESERVE)
1461 	{
1462 		// Fuel is in the SIS reserve, not in a fuel tank.
1463 		// *ppt is unchanged
1464 		return capacity;
1465 	}
1466 
1467 	if (!GetFuelTankForFuelUnit (fuelAmount, &slotNr, &compartmentNr))
1468 	{
1469 		// Fuel does not fit. *ppt is unchanged.
1470 		return capacity;
1471 	}
1472 
1473 	moduleType = GLOBAL_SIS (ModuleSlots[slotNr]);
1474 	volume = GetModuleFuelCapacity (moduleType);
1475 
1476 	rowNr = ((volume - compartmentNr) * MAX_FUEL_BARS / HEFUEL_TANK_CAPACITY);
1477 
1478 	ppt->x = 21 + (slotNr * SHIP_PIECE_OFFSET);
1479 	if (volume == FUEL_TANK_CAPACITY)
1480 		ppt->y = 27 - rowNr;
1481 	else
1482 		ppt->y = 30 - rowNr;
1483 
1484 	assert (rowNr + 1 < (COUNT) (sizeof fuelColors / sizeof fuelColors[0]));
1485 	SetContextForeGroundColor (fuelColors[rowNr]);
1486 	SetContextBackGroundColor (fuelColors[rowNr + 1]);
1487 
1488 	return capacity;
1489 }
1490 
1491 
1492 ////////////////////////////////////////////////////////////////////////////
1493 
1494 COUNT
CountSISPieces(BYTE piece_type)1495 CountSISPieces (BYTE piece_type)
1496 {
1497 	COUNT i, num_pieces;
1498 
1499 	num_pieces = 0;
1500 	if (piece_type == FUSION_THRUSTER)
1501 	{
1502 		for (i = 0; i < NUM_DRIVE_SLOTS; ++i)
1503 		{
1504 			if (GLOBAL_SIS (DriveSlots[i]) == piece_type)
1505 				++num_pieces;
1506 		}
1507 	}
1508 	else if (piece_type == TURNING_JETS)
1509 	{
1510 		for (i = 0; i < NUM_JET_SLOTS; ++i)
1511 		{
1512 			if (GLOBAL_SIS (JetSlots[i]) == piece_type)
1513 				++num_pieces;
1514 		}
1515 	}
1516 	else
1517 	{
1518 		for (i = 0; i < NUM_MODULE_SLOTS; ++i)
1519 		{
1520 			if (GLOBAL_SIS (ModuleSlots[i]) == piece_type)
1521 				++num_pieces;
1522 		}
1523 	}
1524 
1525 	return num_pieces;
1526 }
1527 
1528 void
DrawAutoPilotMessage(BOOLEAN Reset)1529 DrawAutoPilotMessage (BOOLEAN Reset)
1530 {
1531 	static BOOLEAN LastPilot = FALSE;
1532 	static TimeCount NextTime = 0;
1533 	static DWORD cycle_index = 0;
1534 	BOOLEAN OnAutoPilot;
1535 
1536 	static const Color cycle_tab[] = AUTOPILOT_COLOR_CYCLE_TABLE;
1537 	const size_t cycleCount = sizeof cycle_tab / sizeof cycle_tab[0];
1538 #define BLINK_RATE (ONE_SECOND * 3 / 40) // 9 @ 120 ticks/second
1539 
1540 	if (Reset)
1541 	{	// Just a reset, not drawing
1542 		LastPilot = FALSE;
1543 		return;
1544 	}
1545 
1546 	OnAutoPilot = (GLOBAL (autopilot.x) != ~0 && GLOBAL (autopilot.y) != ~0)
1547 			|| GLOBAL_SIS (FuelOnBoard) == 0;
1548 
1549 	if (OnAutoPilot || LastPilot)
1550 	{
1551 		if (!OnAutoPilot)
1552 		{	// AutoPilot aborted -- clear the AUTO-PILOT message
1553 			DrawSISMessage (NULL);
1554 			cycle_index = 0;
1555 		}
1556 		else if (GetTimeCounter () >= NextTime)
1557 		{
1558 			if (!(GLOBAL (CurrentActivity) & CHECK_ABORT)
1559 					&& GLOBAL_SIS (CrewEnlisted) != (COUNT)~0)
1560 			{
1561 				CONTEXT OldContext;
1562 
1563 				OldContext = SetContext (OffScreenContext);
1564 				SetContextForeGroundColor (cycle_tab[cycle_index]);
1565 				if (GLOBAL_SIS (FuelOnBoard) == 0)
1566 				{
1567 					DrawSISMessageEx (GAME_STRING (NAVIGATION_STRING_BASE + 2),
1568 							-1, -1, DSME_MYCOLOR);   // "OUT OF FUEL"
1569 				}
1570 				else
1571 				{
1572 					DrawSISMessageEx (GAME_STRING (NAVIGATION_STRING_BASE + 3),
1573 							-1, -1, DSME_MYCOLOR);   // "AUTO-PILOT"
1574 				}
1575 				SetContext (OldContext);
1576 			}
1577 
1578 			cycle_index = (cycle_index + 1) % cycleCount;
1579 			NextTime = GetTimeCounter () + BLINK_RATE;
1580 		}
1581 
1582 		LastPilot = OnAutoPilot;
1583 	}
1584 }
1585 
1586 
1587 static FlashContext *flashContext = NULL;
1588 static RECT flash_rect;
1589 static Alarm *flashAlarm = NULL;
1590 static BOOLEAN flashPaused = FALSE;
1591 
1592 static void scheduleFlashAlarm (void);
1593 
1594 static void
updateFlashRect(void * arg)1595 updateFlashRect (void *arg)
1596 {
1597 	if (flashContext == NULL)
1598 		return;
1599 
1600 	Flash_process (flashContext);
1601 	scheduleFlashAlarm ();
1602 	(void) arg;
1603 }
1604 
1605 static void
scheduleFlashAlarm(void)1606 scheduleFlashAlarm (void)
1607 {
1608 	TimeCount nextTime = Flash_nextTime (flashContext);
1609 	DWORD nextTimeMs = (nextTime / ONE_SECOND) * 1000 +
1610 			((nextTime % ONE_SECOND) * 1000 / ONE_SECOND);
1611 			// Overflow-safe conversion.
1612 	flashAlarm = Alarm_addAbsoluteMs (nextTimeMs, updateFlashRect, NULL);
1613 }
1614 
1615 void
SetFlashRect(const RECT * pRect)1616 SetFlashRect (const RECT *pRect)
1617 {
1618 	RECT clip_r = {{0, 0}, {0, 0}};
1619 	RECT temp_r;
1620 
1621 	if (pRect != SFR_MENU_3DO && pRect != SFR_MENU_ANY)
1622 	{
1623 		// The caller specified their own flash area, or NULL (stop flashing).
1624 		GetContextClipRect (&clip_r);
1625 	}
1626 	else
1627 	{
1628  		if (optWhichMenu == OPT_PC && pRect != SFR_MENU_ANY)
1629  		{
1630 			// The player wants PC menus and this flash is not used
1631 			// for a PC menu.
1632 			// Don't flash.
1633  			pRect = 0;
1634  		}
1635  		else
1636  		{
1637 			// The player wants 3DO menus, or the flash is used in both
1638 			// 3DO and PC mode.
1639 			CONTEXT OldContext = SetContext (StatusContext);
1640  			GetContextClipRect (&clip_r);
1641  			pRect = &temp_r;
1642  			temp_r.corner.x = RADAR_X - clip_r.corner.x;
1643  			temp_r.corner.y = RADAR_Y - clip_r.corner.y;
1644  			temp_r.extent.width = RADAR_WIDTH;
1645  			temp_r.extent.height = RADAR_HEIGHT;
1646  			SetContext (OldContext);
1647 		}
1648 	}
1649 
1650 	if (pRect != 0 && pRect->extent.width != 0)
1651 	{
1652 		// Flash rectangle is not empty, start or continue flashing.
1653 		flash_rect = *pRect;
1654 		flash_rect.corner.x += clip_r.corner.x;
1655 		flash_rect.corner.y += clip_r.corner.y;
1656 
1657 		if (flashContext == NULL)
1658 		{
1659 			// Create a new flash context.
1660 			flashContext = Flash_createHighlight (ScreenContext, &flash_rect);
1661 			Flash_setMergeFactors(flashContext, 3, 2, 2);
1662 			Flash_setSpeed (flashContext, 0, ONE_SECOND / 16, 0, ONE_SECOND / 16);
1663 			Flash_setFrameTime (flashContext, ONE_SECOND / 16);
1664 			Flash_start (flashContext);
1665 			scheduleFlashAlarm ();
1666 		}
1667 		else
1668 		{
1669 			// Reuse an existing flash context
1670 			Flash_setRect (flashContext, &flash_rect);
1671 		}
1672 	}
1673 	else
1674 	{
1675 		// Flash rectangle is empty. Stop flashing.
1676 		if (flashContext != NULL)
1677 		{
1678 			Alarm_remove(flashAlarm);
1679 			flashAlarm = 0;
1680 
1681 			Flash_terminate (flashContext);
1682 			flashContext = NULL;
1683 		}
1684 	}
1685 }
1686 
1687 COUNT updateFlashRectRecursion = 0;
1688 // XXX This is necessary at least because DMS_AddEscortShip() calls
1689 // DrawRaceStrings() in an UpdateFlashRect block, which calls
1690 // ClearSISRect(), which calls DrawMenuStateStrings(), which starts its own
1691 // UpdateFlashRect block. This should probably be cleaned up.
1692 
1693 void
PreUpdateFlashRect(void)1694 PreUpdateFlashRect (void)
1695 {
1696 	if (flashAlarm)
1697 	{
1698 		updateFlashRectRecursion++;
1699 		if (updateFlashRectRecursion > 1)
1700 			return;
1701 		Flash_preUpdate (flashContext);
1702 	}
1703 }
1704 
1705 void
PostUpdateFlashRect(void)1706 PostUpdateFlashRect (void)
1707 {
1708 	if (flashAlarm)
1709 	{
1710 		updateFlashRectRecursion--;
1711 		if (updateFlashRectRecursion > 0)
1712 			return;
1713 
1714 		Flash_postUpdate (flashContext);
1715 	}
1716 }
1717 
1718 // Stop flashing if flashing is active.
1719 void
PauseFlash(void)1720 PauseFlash (void)
1721 {
1722 	if (flashContext != NULL)
1723 	{
1724 		Alarm_remove(flashAlarm);
1725 		flashAlarm = 0;
1726 		flashPaused = TRUE;
1727 	}
1728 }
1729 
1730 // Continue flashing after PauseFlash (), if flashing was active.
1731 void
ContinueFlash(void)1732 ContinueFlash (void)
1733 {
1734 	if (flashPaused)
1735 	{
1736 		scheduleFlashAlarm ();
1737 		flashPaused = FALSE;
1738 	}
1739 }
1740 
1741 
1742