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 #define PICKMELE_INTERNAL
20 #include "pickmele.h"
21 
22 #include "../battlecontrols.h"
23 #include "../battle.h"
24 #include "../build.h"
25 #include "../controls.h"
26 #include "../flash.h"
27 #include "../igfxres.h"
28 #include "../intel.h"
29 #include "../master.h"
30 #include "../nameref.h"
31 #include "melee.h"
32 #ifdef NETPLAY
33 #	include "netplay/netmelee.h"
34 #	include "netplay/netmisc.h"
35 #	include "netplay/notify.h"
36 #endif
37 #include "../races.h"
38 #include "../setup.h"
39 #include "../sounds.h"
40 #include "libs/async.h"
41 #include "libs/log.h"
42 #include "libs/mathlib.h"
43 
44 
45 #define NUM_PICKMELEE_ROWS 2
46 #define NUM_PICKMELEE_COLUMNS 7
47 
48 #define PICK_X_OFFS 57
49 #define PICK_Y_OFFS 24
50 #define PICK_SIDE_OFFS 100
51 
52 #define NAME_AREA_HEIGHT 7
53 #define MELEE_WIDTH 149
54 #define MELEE_HEIGHT (48 + NAME_AREA_HEIGHT)
55 
56 #define PICKSHIP_TEAM_NAME_TEXT_COLOR \
57 		BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x1F), 0x09)
58 #define PICKSHIP_TEAM_START_VALUE_COLOR \
59 		BUILD_COLOR (MAKE_RGB15 (0x04, 0x05, 0x1F), 0x4B)
60 
61 
62 #ifdef NETPLAY
63 static void reportShipSelected (GETMELEE_STATE *gms, COUNT index);
64 #endif
65 
66 
67 FRAME PickMeleeFrame;
68 
69 
70 static FleetShipIndex
PickMelee_GetShipIndex(BYTE row,BYTE col)71 PickMelee_GetShipIndex (BYTE row, BYTE col)
72 {
73 	return row * NUM_PICKMELEE_COLUMNS + col;
74 }
75 
76 static BYTE
PickMelee_GetShipRow(FleetShipIndex index)77 PickMelee_GetShipRow (FleetShipIndex index)
78 {
79 	return index / NUM_PICKMELEE_COLUMNS;
80 }
81 
82 static BYTE
PickMelee_GetShipColumn(int index)83 PickMelee_GetShipColumn (int index)
84 {
85 	return index % NUM_PICKMELEE_COLUMNS;
86 }
87 
88 // Returns the <index>th ship in the queue, or 0 if it is not available.
89 static HSTARSHIP
MeleeShipByQueueIndex(const QUEUE * queue,COUNT index)90 MeleeShipByQueueIndex (const QUEUE *queue, COUNT index)
91 {
92 	HSTARSHIP hShip;
93 	HSTARSHIP hNextShip;
94 
95 	for (hShip = GetHeadLink (queue); hShip != 0; hShip = hNextShip)
96 	{
97 		STARSHIP *StarShipPtr = LockStarShip (queue, hShip);
98 		if (StarShipPtr->index == index)
99 		{
100 			hNextShip = hShip;
101 			if (StarShipPtr->SpeciesID == NO_ID)
102 				hShip = 0;
103 			UnlockStarShip (queue, hNextShip);
104 			break;
105 		}
106 		hNextShip = _GetSuccLink (StarShipPtr);
107 		UnlockStarShip (queue, hShip);
108 	}
109 
110 	return hShip;
111 }
112 
113 // Returns the <index>th available ship in the queue.
114 static HSTARSHIP
MeleeShipByUsedIndex(const QUEUE * queue,COUNT index)115 MeleeShipByUsedIndex (const QUEUE *queue, COUNT index)
116 {
117 	HSTARSHIP hShip;
118 	HSTARSHIP hNextShip;
119 
120 	for (hShip = GetHeadLink (queue); hShip != 0; hShip = hNextShip)
121 	{
122 		STARSHIP *StarShipPtr = LockStarShip (queue, hShip);
123 		if ((StarShipPtr->SpeciesID != NO_ID) && index-- == 0)
124 		{
125 			UnlockStarShip (queue, hShip);
126 			break;
127 		}
128 		hNextShip = _GetSuccLink (StarShipPtr);
129 		UnlockStarShip (queue, hShip);
130 	}
131 
132 	return hShip;
133 }
134 
135 #if 0
136 static COUNT
137 queueIndexFromShip (HSTARSHIP hShip)
138 {
139 	COUNT result;
140 	STARSHIP *StarShipPtr = LockStarShip (queue, hShip);
141 	result = StarShipPtr->index;
142 	UnlockStarShip (queue, hShip);
143 }
144 #endif
145 
146 // Pre: called does not hold the graphics lock
147 static void
PickMelee_ChangedSelection(GETMELEE_STATE * gms,COUNT playerI)148 PickMelee_ChangedSelection (GETMELEE_STATE *gms, COUNT playerI)
149 {
150 	RECT r;
151 	r.corner.x = PICK_X_OFFS + ((ICON_WIDTH + 2) * gms->player[playerI].col);
152 	r.corner.y = PICK_Y_OFFS + ((ICON_HEIGHT + 2) * gms->player[playerI].row)
153 			+ ((1 - playerI) * PICK_SIDE_OFFS);
154 	r.extent.width = (ICON_WIDTH + 2);
155 	r.extent.height = (ICON_HEIGHT + 2);
156 	Flash_setRect (gms->player[playerI].flashContext, &r);
157 }
158 
159 // Only returns false when there is no ship for the choice.
160 bool
setShipSelected(GETMELEE_STATE * gms,COUNT playerI,COUNT choice,bool reportNetwork)161 setShipSelected(GETMELEE_STATE *gms, COUNT playerI, COUNT choice,
162 		bool reportNetwork)
163 {
164 	HSTARSHIP ship;
165 
166 	assert (!gms->player[playerI].done);
167 
168 	if (choice == (COUNT) ~0)
169 	{
170 		// Random ship selection.
171 		ship = MeleeShipByUsedIndex (&race_q[playerI],
172 				gms->player[playerI].randomIndex);
173 	}
174 	else
175 	{
176 		// Explicit ship selection.
177 		ship = MeleeShipByQueueIndex (&race_q[playerI], choice);
178 	}
179 
180 	if (ship == 0)
181 		return false;
182 
183 	gms->player[playerI].choice = choice;
184 	gms->player[playerI].hBattleShip = ship;
185 	PlayMenuSound (MENU_SOUND_SUCCESS);
186 #ifdef NETPLAY
187 	if (reportNetwork)
188 		reportShipSelected (gms, choice);
189 #else
190 	(void) reportNetwork;
191 #endif
192 	gms->player[playerI].done = true;
193 	return true;
194 }
195 
196 // Returns FALSE if aborted.
197 static BOOLEAN
SelectShip_processInput(GETMELEE_STATE * gms,COUNT playerI,BATTLE_INPUT_STATE inputState)198 SelectShip_processInput (GETMELEE_STATE *gms, COUNT playerI,
199 		BATTLE_INPUT_STATE inputState)
200 {
201 	if (inputState & BATTLE_WEAPON)
202 	{
203 		if (gms->player[playerI].col == NUM_PICKMELEE_COLUMNS &&
204 				gms->player[playerI].row == 0)
205 		{
206 			// Random ship
207 			(void) setShipSelected (gms, playerI, (COUNT) ~0, TRUE);
208 		}
209 		else if (gms->player[playerI].col == NUM_PICKMELEE_COLUMNS &&
210 				gms->player[playerI].row == 1)
211 		{
212 			// Selected exit
213 			if (ConfirmExit ())
214 				return FALSE;
215 		}
216 		else
217 		{
218 			// Selection is on a ship slot.
219 			COUNT slotNr = PickMelee_GetShipIndex (gms->player[playerI].row,
220 					gms->player[playerI].col);
221 			(void) setShipSelected (gms, playerI, slotNr, TRUE);
222 					// If the choice is not valid, setShipSelected()
223 					// will not set .done.
224 		}
225 	}
226 	else
227 	{
228 		// Process motion commands.
229 		COUNT new_row, new_col;
230 
231 		new_row = gms->player[playerI].row;
232 		new_col = gms->player[playerI].col;
233 		if (inputState & BATTLE_LEFT)
234 		{
235 			if (new_col-- == 0)
236 				new_col = NUM_PICKMELEE_COLUMNS;
237 		}
238 		else if (inputState & BATTLE_RIGHT)
239 		{
240 			if (new_col++ == NUM_PICKMELEE_COLUMNS)
241 				new_col = 0;
242 		}
243 		if (inputState & BATTLE_THRUST)
244 		{
245 			if (new_row-- == 0)
246 				new_row = NUM_PICKMELEE_ROWS - 1;
247 		}
248 		else if (inputState & BATTLE_DOWN)
249 		{
250 			if (++new_row == NUM_PICKMELEE_ROWS)
251 				new_row = 0;
252 		}
253 
254 		if (new_row != gms->player[playerI].row ||
255 				new_col != gms->player[playerI].col)
256 		{
257 			gms->player[playerI].row = new_row;
258 			gms->player[playerI].col = new_col;
259 
260 			PlayMenuSound (MENU_SOUND_MOVE);
261 			PickMelee_ChangedSelection (gms, playerI);
262 		}
263 	}
264 
265 	return TRUE;
266 }
267 
268 BOOLEAN
selectShipHuman(HumanInputContext * context,GETMELEE_STATE * gms)269 selectShipHuman (HumanInputContext *context, GETMELEE_STATE *gms)
270 {
271 	BATTLE_INPUT_STATE inputState =
272 			PulsedInputToBattleInput (context->playerNr);
273 
274 	return SelectShip_processInput (gms, context->playerNr, inputState);
275 }
276 
277 BOOLEAN
selectShipComputer(ComputerInputContext * context,GETMELEE_STATE * gms)278 selectShipComputer (ComputerInputContext *context, GETMELEE_STATE *gms)
279 {
280 #define COMPUTER_SELECTION_DELAY (ONE_SECOND >> 1)
281 	TimeCount now = GetTimeCounter ();
282 	if (now < gms->player[context->playerNr].timeIn +
283 			COMPUTER_SELECTION_DELAY)
284 		return TRUE;
285 
286 	return SelectShip_processInput (gms, context->playerNr, BATTLE_WEAPON);
287 			// Simulate selection of the random choice button.
288 }
289 
290 #ifdef NETPLAY
291 BOOLEAN
selectShipNetwork(NetworkInputContext * context,GETMELEE_STATE * gms)292 selectShipNetwork (NetworkInputContext *context, GETMELEE_STATE *gms)
293 {
294 	flushPacketQueues ();
295 			// Sets gms->player[context->playerNr].remoteSelected if input
296 			// is received.
297 	if (gms->player[context->playerNr].remoteSelected)
298 		gms->player[context->playerNr].done = TRUE;
299 
300 	return TRUE;
301 }
302 #endif
303 
304 // Select a new ship from the fleet for battle.
305 // Returns 'TRUE' if no choice has been made yet; this function is to be
306 // called again later.
307 // Returns 'FALSE' if a choice has been made. gms->hStarShip is set
308 // to the chosen (or randomly selected) ship, or to 0 if 'exit' has
309 // been chosen.
310 /* TODO: Include player timeouts */
311 static BOOLEAN
DoGetMelee(GETMELEE_STATE * gms)312 DoGetMelee (GETMELEE_STATE *gms)
313 {
314 	BOOLEAN done;
315 	COUNT playerI;
316 
317 	SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE);
318 
319 	if (!gms->Initialized)
320 	{
321 		gms->Initialized = TRUE;
322 		return TRUE;
323 	}
324 
325 	for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
326 	{
327 		if (!gms->player[playerI].selecting)
328 			continue;
329 
330 		if (!gms->player[playerI].done)
331 			Flash_process (gms->player[playerI].flashContext);
332 	}
333 
334 	SleepThread (ONE_SECOND / 120);
335 
336 #ifdef NETPLAY
337 	netInput ();
338 
339 	if (!allConnected ())
340 		goto aborted;
341 #endif
342 
343 	if (GLOBAL (CurrentActivity) & CHECK_ABORT)
344 		goto aborted;
345 
346 	done = TRUE;
347 	for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
348 	{
349 		if (!gms->player[playerI].selecting)
350 			continue;
351 
352 		if (!gms->player[playerI].done) {
353 			if (!PlayerInput[playerI]->handlers->selectShip (
354 					PlayerInput[playerI], gms))
355 				goto aborted;
356 
357 			if (gms->player[playerI].done)
358 			{
359 				Flash_terminate (gms->player[playerI].flashContext);
360 				gms->player[playerI].flashContext = NULL;
361 			}
362 			else
363 				done = FALSE;
364 		}
365 	}
366 
367 #ifdef NETPLAY
368 	flushPacketQueues ();
369 #endif
370 	return !done;
371 
372 aborted:
373 #ifdef NETPLAY
374 	flushPacketQueues ();
375 #endif
376 	for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
377 	{
378 		if (!gms->player[playerI].selecting)
379 			continue;
380 
381 		gms->player[playerI].choice = 0;
382 		gms->player[playerI].hBattleShip = 0;
383 	}
384 	GLOBAL (CurrentActivity) &= ~CHECK_ABORT;
385 	return FALSE;
386 }
387 
388 static COUNT
GetRaceQueueValue(const QUEUE * queue)389 GetRaceQueueValue (const QUEUE *queue) {
390 	COUNT result;
391 	HSTARSHIP hBattleShip, hNextShip;
392 
393 	result = 0;
394 	for (hBattleShip = GetHeadLink (queue);
395 			hBattleShip != 0; hBattleShip = hNextShip)
396 	{
397 		STARSHIP *StarShipPtr = LockStarShip (queue, hBattleShip);
398 		hNextShip = _GetSuccLink (StarShipPtr);
399 
400 		if (StarShipPtr->SpeciesID == NO_ID)
401 			continue;  // Not active any more.
402 
403 		result += StarShipPtr->ship_cost;
404 
405 		UnlockStarShip (queue, hBattleShip);
406 	}
407 
408 	return result;
409 }
410 
411 // Cross out the icon for the dead ship.
412 // 'frame' is the PickMeleeFrame for the player.
413 // 'shipI' is the index in the ship list.
414 // Pre: caller holds the graphics lock.
415 static void
CrossOutShip(FRAME frame,COUNT shipNr)416 CrossOutShip (FRAME frame, COUNT shipNr)
417 {
418 	CONTEXT OldContext;
419 	STAMP s;
420 	BYTE row = PickMelee_GetShipRow (shipNr);
421 	BYTE col = PickMelee_GetShipColumn (shipNr);
422 
423 	OldContext = SetContext (OffScreenContext);
424 
425 	SetContextFGFrame (frame);
426 
427 	s.origin.x = 3 + ((ICON_WIDTH + 2) * col);
428 	s.origin.y = 9 + ((ICON_HEIGHT + 2) * row);
429 	s.frame = SetAbsFrameIndex (StatusFrame, 3);
430 			// Cross for through the ship image.
431 	DrawStamp (&s);
432 
433 	SetContext (OldContext);
434 }
435 
436 // Draw the value of the fleet in the top right of the PickMeleeFrame.
437 // Pre: caller holds the graphics lock.
438 static void
UpdatePickMeleeFleetValue(FRAME frame,COUNT which_player)439 UpdatePickMeleeFleetValue (FRAME frame, COUNT which_player)
440 {
441 	CONTEXT OldContext;
442 	COUNT value;
443 	RECT r;
444 	TEXT t;
445 	UNICODE buf[40];
446 
447 	value = GetRaceQueueValue (&race_q[which_player]);
448 
449 	OldContext = SetContext (OffScreenContext);
450 	SetContextFGFrame (frame);
451 
452 	// Erase the old value text.
453 	GetFrameRect (frame, &r);
454 	r.extent.width -= 4;
455 	t.baseline.x = r.extent.width;
456 	r.corner.x = r.extent.width - (6 * 3);
457 	r.corner.y = 2;
458 	r.extent.width = (6 * 3);
459 	r.extent.height = 7 - 2;
460 	SetContextForeGroundColor (PICK_BG_COLOR);
461 	DrawFilledRectangle (&r);
462 
463 	// Draw the new value text.
464 	sprintf (buf, "%d", value);
465 	t.baseline.y = 7;
466 	t.align = ALIGN_RIGHT;
467 	t.pStr = buf;
468 	t.CharCount = (COUNT)~0;
469 	SetContextFont (TinyFont);
470 	SetContextForeGroundColor (PICK_VALUE_COLOR);
471 	font_DrawText (&t);
472 
473 	SetContext (OldContext);
474 }
475 
476 // Create a frame for each player to display their current fleet in,
477 // to be used when selecting the next ship to fight with.
478 void
BuildPickMeleeFrame(void)479 BuildPickMeleeFrame (void)
480 {
481 	STAMP s;
482 	CONTEXT	OldContext = SetContext (OffScreenContext);
483 
484 	if (PickMeleeFrame)
485 		DestroyDrawable (ReleaseDrawable (PickMeleeFrame));
486 
487 	PickMeleeFrame = CaptureDrawable (CreateDrawable (
488 			WANT_PIXMAP, MELEE_WIDTH, MELEE_HEIGHT, 2));
489 	s.origin.x = 0;
490 	s.origin.y = 0;
491 
492 	s.frame = CaptureDrawable (LoadGraphic (MELEE_PICK_MASK_PMAP_ANIM));
493 	SetContextFGFrame (PickMeleeFrame);
494 	DrawStamp (&s);
495 
496 	s.frame = IncFrameIndex (s.frame);
497 	SetContextFGFrame (IncFrameIndex (PickMeleeFrame));
498 	DrawStamp (&s);
499 
500 	DestroyDrawable (ReleaseDrawable (s.frame));
501 
502 	SetContext (OldContext);
503 }
504 
505 // Put the ship icons in the PickMeleeFrame, and create a queue
506 // for each player.
507 // XXX TODO: split off creating the queue into a separate function.
508 void
FillPickMeleeFrame(MeleeSetup * setup)509 FillPickMeleeFrame (MeleeSetup *setup)
510 {
511 	COUNT i;
512 	CONTEXT OldContext;
513 
514 	OldContext = SetContext (OffScreenContext);
515 
516 	for (i = 0; i < NUM_SIDES; ++i)
517 	{
518 		COUNT side;
519 		COUNT sideI;
520 		RECT r;
521 		TEXT t;
522 		STAMP s;
523 		UNICODE buf[30];
524 		FleetShipIndex index;
525 
526 		sideI = GetPlayerOrder (i);
527 		side = !sideI;
528 
529 		s.frame = SetAbsFrameIndex (PickMeleeFrame, side);
530 		SetContextFGFrame (s.frame);
531 
532 		GetFrameRect (s.frame, &r);
533 		t.baseline.x = r.extent.width >> 1;
534 		t.baseline.y = r.extent.height - NAME_AREA_HEIGHT + 4;
535 
536 		r.corner.x += 2;
537 		r.corner.y += 2;
538 		r.extent.width -= (2 * 2) + (ICON_WIDTH + 2) + 1;
539 		r.extent.height -= (2 * 2) + NAME_AREA_HEIGHT;
540 		SetContextForeGroundColor (PICK_BG_COLOR);
541 		DrawFilledRectangle (&r);
542 
543 		r.corner.x += 2;
544 		r.extent.width += (ICON_WIDTH + 2) - (2 * 2);
545 		r.corner.y += r.extent.height;
546 		r.extent.height = NAME_AREA_HEIGHT;
547 		DrawFilledRectangle (&r);
548 
549 		// Team name at the bottom of the frame:
550 		t.align = ALIGN_CENTER;
551 		t.pStr = MeleeSetup_getTeamName (setup, sideI);
552 		t.CharCount = (COUNT) ~0;
553 		SetContextFont (TinyFont);
554 		SetContextForeGroundColor (PICKSHIP_TEAM_NAME_TEXT_COLOR);
555 		font_DrawText (&t);
556 
557 		// Total team value of the starting team:
558 		sprintf (buf, "%u", MeleeSetup_getFleetValue (setup, sideI));
559 		t.baseline.x = 4;
560 		t.baseline.y = 7;
561 		t.align = ALIGN_LEFT;
562 		t.pStr = buf;
563 		t.CharCount = (COUNT)~0;
564 		SetContextForeGroundColor (PICKSHIP_TEAM_START_VALUE_COLOR);
565 		font_DrawText (&t);
566 
567 		assert (CountLinks (&race_q[side]) == 0);
568 
569 		for (index = 0; index < MELEE_FLEET_SIZE; index++)
570 		{
571 			MeleeShip StarShip;
572 
573 			StarShip = MeleeSetup_getShip (setup, sideI, index);
574 			if (StarShip == MELEE_NONE)
575 				continue;
576 
577 			{
578 				BYTE row, col;
579 				BYTE ship_cost;
580 				HMASTERSHIP hMasterShip;
581 				HSTARSHIP hBuiltShip;
582 				MASTER_SHIP_INFO *MasterPtr;
583 				STARSHIP *BuiltShipPtr;
584 				BYTE captains_name_index;
585 
586 				hMasterShip = GetStarShipFromIndex (&master_q, StarShip);
587 				MasterPtr = LockMasterShip (&master_q, hMasterShip);
588 
589 				captains_name_index = NameCaptain (&race_q[side],
590 						MasterPtr->SpeciesID);
591 				hBuiltShip = Build (&race_q[side], MasterPtr->SpeciesID);
592 
593 				// Draw the icon.
594 				row = PickMelee_GetShipRow (index);
595 				col = PickMelee_GetShipColumn (index);
596 				s.origin.x = 4 + ((ICON_WIDTH + 2) * col);
597 				s.origin.y = 10 + ((ICON_HEIGHT + 2) * row);
598 				s.frame = MasterPtr->ShipInfo.icons;
599 				DrawStamp (&s);
600 
601 				ship_cost = MasterPtr->ShipInfo.ship_cost;
602 				UnlockMasterShip (&master_q, hMasterShip);
603 
604 				BuiltShipPtr = LockStarShip (&race_q[side], hBuiltShip);
605 				BuiltShipPtr->index = index;
606 				BuiltShipPtr->ship_cost = ship_cost;
607 				BuiltShipPtr->playerNr = side;
608 				BuiltShipPtr->captains_name_index = captains_name_index;
609 				// The next ones are not used in Melee
610 				BuiltShipPtr->crew_level = 0;
611 				BuiltShipPtr->max_crew = 0;
612 				BuiltShipPtr->race_strings = 0;
613 				BuiltShipPtr->icons = 0;
614 				BuiltShipPtr->RaceDescPtr = 0;
615 				UnlockStarShip (&race_q[side], hBuiltShip);
616 			}
617 		}
618 	}
619 
620 	SetContext (OldContext);
621 }
622 
623 void
DestroyPickMeleeFrame(void)624 DestroyPickMeleeFrame (void)
625 {
626 	DestroyDrawable (ReleaseDrawable (PickMeleeFrame));
627 	PickMeleeFrame = 0;
628 }
629 
630 // Pre: caller holds the graphics lock.
631 static void
DrawPickMeleeFrame(COUNT which_player)632 DrawPickMeleeFrame (COUNT which_player)
633 {
634 	CONTEXT oldContext;
635 	STAMP s;
636 
637 	oldContext = SetContext (SpaceContext);
638 	s.frame = SetAbsFrameIndex (PickMeleeFrame, which_player);
639 	s.origin.x = PICK_X_OFFS - 3;
640 	s.origin.y = PICK_Y_OFFS - 9 + ((1 - which_player) * PICK_SIDE_OFFS);
641 	DrawStamp (&s);
642 			// Draw the selection box to screen.
643 
644 	SetContext (oldContext);
645 }
646 
647 // Pre: caller holds the graphics lock.
648 void
MeleeGameOver(void)649 MeleeGameOver (void)
650 {
651 	COUNT playerI;
652 	DWORD TimeOut;
653 	BOOLEAN PressState, ButtonState;
654 
655 	// Show the battle result.
656 	for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
657 		DrawPickMeleeFrame (playerI);
658 
659 
660 #ifdef NETPLAY
661 	negotiateReadyConnections(true, NetState_inSetup);
662 #endif
663 
664 	TimeOut = GetTimeCounter () + (ONE_SECOND * 4);
665 
666 	PressState = PulsedInputState.menu[KEY_MENU_SELECT] ||
667 			PulsedInputState.menu[KEY_MENU_CANCEL];
668 	do
669 	{
670 		UpdateInputState ();
671 		ButtonState = PulsedInputState.menu[KEY_MENU_SELECT] ||
672 				PulsedInputState.menu[KEY_MENU_CANCEL];
673 		if (PressState)
674 		{
675 			PressState = ButtonState;
676 			ButtonState = FALSE;
677 		}
678 
679 		Async_process ();
680 		TaskSwitch ();
681 	} while (!(GLOBAL (CurrentActivity) & CHECK_ABORT) && (!ButtonState
682 			&& (!(PlayerControl[0] & PlayerControl[1] & PSYTRON_CONTROL)
683 			|| GetTimeCounter () < TimeOut)));
684 
685 }
686 
687 void
MeleeShipDeath(STARSHIP * ship)688 MeleeShipDeath (STARSHIP *ship)
689 {
690 	FRAME frame;
691 
692 	// Deactivate fleet position.
693 	ship->SpeciesID = NO_ID;
694 
695 	frame = SetAbsFrameIndex (PickMeleeFrame, ship->playerNr);
696 	CrossOutShip (frame, ship->index);
697 	UpdatePickMeleeFleetValue (frame, ship->playerNr);
698 }
699 
700 // Post: the NetState for all players is NetState_interBattle
701 static BOOLEAN
GetMeleeStarShips(COUNT playerMask,HSTARSHIP * ships)702 GetMeleeStarShips (COUNT playerMask, HSTARSHIP *ships)
703 {
704 	COUNT playerI;
705 	BOOLEAN ok;
706 	GETMELEE_STATE gmstate;
707 	TimeCount now;
708 	COUNT i;
709 
710 #ifdef NETPLAY
711 	for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
712 	{
713 		NetConnection *conn;
714 
715 		if ((playerMask & (1 << playerI)) == 0)
716 			continue;
717 
718 		// XXX: This does not have to be done per connection.
719 		conn = netConnections[playerI];
720 		if (conn != NULL) {
721 			BattleStateData *battleStateData;
722 			battleStateData =
723 					(BattleStateData *) NetConnection_getStateData (conn);
724 			battleStateData->getMeleeState = &gmstate;
725 		}
726 	}
727 #endif
728 
729 	ok = true;
730 
731 	now = GetTimeCounter ();
732 	gmstate.InputFunc = DoGetMelee;
733 	gmstate.Initialized = FALSE;
734 	for (i = 0; i < NUM_PLAYERS; ++i)
735 	{
736 		// We have to use TFB_Random() results in specific order
737 		playerI = GetPlayerOrder (i);
738 		gmstate.player[playerI].selecting =
739 				(playerMask & (1 << playerI)) != 0;
740 		gmstate.player[playerI].ships_left = battle_counter[playerI];
741 
742 		// We determine in advance which ship would be chosen if the player
743 		// wants a random ship, to keep it simple to keep network parties
744 		// synchronised.
745 		gmstate.player[playerI].randomIndex =
746 				(COUNT)TFB_Random () % gmstate.player[playerI].ships_left;
747 		gmstate.player[playerI].done = FALSE;
748 
749 		if (!gmstate.player[playerI].selecting)
750 			continue;
751 
752 		gmstate.player[playerI].timeIn = now;
753 		gmstate.player[playerI].row = 0;
754 		gmstate.player[playerI].col = NUM_PICKMELEE_COLUMNS;
755 #ifdef NETPLAY
756 		gmstate.player[playerI].remoteSelected = FALSE;
757 #endif
758 
759 		gmstate.player[playerI].flashContext =
760 				Flash_createHighlight (ScreenContext, NULL);
761 		Flash_setMergeFactors (gmstate.player[playerI].flashContext,
762 				2, 3, 2);
763 		Flash_setFrameTime (gmstate.player[playerI].flashContext,
764 				ONE_SECOND / 16);
765 #ifdef NETPLAY
766 		if (PlayerControl[playerI] & NETWORK_CONTROL)
767 			Flash_setSpeed (gmstate.player[playerI].flashContext,
768 					ONE_SECOND / 2, 0, ONE_SECOND / 2, 0);
769 		else
770 #endif
771 		{
772 			Flash_setSpeed (gmstate.player[playerI].flashContext,
773 					0, ONE_SECOND / 16, 0, ONE_SECOND / 16);
774 		}
775 		PickMelee_ChangedSelection (&gmstate, playerI);
776 		Flash_start (gmstate.player[playerI].flashContext);
777 	}
778 
779 #ifdef NETPLAY
780 	{
781 		// NB. gmstate.player[].randomIndex and gmstate.player[].done must
782 		// be initialised before negotiateReadyConnections is completed, to
783 		// ensure that they are initialised when the SelectShip packet
784 		// arrives.
785 		bool allOk = negotiateReadyConnections (true, NetState_selectShip);
786 		if (!allOk)
787 		{
788 			// Some network connection has been reset.
789 			ok = false;
790 		}
791 	}
792 #endif
793 	SetDefaultMenuRepeatDelay ();
794 
795 	SetContext (OffScreenContext);
796 
797 
798 	DoInput (&gmstate, FALSE);
799 	WaitForSoundEnd (0);
800 
801 
802 	for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
803 	{
804 		if (!gmstate.player[playerI].selecting)
805 			continue;
806 
807 		if (gmstate.player[playerI].done)
808 		{
809 			// Flash rectangle is already terminated.
810 			ships[playerI] = gmstate.player[playerI].hBattleShip;
811 		}
812 		else
813 		{
814 			Flash_terminate (gmstate.player[playerI].flashContext);
815 			gmstate.player[playerI].flashContext = NULL;
816 			ok = false;
817 		}
818 	}
819 
820 #ifdef NETPLAY
821 	if (ok)
822 	{
823 		if (!negotiateReadyConnections (true, NetState_interBattle))
824 			ok = false;
825 	}
826 	else
827 		setStateConnections (NetState_interBattle);
828 #endif
829 
830 	if (!ok)
831 	{
832 		// Aborting.
833 		GLOBAL (CurrentActivity) &= ~IN_BATTLE;
834 	}
835 
836 #ifdef NETPLAY
837 	for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
838 	{
839 		NetConnection *conn;
840 
841 		if ((playerMask & (1 << playerI)) == 0)
842 			continue;
843 
844 		// XXX: This does not have to be done per connection.
845 		conn = netConnections[playerI];
846 		if (conn != NULL && NetConnection_isConnected (conn))
847 		{
848 			BattleStateData *battleStateData;
849 			battleStateData =
850 					(BattleStateData *) NetConnection_getStateData (conn);
851 			battleStateData->getMeleeState = NULL;
852 		}
853 	}
854 #endif
855 
856 	return ok;
857 }
858 
859 BOOLEAN
GetInitialMeleeStarShips(HSTARSHIP * result)860 GetInitialMeleeStarShips (HSTARSHIP *result)
861 {
862 	COUNT playerI;
863 	COUNT playerMask;
864 
865 	for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
866 	{
867 		FRAME frame;
868 		frame = SetAbsFrameIndex (PickMeleeFrame, playerI);
869 		UpdatePickMeleeFleetValue (frame, playerI);
870 		DrawPickMeleeFrame (playerI);
871 	}
872 
873 	// Fade in
874 	SleepThreadUntil (FadeScreen (FadeAllToColor, ONE_SECOND / 2)
875 			+ ONE_SECOND / 60);
876 	FlushColorXForms ();
877 
878 	playerMask = 0;
879 	for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
880 		playerMask |= (1 << playerI);
881 
882 	return GetMeleeStarShips (playerMask, result);
883 }
884 
885 // Get the next ship to use in SuperMelee.
886 BOOLEAN
GetNextMeleeStarShip(COUNT which_player,HSTARSHIP * result)887 GetNextMeleeStarShip (COUNT which_player, HSTARSHIP *result)
888 {
889 	COUNT playerMask;
890 	HSTARSHIP ships[NUM_PLAYERS];
891 	BOOLEAN ok;
892 
893 	DrawPickMeleeFrame (which_player);
894 
895 	playerMask = 1 << which_player;
896 	ok = GetMeleeStarShips (playerMask, ships);
897 	if (ok)
898 		*result = ships[which_player];
899 
900 	return ok;
901 }
902 
903 #ifdef NETPLAY
904 // Called when a ship selection has arrived from a remote player.
905 bool
updateMeleeSelection(GETMELEE_STATE * gms,COUNT playerI,COUNT ship)906 updateMeleeSelection (GETMELEE_STATE *gms, COUNT playerI, COUNT ship)
907 {
908 	if (gms == NULL || !gms->player[playerI].selecting ||
909 			gms->player[playerI].done)
910 	{
911 		// This happens when we get an update message from a connection
912 		// for who we are not selecting a ship.
913 		log_add (log_Warning, "Unexpected ship selection packet "
914 				"received.\n");
915 		return false;
916 	}
917 
918 	if (!setShipSelected (gms, playerI, ship, false))
919 	{
920 		log_add (log_Warning, "Invalid ship selection received from remote "
921 				"party.\n");
922 		return false;
923 	}
924 
925 	gms->player[playerI].remoteSelected = TRUE;
926 	return true;
927 }
928 
929 static void
reportShipSelected(GETMELEE_STATE * gms,COUNT index)930 reportShipSelected (GETMELEE_STATE *gms, COUNT index)
931 {
932 	size_t playerI;
933 	for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
934 	{
935 		NetConnection *conn = netConnections[playerI];
936 
937 		if (conn == NULL)
938 			continue;
939 
940 		if (!NetConnection_isConnected (conn))
941 			continue;
942 
943 		Netplay_Notify_shipSelected (conn, index);
944 	}
945 	(void) gms;
946 }
947 #endif
948 
949