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