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