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 "../build.h"
20 #include "../colors.h"
21 #include "../controls.h"
22 #include "../races.h"
23 #include "../units.h"
24 #include "../sis.h"
25 #include "../shipcont.h"
26 #include "../setup.h"
27 #include "../sounds.h"
28 #include "port.h"
29 #include "libs/gfxlib.h"
30 #include "libs/tasklib.h"
31
32 #include <stdlib.h>
33
34 // Ship icon positions in status display around the flagship
35 static const POINT ship_pos[MAX_BUILT_SHIPS] =
36 {
37 SUPPORT_SHIP_PTS
38 };
39
40 typedef struct
41 {
42 // Ship icon positions split into (lower half) left and right (upper)
43 // and sorted in the Y coord. These are used for navigation around the
44 // escort positions.
45 POINT shipPos[MAX_BUILT_SHIPS];
46 COUNT count;
47 // Number of ships
48
49 POINT curShipPt;
50 // Location of the currently selected escort
51 FRAME curShipFrame;
52 // Icon of the currently selected escort
53 bool modifyingCrew;
54 // true when in crew modification "sub-menu". This is simple
55 // enough that it does not require a real sub-menu.
56 } ROSTER_STATE;
57
58 static SHIP_FRAGMENT* LockSupportShip (ROSTER_STATE *, HSHIPFRAG *phFrag);
59
60 static void
drawSupportShip(ROSTER_STATE * rosterState,bool filled)61 drawSupportShip (ROSTER_STATE *rosterState, bool filled)
62 {
63 STAMP s;
64
65 if (!rosterState->curShipFrame)
66 return;
67
68 s.origin = rosterState->curShipPt;
69 s.frame = rosterState->curShipFrame;
70
71 if (filled)
72 DrawFilledStamp (&s);
73 else
74 DrawStamp (&s);
75 }
76
77 static void
getSupportShipIcon(ROSTER_STATE * rosterState)78 getSupportShipIcon (ROSTER_STATE *rosterState)
79 {
80 HSHIPFRAG hShipFrag;
81 SHIP_FRAGMENT *ShipFragPtr;
82
83 rosterState->curShipFrame = NULL;
84 ShipFragPtr = LockSupportShip (rosterState, &hShipFrag);
85 if (!ShipFragPtr)
86 return;
87
88 rosterState->curShipFrame = ShipFragPtr->icons;
89 UnlockShipFrag (&GLOBAL (built_ship_q), hShipFrag);
90 }
91
92 static void
flashSupportShip(ROSTER_STATE * rosterState)93 flashSupportShip (ROSTER_STATE *rosterState)
94 {
95 static Color c = BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x00, 0x00), 0x24);
96 static TimeCount NextTime = 0;
97
98 if (GetTimeCounter () >= NextTime)
99 {
100 NextTime = GetTimeCounter () + (ONE_SECOND / 15);
101
102 /* The commented code out code is the old code before the switch
103 * to 24-bits colors. The current code produces very slightly
104 * different colors due to rounding errors, but the old code wasn't
105 * original anyhow, and you can't tell the difference visually.
106 * - SvdB
107 if (c >= BUILD_COLOR (MAKE_RGB15 (0x1F, 0x19, 0x19), 0x24))
108 c = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x00, 0x00), 0x24);
109 else
110 c += BUILD_COLOR (MAKE_RGB15 (0x00, 0x02, 0x02), 0x00);
111 */
112
113 if (c.g >= CC5TO8 (0x19))
114 {
115 c = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x00, 0x00), 0x24);
116 }
117 else
118 {
119 c.g += CC5TO8 (0x02);
120 c.b += CC5TO8 (0x02);
121 }
122 SetContextForeGroundColor (c);
123
124 drawSupportShip (rosterState, TRUE);
125 }
126 }
127
128 static SHIP_FRAGMENT *
LockSupportShip(ROSTER_STATE * rosterState,HSHIPFRAG * phFrag)129 LockSupportShip (ROSTER_STATE *rosterState, HSHIPFRAG *phFrag)
130 {
131 const POINT *pship_pos;
132 HSHIPFRAG hStarShip, hNextShip;
133
134 // Lookup the current escort's location in the unsorted points list
135 // to find the original escort index
136 for (hStarShip = GetHeadLink (&GLOBAL (built_ship_q)),
137 pship_pos = ship_pos;
138 hStarShip; hStarShip = hNextShip, ++pship_pos)
139 {
140 SHIP_FRAGMENT *StarShipPtr;
141
142 StarShipPtr = LockShipFrag (&GLOBAL (built_ship_q), hStarShip);
143
144 if (pointsEqual (*pship_pos, rosterState->curShipPt))
145 {
146 *phFrag = hStarShip;
147 return StarShipPtr;
148 }
149
150 hNextShip = _GetSuccLink (StarShipPtr);
151 UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip);
152 }
153
154 return NULL;
155 }
156
157 static void
flashSupportShipCrew(void)158 flashSupportShipCrew (void)
159 {
160 RECT r;
161
162 SetContext (StatusContext);
163 GetStatusMessageRect (&r);
164 SetFlashRect (&r);
165 }
166
167 static BOOLEAN
DeltaSupportCrew(ROSTER_STATE * rosterState,SIZE crew_delta)168 DeltaSupportCrew (ROSTER_STATE *rosterState, SIZE crew_delta)
169 {
170 BOOLEAN ret = FALSE;
171 UNICODE buf[40];
172 HFLEETINFO hTemplate;
173 HSHIPFRAG hShipFrag;
174 SHIP_FRAGMENT *StarShipPtr;
175 FLEET_INFO *TemplatePtr;
176
177 StarShipPtr = LockSupportShip (rosterState, &hShipFrag);
178 if (!StarShipPtr)
179 return FALSE;
180
181 hTemplate = GetStarShipFromIndex (&GLOBAL (avail_race_q),
182 StarShipPtr->race_id);
183 TemplatePtr = LockFleetInfo (&GLOBAL (avail_race_q), hTemplate);
184
185 StarShipPtr->crew_level += crew_delta;
186
187 if (StarShipPtr->crew_level == 0)
188 StarShipPtr->crew_level = 1;
189 else if (StarShipPtr->crew_level > TemplatePtr->crew_level &&
190 crew_delta > 0)
191 StarShipPtr->crew_level -= crew_delta;
192 else
193 {
194 if (StarShipPtr->crew_level >= TemplatePtr->crew_level)
195 sprintf (buf, "%u", StarShipPtr->crew_level);
196 else
197 sprintf (buf, "%u/%u",
198 StarShipPtr->crew_level,
199 TemplatePtr->crew_level);
200
201 PreUpdateFlashRect ();
202 DrawStatusMessage (buf);
203 PostUpdateFlashRect ();
204 DeltaSISGauges (-crew_delta, 0, 0);
205 if (crew_delta)
206 {
207 flashSupportShipCrew ();
208 }
209 ret = TRUE;
210 }
211
212 UnlockFleetInfo (&GLOBAL (avail_race_q), hTemplate);
213 UnlockShipFrag (&GLOBAL (built_ship_q), hShipFrag);
214
215 return ret;
216 }
217
218 static void
drawModifiedSupportShip(ROSTER_STATE * rosterState)219 drawModifiedSupportShip (ROSTER_STATE *rosterState)
220 {
221 SetContext (StatusContext);
222 SetContextForeGroundColor (ROSTER_MODIFY_SHIP_COLOR);
223 drawSupportShip (rosterState, TRUE);
224 }
225
226 static void
selectSupportShip(ROSTER_STATE * rosterState,COUNT shipIndex)227 selectSupportShip (ROSTER_STATE *rosterState, COUNT shipIndex)
228 {
229 rosterState->curShipPt = rosterState->shipPos[shipIndex];
230 getSupportShipIcon (rosterState);
231 DeltaSupportCrew (rosterState, 0);
232 }
233
234 static BOOLEAN
DoModifyRoster(MENU_STATE * pMS)235 DoModifyRoster (MENU_STATE *pMS)
236 {
237 ROSTER_STATE *rosterState = pMS->privData;
238 BOOLEAN select, cancel, up, down, horiz;
239
240 if (GLOBAL (CurrentActivity) & CHECK_ABORT)
241 return FALSE;
242
243 select = PulsedInputState.menu[KEY_MENU_SELECT];
244 cancel = PulsedInputState.menu[KEY_MENU_CANCEL];
245 up = PulsedInputState.menu[KEY_MENU_UP];
246 down = PulsedInputState.menu[KEY_MENU_DOWN];
247 // Left or right produces the same effect because there are 2 columns
248 horiz = PulsedInputState.menu[KEY_MENU_LEFT] ||
249 PulsedInputState.menu[KEY_MENU_RIGHT];
250
251 if (cancel && !rosterState->modifyingCrew)
252 {
253 return FALSE;
254 }
255 else if (select || cancel)
256 {
257 rosterState->modifyingCrew ^= true;
258 if (!rosterState->modifyingCrew)
259 {
260 SetFlashRect (NULL);
261 SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
262 }
263 else
264 {
265 drawModifiedSupportShip (rosterState);
266 flashSupportShipCrew ();
267 SetMenuSounds (MENU_SOUND_UP | MENU_SOUND_DOWN,
268 MENU_SOUND_SELECT | MENU_SOUND_CANCEL);
269 }
270 }
271 else if (rosterState->modifyingCrew)
272 {
273 SIZE delta = 0;
274 BOOLEAN failed = FALSE;
275
276 if (up)
277 {
278 if (GLOBAL_SIS (CrewEnlisted))
279 delta = 1;
280 else
281 failed = TRUE;
282 }
283 else if (down)
284 {
285 if (GLOBAL_SIS (CrewEnlisted) < GetCrewPodCapacity ())
286 delta = -1;
287 else
288 failed = TRUE;
289 }
290
291 if (delta != 0)
292 {
293 failed = !DeltaSupportCrew (rosterState, delta);
294 }
295
296 if (failed)
297 { // not enough room or crew
298 PlayMenuSound (MENU_SOUND_FAILURE);
299 }
300 }
301 else
302 {
303 COUNT NewState;
304 POINT *pship_pos = rosterState->shipPos;
305 COUNT top_right = (rosterState->count + 1) >> 1;
306
307 NewState = pMS->CurState;
308
309 if (rosterState->count < 2)
310 {
311 // no navigation allowed
312 }
313 else if (horiz)
314 {
315 if (NewState == top_right - 1)
316 NewState = rosterState->count - 1;
317 else if (NewState >= top_right)
318 {
319 NewState -= top_right;
320 if (pship_pos[NewState].y < pship_pos[pMS->CurState].y)
321 ++NewState;
322 }
323 else
324 {
325 NewState += top_right;
326 if (NewState != top_right
327 && pship_pos[NewState].y > pship_pos[pMS->CurState].y)
328 --NewState;
329 }
330 }
331 else if (down)
332 {
333 ++NewState;
334 if (NewState == rosterState->count)
335 NewState = top_right;
336 else if (NewState == top_right)
337 NewState = 0;
338 }
339 else if (up)
340 {
341 if (NewState == 0)
342 NewState = top_right - 1;
343 else if (NewState == top_right)
344 NewState = rosterState->count - 1;
345 else
346 --NewState;
347 }
348
349 BatchGraphics ();
350 SetContext (StatusContext);
351
352 if (NewState != pMS->CurState)
353 {
354 // Draw the previous escort in unselected state
355 drawSupportShip (rosterState, FALSE);
356 // Select the new one
357 selectSupportShip (rosterState, NewState);
358 pMS->CurState = NewState;
359 }
360
361 flashSupportShip (rosterState);
362
363 UnbatchGraphics ();
364 }
365
366 SleepThread (ONE_SECOND / 30);
367
368 return TRUE;
369 }
370
371 static int
compShipPos(const void * ptr1,const void * ptr2)372 compShipPos (const void *ptr1, const void *ptr2)
373 {
374 const POINT *pt1 = (const POINT *) ptr1;
375 const POINT *pt2 = (const POINT *) ptr2;
376
377 // Ships on the left in the lower half
378 if (pt1->x < pt2->x)
379 return -1;
380 else if (pt1->x > pt2->x)
381 return 1;
382
383 // and ordered on Y
384 if (pt1->y < pt2->y)
385 return -1;
386 else if (pt1->y > pt2->y)
387 return 1;
388 else
389 return 0;
390 }
391
392 BOOLEAN
RosterMenu(void)393 RosterMenu (void)
394 {
395 MENU_STATE MenuState;
396 ROSTER_STATE RosterState;
397
398 memset (&MenuState, 0, sizeof MenuState);
399 MenuState.privData = &RosterState;
400
401 memset (&RosterState, 0, sizeof RosterState);
402
403 RosterState.count = CountLinks (&GLOBAL (built_ship_q));
404 if (!RosterState.count)
405 return FALSE;
406
407 // Get the escort positions we will use and sort on X then Y
408 assert (sizeof (RosterState.shipPos) == sizeof (ship_pos));
409 memcpy (RosterState.shipPos, ship_pos, sizeof (ship_pos));
410 qsort (RosterState.shipPos, RosterState.count,
411 sizeof (RosterState.shipPos[0]), compShipPos);
412
413 SetContext (StatusContext);
414 selectSupportShip (&RosterState, MenuState.CurState);
415
416 SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
417
418 MenuState.InputFunc = DoModifyRoster;
419 DoInput (&MenuState, TRUE);
420
421 SetContext (StatusContext);
422 // unselect the last ship
423 drawSupportShip (&RosterState, FALSE);
424 DrawStatusMessage (NULL);
425
426 return TRUE;
427 }
428
429