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 "melee.h"
20 
21 #include "options.h"
22 #include "buildpick.h"
23 #include "meleeship.h"
24 #include "../battle.h"
25 #include "../build.h"
26 #include "../status.h"
27 #include "../colors.h"
28 #include "../comm.h"
29 		// for getLineWithinWidth()
30 #include "../cons_res.h"
31 		// for load_gravity_well() and free_gravity_well()
32 #include "../controls.h"
33 #include "../gamestr.h"
34 #include "../globdata.h"
35 #include "../intel.h"
36 #include "../master.h"
37 #include "../nameref.h"
38 #ifdef NETPLAY
39 #	include "netplay/netconnection.h"
40 #	include "netplay/netmelee.h"
41 #	include "netplay/notify.h"
42 #	include "netplay/notifyall.h"
43 #	include "libs/graphics/widgets.h"
44 		// for DrawShadowedBox()
45 #	include "../cnctdlg.h"
46 		// for MeleeConnectDialog()
47 #endif  /* defined (NETPLAY) */
48 #include "../resinst.h"
49 #include "../settings.h"
50 #include "../setup.h"
51 #include "../sounds.h"
52 #include "../util.h"
53 		// for DrawStarConBox()
54 #include "../planets/planets.h"
55 		// for NUMBER_OF_PLANET_TYPES
56 #include "libs/gfxlib.h"
57 #include "libs/mathlib.h"
58 		// for TFB_Random()
59 #include "libs/reslib.h"
60 #include "libs/log.h"
61 #include "libs/uio.h"
62 
63 
64 #include <assert.h>
65 #include <string.h>
66 
67 
68 static void StartMelee (MELEE_STATE *pMS);
69 #ifdef NETPLAY
70 static ssize_t numPlayersReady (void);
71 #endif  /* NETPLAY */
72 
73 enum
74 {
75 #ifdef NETPLAY
76 	NET_TOP,
77 #endif
78 	CONTROLS_TOP,
79 	SAVE_TOP,
80 	LOAD_TOP,
81 	START_MELEE,
82 	LOAD_BOT,
83 	SAVE_BOT,
84 	CONTROLS_BOT,
85 #ifdef NETPLAY
86 	NET_BOT,
87 #endif
88 	QUIT_BOT,
89 	EDIT_MELEE, // Editing a fleet or the team name
90 	BUILD_PICK  // Selecting a ship to add to a fleet
91 };
92 
93 #ifdef NETPLAY
94 #define TOP_ENTRY NET_TOP
95 #else
96 #define TOP_ENTRY CONTROLS_TOP
97 #endif
98 
99 #define MELEE_X_OFFS 2
100 #define MELEE_Y_OFFS 21
101 #define MELEE_BOX_WIDTH 34
102 #define MELEE_BOX_HEIGHT 34
103 #define MELEE_BOX_SPACE 1
104 
105 #define MENU_X_OFFS 29
106 
107 #define INFO_ORIGIN_X 4
108 #define INFO_WIDTH 58
109 #define TEAM_INFO_ORIGIN_Y 3
110 #define TEAM_INFO_HEIGHT (SHIP_INFO_HEIGHT + 75)
111 #define MODE_INFO_ORIGIN_Y (TEAM_INFO_HEIGHT + 6)
112 #define MODE_INFO_HEIGHT ((STATUS_HEIGHT - 3) - MODE_INFO_ORIGIN_Y)
113 #define RACE_INFO_ORIGIN_Y (SHIP_INFO_HEIGHT + 6)
114 #define RACE_INFO_HEIGHT ((STATUS_HEIGHT - 3) - RACE_INFO_ORIGIN_Y)
115 
116 #define MELEE_STATUS_X_OFFS 1
117 #define MELEE_STATUS_Y_OFFS 201
118 #define MELEE_STATUS_WIDTH  (NUM_MELEE_COLUMNS * \
119 		(MELEE_BOX_WIDTH + MELEE_BOX_SPACE))
120 #define MELEE_STATUS_HEIGHT 38
121 
122 #define MELEE_BACKGROUND_COLOR \
123 		BUILD_COLOR (MAKE_RGB15 (0x14, 0x00, 0x00), 0x04)
124 #define MELEE_TITLE_COLOR \
125 		BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x0A), 0x0C)
126 #define MELEE_TEXT_COLOR \
127 		BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x0A), 0x0C)
128 #define MELEE_TEAM_TEXT_COLOR \
129 		BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x0A), 0x0E)
130 
131 #define STATE_BACKGROUND_COLOR \
132 		BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01)
133 #define STATE_TEXT_COLOR \
134 		BUILD_COLOR (MAKE_RGB15 (0x00, 0x14, 0x14), 0x03)
135 #define ACTIVE_STATE_TEXT_COLOR \
136 		BUILD_COLOR (MAKE_RGB15 (0x0A, 0x1F, 0x1F), 0x0B)
137 #define UNAVAILABLE_STATE_TEXT_COLOR \
138 		BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x1F), 0x09)
139 #define HI_STATE_TEXT_COLOR \
140 		BUILD_COLOR (MAKE_RGB15 (0x0A, 0x1F, 0x1F), 0x0B)
141 #define HI_STATE_BACKGROUND_COLOR \
142 		BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x1F), 0x09)
143 
144 // XXX: The following entries are unused:
145 #define LIST_INFO_BACKGROUND_COLOR \
146 		BUILD_COLOR (MAKE_RGB15 (0x14, 0x00, 0x14), 0x05)
147 #define LIST_INFO_TITLE_COLOR \
148 		WHITE_COLOR
149 #define LIST_INFO_TEXT_COLOR \
150 		LT_GRAY_COLOR
151 #define LIST_INFO_CURENTRY_TEXT_COLOR \
152 		WHITE_COLOR
153 #define HI_LIST_INFO_TEXT_COLOR \
154 		BUILD_COLOR (MAKE_RGB15 (0x14, 0x00, 0x00), 0x04)
155 #define HI_LIST_INFO_BACKGROUND_COLOR \
156 		BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x1F), 0x0D)
157 
158 #define TEAM_NAME_TEXT_COLOR \
159 		BUILD_COLOR (MAKE_RGB15 (0x0F, 0x10, 0x1B), 0x00)
160 #define TEAM_NAME_EDIT_TEXT_COLOR \
161 		BUILD_COLOR (MAKE_RGB15 (0x17, 0x18, 0x1D), 0x00)
162 #define TEAM_NAME_EDIT_RECT_COLOR \
163 		BUILD_COLOR (MAKE_RGB15 (0x14, 0x00, 0x14), 0x05)
164 #define TEAM_NAME_EDIT_CURS_COLOR \
165 		WHITE_COLOR
166 
167 #define SHIPBOX_TOPLEFT_COLOR_NORMAL \
168 		BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x09), 0x56)
169 #define SHIPBOX_BOTTOMRIGHT_COLOR_NORMAL \
170 		BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x0E), 0x54)
171 #define SHIPBOX_INTERIOR_COLOR_NORMAL \
172 		BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x0C), 0x55)
173 
174 #define SHIPBOX_TOPLEFT_COLOR_HILITE \
175 		BUILD_COLOR (MAKE_RGB15 (0x07, 0x00, 0x0C), 0x3E)
176 #define SHIPBOX_BOTTOMRIGHT_COLOR_HILITE \
177 		BUILD_COLOR (MAKE_RGB15 (0x0C, 0x00, 0x14), 0x3C)
178 #define SHIPBOX_INTERIOR_COLOR_HILITE \
179 		BUILD_COLOR (MAKE_RGB15 (0x0A, 0x00, 0x11), 0x3D)
180 
181 #define MELEE_STATUS_COLOR \
182 		BUILD_COLOR (MAKE_RGB15 (0x00, 0x14, 0x00), 0x02)
183 
184 
185 FRAME MeleeFrame;
186 		// Loaded from melee/melebkgd.ani
187 MELEE_STATE *pMeleeState;
188 
189 BOOLEAN DoMelee (MELEE_STATE *pMS);
190 static BOOLEAN DoEdit (MELEE_STATE *pMS);
191 static BOOLEAN DoConfirmSettings (MELEE_STATE *pMS);
192 
193 #define DTSHS_NORMAL   0
194 #define DTSHS_EDIT     1
195 #define DTSHS_SELECTED 2
196 #define DTSHS_REPAIR   4
197 #define DTSHS_BLOCKCUR 8
198 static BOOLEAN DrawTeamString (MELEE_STATE *pMS, COUNT side,
199 		COUNT HiLiteState, const char *str);
200 static void DrawFleetValue (MELEE_STATE *pMS, COUNT side, COUNT HiLiteState);
201 
202 static void Melee_UpdateView_fleetValue (MELEE_STATE *pMS, COUNT side);
203 static void Melee_UpdateView_ship (MELEE_STATE *pMS, COUNT side,
204 		FleetShipIndex index);
205 static void Melee_UpdateView_teamName (MELEE_STATE *pMS, COUNT side);
206 
207 
208 // These icons come from melee/melebkgd.ani
209 void
DrawMeleeIcon(COUNT which_icon)210 DrawMeleeIcon (COUNT which_icon)
211 {
212 	STAMP s;
213 
214 	s.origin.x = 0;
215 	s.origin.y = 0;
216 	s.frame = SetAbsFrameIndex (MeleeFrame, which_icon);
217 	DrawStamp (&s);
218 }
219 
220 static FleetShipIndex
GetShipIndex(BYTE row,BYTE col)221 GetShipIndex (BYTE row, BYTE col)
222 {
223 	return row * NUM_MELEE_COLUMNS + col;
224 }
225 
226 static BYTE
GetShipRow(FleetShipIndex index)227 GetShipRow (FleetShipIndex index)
228 {
229 	return index / NUM_MELEE_COLUMNS;
230 }
231 
232 static BYTE
GetShipColumn(int index)233 GetShipColumn (int index)
234 {
235 	return index % NUM_MELEE_COLUMNS;
236 }
237 
238 // Get the rectangle containing the ship slot for the specified side, row,
239 // and column.
240 void
GetShipBox(RECT * pRect,COUNT side,COUNT row,COUNT col)241 GetShipBox (RECT *pRect, COUNT side, COUNT row, COUNT col)
242 {
243 	pRect->corner.x = MELEE_X_OFFS
244 			+ (col * (MELEE_BOX_WIDTH + MELEE_BOX_SPACE));
245 	pRect->corner.y = MELEE_Y_OFFS
246 			+ (side * (MELEE_Y_OFFS + MELEE_BOX_SPACE
247 			+ (NUM_MELEE_ROWS * (MELEE_BOX_HEIGHT + MELEE_BOX_SPACE))))
248 			+ (row * (MELEE_BOX_HEIGHT + MELEE_BOX_SPACE));
249 	pRect->extent.width = MELEE_BOX_WIDTH;
250 	pRect->extent.height = MELEE_BOX_HEIGHT;
251 }
252 
253 static void
DrawShipBox(COUNT side,FleetShipIndex index,MeleeShip ship,BOOLEAN HiLite)254 DrawShipBox (COUNT side, FleetShipIndex index, MeleeShip ship, BOOLEAN HiLite)
255 {
256 	RECT r;
257 	BYTE row = GetShipRow (index);
258 	BYTE col = GetShipColumn (index);
259 
260 	GetShipBox (&r, side, row, col);
261 
262 	BatchGraphics ();
263 	if (HiLite)
264 		DrawStarConBox (&r, 1,
265 				SHIPBOX_TOPLEFT_COLOR_HILITE,
266 				SHIPBOX_BOTTOMRIGHT_COLOR_HILITE,
267 				(BOOLEAN)(ship != MELEE_NONE),
268 				SHIPBOX_INTERIOR_COLOR_HILITE);
269 	else
270 		DrawStarConBox (&r, 1,
271 				SHIPBOX_TOPLEFT_COLOR_NORMAL,
272 				SHIPBOX_BOTTOMRIGHT_COLOR_NORMAL,
273 				(BOOLEAN)(ship != MELEE_NONE),
274 				SHIPBOX_INTERIOR_COLOR_NORMAL);
275 
276 	if (ship != MELEE_NONE)
277 	{
278 		STAMP s;
279 		s.origin.x = r.corner.x + (r.extent.width >> 1);
280 		s.origin.y = r.corner.y + (r.extent.height >> 1);
281 		s.frame = GetShipMeleeIconsFromIndex (ship);
282 
283 		DrawStamp (&s);
284 	}
285 	UnbatchGraphics ();
286 }
287 
288 static void
ClearShipBox(COUNT side,FleetShipIndex index)289 ClearShipBox (COUNT side, FleetShipIndex index)
290 {
291 	RECT rect;
292 	BYTE row = GetShipRow (index);
293 	BYTE col = GetShipColumn (index);
294 
295 	GetShipBox (&rect, side, row, col);
296 	RepairMeleeFrame (&rect);
297 }
298 
299 static void
DrawShipBoxCurrent(MELEE_STATE * pMS,BOOLEAN HiLite)300 DrawShipBoxCurrent (MELEE_STATE *pMS, BOOLEAN HiLite)
301 {
302 	FleetShipIndex slotI = GetShipIndex (pMS->row, pMS->col);
303 	MeleeShip ship = MeleeSetup_getShip (pMS->meleeSetup, pMS->side, slotI);
304 	DrawShipBox (pMS->side, slotI, ship, HiLite);
305 }
306 
307 // Draw an image for one of the control method selection buttons.
308 static void
DrawControls(COUNT which_side,BOOLEAN HiLite)309 DrawControls (COUNT which_side, BOOLEAN HiLite)
310 {
311 	COUNT which_icon;
312 
313 	if (PlayerControl[which_side] & NETWORK_CONTROL)
314 	{
315 		DrawMeleeIcon (31 + (HiLite ? 1 : 0) + 2 * (1 - which_side));
316 				/* "Network Control" */
317 		return;
318 	}
319 
320 	if (PlayerControl[which_side] & HUMAN_CONTROL)
321 		which_icon = 0;
322 	else
323 	{
324 		switch (PlayerControl[which_side]
325 				& (STANDARD_RATING | GOOD_RATING | AWESOME_RATING))
326 		{
327 			case STANDARD_RATING:
328 				which_icon = 1;
329 				break;
330 			case GOOD_RATING:
331 				which_icon = 2;
332 				break;
333 			case AWESOME_RATING:
334 				which_icon = 3;
335 				break;
336 			default:
337 				// Should not happen. Satisfying compiler.
338 				which_icon = 0;
339 				break;
340 		}
341 	}
342 
343 	DrawMeleeIcon (1 + (8 * (1 - which_side)) + (HiLite ? 4 : 0) + which_icon);
344 }
345 
346 static void
DrawTeams(void)347 DrawTeams (void)
348 {
349 	COUNT side;
350 
351 	for (side = 0; side < NUM_SIDES; side++)
352 	{
353 		FleetShipIndex index;
354 
355 		DrawControls (side, FALSE);
356 
357 		for (index = 0; index < MELEE_FLEET_SIZE; index++)
358 		{
359 			MeleeShip ship = MeleeSetup_getShip(pMeleeState->meleeSetup,
360 					side, index);
361 			DrawShipBox (side, index, ship, FALSE);
362 		}
363 
364 		DrawTeamString (pMeleeState, side, DTSHS_NORMAL, NULL);
365 		DrawFleetValue (pMeleeState, side, DTSHS_NORMAL);
366 	}
367 }
368 
369 void
RepairMeleeFrame(const RECT * pRect)370 RepairMeleeFrame (const RECT *pRect)
371 {
372 	RECT r;
373 	CONTEXT OldContext;
374 	RECT OldRect;
375 	POINT oldOrigin;
376 
377 	r.corner.x = pRect->corner.x + SAFE_X;
378 	r.corner.y = pRect->corner.y + SAFE_Y;
379 	r.extent = pRect->extent;
380 	if (r.corner.y & 1)
381 	{
382 		--r.corner.y;
383 		++r.extent.height;
384 	}
385 
386 	OldContext = SetContext (SpaceContext);
387 	GetContextClipRect (&OldRect);
388 	SetContextClipRect (&r);
389 	// Offset the origin so that we draw the correct gfx in the cliprect
390 	oldOrigin = SetContextOrigin (MAKE_POINT (-r.corner.x + SAFE_X,
391 			-r.corner.y + SAFE_Y));
392 	BatchGraphics ();
393 
394 	DrawMeleeIcon (0);   /* Entire melee screen */
395 #ifdef NETPLAY
396 	DrawMeleeIcon (35);  /* "Net..." (top, not highlighted) */
397 	DrawMeleeIcon (37);  /* "Net..." (bottom, not highlighted) */
398 #endif
399 	DrawMeleeIcon (26);  /* "Battle!" (highlighted) */
400 
401 	DrawTeams ();
402 
403 	if (pMeleeState->MeleeOption == BUILD_PICK)
404 		DrawPickFrame (pMeleeState);
405 
406 	UnbatchGraphics ();
407 	SetContextOrigin (oldOrigin);
408 	SetContextClipRect (&OldRect);
409 	SetContext (OldContext);
410 }
411 
412 static void
RedrawMeleeFrame(void)413 RedrawMeleeFrame (void)
414 {
415 	RECT r;
416 
417 	r.corner.x = 0;
418 	r.corner.y = 0;
419 	r.extent.width = SCREEN_WIDTH;
420 	r.extent.height = SCREEN_HEIGHT;
421 
422 	RepairMeleeFrame (&r);
423 }
424 
425 static void
GetTeamStringRect(COUNT side,RECT * r)426 GetTeamStringRect (COUNT side, RECT *r)
427 {
428 	r->corner.x = MELEE_X_OFFS - 1;
429 	r->corner.y = (side + 1) * (MELEE_Y_OFFS
430 			+ ((MELEE_BOX_HEIGHT + MELEE_BOX_SPACE) * NUM_MELEE_ROWS + 2));
431 	r->extent.width = NUM_MELEE_COLUMNS * (MELEE_BOX_WIDTH + MELEE_BOX_SPACE)
432 			- 29;
433 	r->extent.height = 13;
434 }
435 
436 static void
GetFleetValueRect(COUNT side,RECT * r)437 GetFleetValueRect (COUNT side, RECT *r)
438 {
439 	r->corner.x = MELEE_X_OFFS
440 			+ NUM_MELEE_COLUMNS * (MELEE_BOX_WIDTH + MELEE_BOX_SPACE) - 30;
441 	r->corner.y = (side + 1) * (MELEE_Y_OFFS
442 			+ ((MELEE_BOX_HEIGHT + MELEE_BOX_SPACE) * NUM_MELEE_ROWS + 2));
443 	r->extent.width = 29;
444 	r->extent.height = 13;
445 }
446 
447 static void
DrawFleetValue(MELEE_STATE * pMS,COUNT side,COUNT HiLiteState)448 DrawFleetValue (MELEE_STATE *pMS, COUNT side, COUNT HiLiteState)
449 {
450 	RECT r;
451 	TEXT rtText;
452 	UNICODE buf[30];
453 	COUNT fleetValue;
454 
455 	GetFleetValueRect (side ,&r);
456 
457 	if (HiLiteState == DTSHS_REPAIR)
458 	{
459 		RepairMeleeFrame (&r);
460 		return;
461 	}
462 
463 	SetContextFont (MicroFont);
464 
465 	fleetValue = MeleeSetup_getFleetValue (pMS->meleeSetup, side);
466 	sprintf (buf, "%u", fleetValue);
467 	rtText.pStr = buf;
468 	rtText.align = ALIGN_RIGHT;
469 	rtText.CharCount = (COUNT)~0;
470 	rtText.baseline.y = r.corner.y + r.extent.height - 3;
471 	rtText.baseline.x = r.corner.x + r.extent.width;
472 
473 	SetContextForeGroundColor (!(HiLiteState & DTSHS_SELECTED)
474 			? TEAM_NAME_TEXT_COLOR : TEAM_NAME_EDIT_TEXT_COLOR);
475 	font_DrawText (&rtText);
476 }
477 
478 // If teamName == NULL, the team name is taken from pMS->meleeSetup
479 static BOOLEAN
DrawTeamString(MELEE_STATE * pMS,COUNT side,COUNT HiLiteState,const char * teamName)480 DrawTeamString (MELEE_STATE *pMS, COUNT side, COUNT HiLiteState,
481 		const char *teamName)
482 {
483 	RECT r;
484 	TEXT lfText;
485 
486 	GetTeamStringRect (side, &r);
487 	if (HiLiteState == DTSHS_REPAIR)
488 	{
489 		RepairMeleeFrame (&r);
490 		return TRUE;
491 	}
492 
493 	SetContextFont (MicroFont);
494 
495 	lfText.pStr = (teamName != NULL) ? teamName :
496 			MeleeSetup_getTeamName (pMS->meleeSetup, side);
497 	lfText.baseline.y = r.corner.y + r.extent.height - 3;
498 	lfText.baseline.x = r.corner.x + 1;
499 	lfText.align = ALIGN_LEFT;
500 	lfText.CharCount = strlen (lfText.pStr);
501 
502 	BatchGraphics ();
503 	if (!(HiLiteState & DTSHS_EDIT))
504 	{	// normal or selected state
505 		SetContextForeGroundColor (!(HiLiteState & DTSHS_SELECTED)
506 				? TEAM_NAME_TEXT_COLOR : TEAM_NAME_EDIT_TEXT_COLOR);
507 		font_DrawText (&lfText);
508 	}
509 	else
510 	{	// editing state
511 		COUNT i;
512 		RECT text_r;
513 		BYTE char_deltas[MAX_TEAM_CHARS];
514 		BYTE *pchar_deltas;
515 
516 		TextRect (&lfText, &text_r, char_deltas);
517 		if ((text_r.extent.width + 2) >= r.extent.width)
518 		{	// the text does not fit the input box size and so
519 			// will not fit when displayed later
520 			UnbatchGraphics ();
521 			// disallow the change
522 			return FALSE;
523 		}
524 
525 		text_r = r;
526 		SetContextForeGroundColor (TEAM_NAME_EDIT_RECT_COLOR);
527 		DrawFilledRectangle (&text_r);
528 
529 		// calculate the cursor position and draw it
530 		pchar_deltas = char_deltas;
531 		for (i = pMS->CurIndex; i > 0; --i)
532 			text_r.corner.x += (SIZE)*pchar_deltas++;
533 		if (pMS->CurIndex < lfText.CharCount) /* cursor mid-line */
534 			--text_r.corner.x;
535 		if (HiLiteState & DTSHS_BLOCKCUR)
536 		{	// Use block cursor for keyboardless systems
537 			if (pMS->CurIndex == lfText.CharCount)
538 			{	// cursor at end-line -- use insertion point
539 				text_r.extent.width = 1;
540 			}
541 			else if (pMS->CurIndex + 1 == lfText.CharCount)
542 			{	// extra pixel for last char margin
543 				text_r.extent.width = (SIZE)*pchar_deltas + 2;
544 			}
545 			else
546 			{	// normal mid-line char
547 				text_r.extent.width = (SIZE)*pchar_deltas + 1;
548 			}
549 		}
550 		else
551 		{	// Insertion point cursor
552 			text_r.extent.width = 1;
553 		}
554 		// position cursor within input field rect
555 		++text_r.corner.x;
556 		++text_r.corner.y;
557 		text_r.extent.height -= 2;
558 		SetContextForeGroundColor (TEAM_NAME_EDIT_CURS_COLOR);
559 		DrawFilledRectangle (&text_r);
560 
561 		SetContextForeGroundColor (BLACK_COLOR); // TEAM_NAME_EDIT_TEXT_COLOR);
562 		font_DrawText (&lfText);
563 	}
564 	UnbatchGraphics ();
565 
566 	return TRUE;
567 }
568 
569 #ifdef NETPLAY
570 // This function is generic. It should probably be moved to elsewhere.
571 static void
multiLineDrawText(TEXT * textIn,RECT * clipRect)572 multiLineDrawText (TEXT *textIn, RECT *clipRect) {
573 	RECT oldRect;
574 
575 	SIZE leading;
576 	TEXT text;
577 	SIZE lineWidth;
578 
579 	GetContextClipRect (&oldRect);
580 
581 	SetContextClipRect (clipRect);
582 	GetContextFontLeading (&leading);
583 
584 	text = *textIn;
585 	text.baseline.x = 1;
586 	text.baseline.y = 0;
587 
588 	if (clipRect->extent.width <= text.baseline.x)
589 		goto out;
590 
591 	lineWidth = clipRect->extent.width - text.baseline.x;
592 
593 	while (*text.pStr != '\0') {
594 		const char *nextLine;
595 
596 		text.baseline.y += leading;
597 		text.CharCount = (COUNT) ~0;
598 		getLineWithinWidth (&text, &nextLine, lineWidth, text.CharCount);
599 				// This will also fill in text->CharCount.
600 
601 		font_DrawText (&text);
602 
603 		text.pStr = nextLine;
604 	}
605 
606 out:
607 	SetContextClipRect (&oldRect);
608 }
609 
610 // Use an empty string to clear the status area.
611 static void
DrawMeleeStatusMessage(const char * message)612 DrawMeleeStatusMessage (const char *message)
613 {
614 	CONTEXT oldContext;
615 	RECT r;
616 
617 	oldContext = SetContext (SpaceContext);
618 
619 	r.corner.x = MELEE_STATUS_X_OFFS;
620 	r.corner.y = MELEE_STATUS_Y_OFFS;
621 	r.extent.width = MELEE_STATUS_WIDTH;
622 	r.extent.height = MELEE_STATUS_HEIGHT;
623 
624 	RepairMeleeFrame (&r);
625 
626 	if (message[0] != '\0')
627 	{
628 		TEXT lfText;
629 		lfText.pStr = message;
630 		lfText.align = ALIGN_LEFT;
631 		lfText.CharCount = (COUNT)~0;
632 
633 		SetContextFont (MicroFont);
634 		SetContextForeGroundColor (MELEE_STATUS_COLOR);
635 
636 		BatchGraphics ();
637 		multiLineDrawText (&lfText, &r);
638 		UnbatchGraphics ();
639 	}
640 
641 	SetContext (oldContext);
642 }
643 
644 static void
UpdateMeleeStatusMessage(ssize_t player)645 UpdateMeleeStatusMessage (ssize_t player)
646 {
647 	NetConnection *conn;
648 
649 	assert (player == -1 || (player >= 0 && player < NUM_PLAYERS));
650 
651 	if (player == -1)
652 	{
653 		DrawMeleeStatusMessage ("");
654 		return;
655 	}
656 
657 	conn = netConnections[player];
658 	if (conn == NULL)
659 	{
660 		DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 0));
661 				// "Unconnected. Press LEFT to connect."
662 		return;
663 	}
664 
665 	switch (NetConnection_getState (conn)) {
666 		case NetState_unconnected:
667 			DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 0));
668 					// "Unconnected. Press LEFT to connect."
669 			break;
670 		case NetState_connecting:
671 			if (NetConnection_getPeerOptions (conn)->isServer)
672 				DrawMeleeStatusMessage (
673 						GAME_STRING (NETMELEE_STRING_BASE + 1));
674 						// "Awaiting incoming connection...\n"
675 						// "Press RIGHT to cancel."
676 			else
677 				DrawMeleeStatusMessage (
678 						GAME_STRING (NETMELEE_STRING_BASE + 2));
679 						// "Attempting outgoing connection...\n"
680 						// "Press RIGHT to cancel."
681 			break;
682 		case NetState_init:
683 		case NetState_inSetup:
684 			DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 3));
685 					// "Connected. Press RIGHT to disconnect."
686 			break;
687 		default:
688 			DrawMeleeStatusMessage ("");
689 			break;
690 	}
691 }
692 #endif  /* NETPLAY */
693 
694 // XXX: this function is called when the current selection is blinking off.
695 static void
Deselect(BYTE opt)696 Deselect (BYTE opt)
697 {
698 	switch (opt)
699 	{
700 		case START_MELEE:
701 			DrawMeleeIcon (25);  /* "Battle!" (not highlighted) */
702 			break;
703 		case LOAD_TOP:
704 			DrawMeleeIcon (17); /* "Load" (top, not highlighted) */
705 			break;
706 		case LOAD_BOT:
707 			DrawMeleeIcon (22); /* "Load" (bottom, not highlighted) */
708 			break;
709 		case SAVE_TOP:
710 			DrawMeleeIcon (18);  /* "Save" (top, not highlighted) */
711 			break;
712 		case SAVE_BOT:
713 			DrawMeleeIcon (21);  /* "Save" (bottom, not highlighted) */
714 			break;
715 #ifdef NETPLAY
716 		case NET_TOP:
717 			DrawMeleeIcon (35);  /* "Net..." (top, not highlighted) */
718 			break;
719 		case NET_BOT:
720 			DrawMeleeIcon (37);  /* "Net..." (bottom, not highlighted) */
721 			break;
722 #endif
723 		case QUIT_BOT:
724 			DrawMeleeIcon (29);  /* "Quit" (not highlighted) */
725 			break;
726 		case CONTROLS_TOP:
727 		case CONTROLS_BOT:
728 		{
729 			COUNT which_side;
730 
731 			which_side = opt == CONTROLS_TOP ? 1 : 0;
732 			DrawControls (which_side, FALSE);
733 			break;
734 		}
735 		case EDIT_MELEE:
736 			if (pMeleeState->InputFunc == DoEdit)
737 			{
738 				if (pMeleeState->row < NUM_MELEE_ROWS)
739 					DrawShipBoxCurrent (pMeleeState, FALSE);
740 				else if (pMeleeState->CurIndex == MELEE_STATE_INDEX_DONE)
741 				{
742 					// Not currently editing the team name.
743 					DrawTeamString (pMeleeState, pMeleeState->side,
744 							DTSHS_NORMAL, NULL);
745 					DrawFleetValue (pMeleeState, pMeleeState->side,
746 							DTSHS_NORMAL);
747 				}
748 			}
749 			break;
750 		case BUILD_PICK:
751 			DrawPickIcon (pMeleeState->currentShip, true);
752 			break;
753 	}
754 }
755 
756 // XXX: this function is called when the current selection is blinking off.
757 static void
Select(BYTE opt)758 Select (BYTE opt)
759 {
760 	switch (opt)
761 	{
762 		case START_MELEE:
763 			DrawMeleeIcon (26);  /* "Battle!" (highlighted) */
764 			break;
765 		case LOAD_TOP:
766 			DrawMeleeIcon (19); /* "Load" (top, highlighted) */
767 			break;
768 		case LOAD_BOT:
769 			DrawMeleeIcon (24); /* "Load" (bottom, highlighted) */
770 			break;
771 		case SAVE_TOP:
772 			DrawMeleeIcon (20);  /* "Save" (top; highlighted) */
773 			break;
774 		case SAVE_BOT:
775 			DrawMeleeIcon (23);  /* "Save" (bottom; highlighted) */
776 			break;
777 #ifdef NETPLAY
778 		case NET_TOP:
779 			DrawMeleeIcon (36);  /* "Net..." (top; highlighted) */
780 			break;
781 		case NET_BOT:
782 			DrawMeleeIcon (38);  /* "Net..." (bottom; highlighted) */
783 			break;
784 #endif
785 		case QUIT_BOT:
786 			DrawMeleeIcon (30);  /* "Quit" (highlighted) */
787 			break;
788 		case CONTROLS_TOP:
789 		case CONTROLS_BOT:
790 		{
791 			COUNT which_side;
792 
793 			which_side = (opt == CONTROLS_TOP) ? 1 : 0;
794 			DrawControls (which_side, TRUE);
795 			break;
796 		}
797 		case EDIT_MELEE:
798 			if (pMeleeState->InputFunc == DoEdit)
799 			{
800 				if (pMeleeState->row < NUM_MELEE_ROWS)
801 					DrawShipBoxCurrent (pMeleeState, TRUE);
802 				else if (pMeleeState->CurIndex == MELEE_STATE_INDEX_DONE)
803 				{
804 					// Not currently editing the team name.
805 					DrawTeamString (pMeleeState, pMeleeState->side,
806 							DTSHS_SELECTED, NULL);
807 					DrawFleetValue (pMeleeState, pMeleeState->side,
808 							DTSHS_SELECTED);
809 				}
810 			}
811 			break;
812 		case BUILD_PICK:
813 			DrawPickIcon (pMeleeState->currentShip, false);
814 			break;
815 	}
816 }
817 
818 void
Melee_flashSelection(MELEE_STATE * pMS)819 Melee_flashSelection (MELEE_STATE *pMS)
820 {
821 #define FLASH_RATE (ONE_SECOND / 9)
822 	static TimeCount NextTime = 0;
823 	static bool select = false;
824 	TimeCount Now = GetTimeCounter ();
825 
826 	if (Now >= NextTime)
827 	{
828 		CONTEXT OldContext;
829 
830 		NextTime = Now + FLASH_RATE;
831 		select = !select;
832 
833 		OldContext = SetContext (SpaceContext);
834 		if (select)
835 			Select (pMS->MeleeOption);
836 		else
837 			Deselect (pMS->MeleeOption);
838 		SetContext (OldContext);
839 	}
840 }
841 
842 static void
InitMelee(MELEE_STATE * pMS)843 InitMelee (MELEE_STATE *pMS)
844 {
845 	RECT r;
846 
847 	SetContext (SpaceContext);
848 	SetContextFGFrame (Screen);
849 	SetContextClipRect (NULL);
850 	SetContextBackGroundColor (BLACK_COLOR);
851 	ClearDrawable ();
852 	r.corner.x = SAFE_X;
853 	r.corner.y = SAFE_Y;
854 	r.extent.width = SCREEN_WIDTH - (SAFE_X * 2);
855 	r.extent.height = SCREEN_HEIGHT - (SAFE_Y * 2);
856 	SetContextClipRect (&r);
857 
858 	r.corner.x = r.corner.y = 0;
859 	RedrawMeleeFrame ();
860 
861 	(void) pMS;
862 }
863 
864 void
DrawMeleeShipStrings(MELEE_STATE * pMS,MeleeShip NewStarShip)865 DrawMeleeShipStrings (MELEE_STATE *pMS, MeleeShip NewStarShip)
866 {
867 	RECT r, OldRect;
868 	CONTEXT OldContext;
869 
870 	OldContext = SetContext (StatusContext);
871 	GetContextClipRect (&OldRect);
872 	r = OldRect;
873 	r.corner.x += ((SAFE_X << 1) - 32) + MENU_X_OFFS;
874 	r.corner.y += 76;
875 	r.extent.height = SHIP_INFO_HEIGHT;
876 	SetContextClipRect (&r);
877 	BatchGraphics ();
878 
879 	if (NewStarShip == MELEE_NONE)
880 	{
881 		RECT r;
882 		TEXT t;
883 
884 		ClearShipStatus (0);
885 		SetContextFont (StarConFont);
886 		r.corner.x = 3;
887 		r.corner.y = 4;
888 		r.extent.width = 57;
889 		r.extent.height = 60;
890 		SetContextForeGroundColor (BLACK_COLOR);
891 		DrawRectangle (&r);
892 		t.baseline.x = STATUS_WIDTH >> 1;
893 		t.baseline.y = 32;
894 		t.align = ALIGN_CENTER;
895 		if (pMS->row < NUM_MELEE_ROWS)
896 		{
897 			// A ship is selected (or an empty fleet position).
898 			t.pStr = GAME_STRING (MELEE_STRING_BASE + 0);  // "Empty"
899 			t.CharCount = (COUNT)~0;
900 			font_DrawText (&t);
901 			t.pStr = GAME_STRING (MELEE_STRING_BASE + 1);  // "Slot"
902 		}
903 		else
904 		{
905 			// The team name is selected.
906 			t.pStr = GAME_STRING (MELEE_STRING_BASE + 2);  // "Team"
907 			t.CharCount = (COUNT)~0;
908 			font_DrawText (&t);
909 			t.pStr = GAME_STRING (MELEE_STRING_BASE + 3);  // "Name"
910 		}
911 		t.baseline.y += TINY_TEXT_HEIGHT;
912 		t.CharCount = (COUNT)~0;
913 		font_DrawText (&t);
914 	}
915 	else
916 	{
917 		HMASTERSHIP hMasterShip;
918 		MASTER_SHIP_INFO *MasterPtr;
919 
920 		hMasterShip = GetStarShipFromIndex (&master_q, NewStarShip);
921 		MasterPtr = LockMasterShip (&master_q, hMasterShip);
922 
923 		InitShipStatus (&MasterPtr->ShipInfo, NULL, NULL);
924 
925 		UnlockMasterShip (&master_q, hMasterShip);
926 	}
927 
928 	UnbatchGraphics ();
929 	SetContextClipRect (&OldRect);
930 	SetContext (OldContext);
931 }
932 
933 // Set the currently displayed ship to the ship for the slot indicated by
934 // pMS->row and pMS->col.
935 static void
UpdateCurrentShip(MELEE_STATE * pMS)936 UpdateCurrentShip (MELEE_STATE *pMS)
937 {
938 	if (pMS->row == NUM_MELEE_ROWS)
939 	{
940 		// The team name is selected.
941 		pMS->currentShip = MELEE_NONE;
942 	}
943 	else
944 	{
945 		FleetShipIndex slotNr = GetShipIndex (pMS->row, pMS->col);
946 		pMS->currentShip =
947 				MeleeSetup_getShip (pMS->meleeSetup, pMS->side, slotNr);
948 	}
949 
950 	DrawMeleeShipStrings (pMS, pMS->currentShip);
951 }
952 
953 // returns (COUNT) ~0 for an invalid ship.
954 COUNT
GetShipValue(MeleeShip StarShip)955 GetShipValue (MeleeShip StarShip)
956 {
957 	COUNT val;
958 
959 	if (StarShip == MELEE_NONE)
960 		return 0;
961 
962 	val = GetShipCostFromIndex (StarShip);
963 	if (val == 0)
964 		val = (COUNT)~0;
965 
966 	return val;
967 }
968 
969 static void
DeleteCurrentShip(MELEE_STATE * pMS)970 DeleteCurrentShip (MELEE_STATE *pMS)
971 {
972 	FleetShipIndex slotI = GetShipIndex (pMS->row, pMS->col);
973 	Melee_LocalChange_ship (pMS, pMS->side, slotI, MELEE_NONE);
974 }
975 
976 static bool
isShipSlotSelected(MELEE_STATE * pMS,COUNT side,FleetShipIndex index)977 isShipSlotSelected (MELEE_STATE *pMS, COUNT side, FleetShipIndex index)
978 {
979 	if (pMS->MeleeOption != EDIT_MELEE)
980 		return false;
981 
982 	if (pMS->side != side)
983 		return false;
984 
985 	return (index == GetShipIndex (pMS->row, pMS->col));
986 }
987 
988 static void
AdvanceCursor(MELEE_STATE * pMS)989 AdvanceCursor (MELEE_STATE *pMS)
990 {
991 	++pMS->col;
992 	if (pMS->col == NUM_MELEE_COLUMNS)
993 	{
994 		++pMS->row;
995 		if (pMS->row < NUM_MELEE_ROWS)
996 			pMS->col = 0;
997 		else
998 		{
999 			pMS->col = NUM_MELEE_COLUMNS - 1;
1000 			pMS->row = NUM_MELEE_ROWS - 1;
1001 		}
1002 	}
1003 }
1004 
1005 static BOOLEAN
OnTeamNameChange(TEXTENTRY_STATE * pTES)1006 OnTeamNameChange (TEXTENTRY_STATE *pTES)
1007 {
1008 	MELEE_STATE *pMS = (MELEE_STATE*) pTES->CbParam;
1009 	BOOLEAN ret;
1010 	COUNT hl = DTSHS_EDIT;
1011 
1012 	pMS->CurIndex = pTES->CursorPos;
1013 	if (pTES->JoystickMode)
1014 		hl |= DTSHS_BLOCKCUR;
1015 
1016 	ret = DrawTeamString (pMS, pMS->side, hl, pTES->BaseStr);
1017 
1018 	return ret;
1019 }
1020 
1021 static BOOLEAN
TeamNameFrameCallback(TEXTENTRY_STATE * pTES)1022 TeamNameFrameCallback (TEXTENTRY_STATE *pTES)
1023 {
1024 #ifdef NETPLAY
1025 	// Process incoming packets, so that remote changes are displayed
1026 	// while we are editing the team name.
1027 	// The team name itself isn't modified visually due to remote changes
1028 	// while it is being edited.
1029 	netInput ();
1030 #endif
1031 
1032 	(void) pTES;
1033 
1034 	return TRUE;
1035 			// Keep editing
1036 }
1037 
1038 static void
BuildPickShipPopup(MELEE_STATE * pMS)1039 BuildPickShipPopup (MELEE_STATE *pMS)
1040 {
1041 	bool buildOk;
1042 
1043 	pMS->MeleeOption = BUILD_PICK;
1044 
1045 	buildOk = BuildPickShip (pMS);
1046 	if (buildOk)
1047 	{
1048 		// A ship has been selected.
1049 		// Add the currently selected ship to the fleet.
1050 		FleetShipIndex index = GetShipIndex (pMS->row, pMS->col);
1051 		Melee_LocalChange_ship (pMS, pMS->side, index, pMS->currentShip);
1052 		AdvanceCursor (pMS);
1053 	}
1054 
1055 	pMS->MeleeOption = EDIT_MELEE;
1056 			// Must set this before the call to RepairMeleeFrame(), so that
1057 			// it will not redraw the BuildPickFrame.
1058 
1059 	{
1060 		RECT r;
1061 
1062 		GetBuildPickFrameRect (&r);
1063 		RepairMeleeFrame (&r);
1064 	}
1065 
1066 	UpdateCurrentShip (pMS);
1067 	pMS->InputFunc = DoEdit;
1068 }
1069 
1070 static BOOLEAN
DoEdit(MELEE_STATE * pMS)1071 DoEdit (MELEE_STATE *pMS)
1072 {
1073 	DWORD TimeIn = GetTimeCounter ();
1074 
1075 	/* Cancel any presses of the Pause key. */
1076 	GamePaused = FALSE;
1077 
1078 	if (GLOBAL (CurrentActivity) & CHECK_ABORT)
1079 		return FALSE;
1080 
1081 	SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT | MENU_SOUND_DELETE);
1082 	if (!pMS->Initialized)
1083 	{
1084 		UpdateCurrentShip (pMS);
1085 		pMS->Initialized = TRUE;
1086 		pMS->InputFunc = DoEdit;
1087 		return TRUE;
1088 	}
1089 
1090 #ifdef NETPLAY
1091 	netInput ();
1092 #endif
1093 	if ((pMS->row < NUM_MELEE_ROWS || pMS->currentShip == MELEE_NONE)
1094 			&& (PulsedInputState.menu[KEY_MENU_CANCEL]
1095 			|| (PulsedInputState.menu[KEY_MENU_RIGHT]
1096 			&& (pMS->col == NUM_MELEE_COLUMNS - 1
1097 			|| pMS->row == NUM_MELEE_ROWS))))
1098 	{
1099 		// Done editing the teams.
1100 		Deselect (EDIT_MELEE);
1101 		pMS->currentShip = MELEE_NONE;
1102 		pMS->MeleeOption = START_MELEE;
1103 		pMS->InputFunc = DoMelee;
1104 		pMS->LastInputTime = GetTimeCounter ();
1105 	}
1106 	else if (pMS->row < NUM_MELEE_ROWS
1107 			&& PulsedInputState.menu[KEY_MENU_SELECT])
1108 	{
1109 		// Show a popup to add a new ship to the current team.
1110 		Deselect (EDIT_MELEE);
1111 		BuildPickShipPopup (pMS);
1112 	}
1113 	else if (pMS->row < NUM_MELEE_ROWS
1114 			&& PulsedInputState.menu[KEY_MENU_SPECIAL])
1115 	{
1116 		// TODO: this is a stub; Should we display a ship spin?
1117 		Deselect (EDIT_MELEE);
1118 		if (pMS->currentShip != MELEE_NONE)
1119 		{
1120 			// Do something with pMS->currentShip here
1121 		}
1122 	}
1123 	else if (pMS->row < NUM_MELEE_ROWS &&
1124 			PulsedInputState.menu[KEY_MENU_DELETE])
1125 	{
1126 		// Remove the currently selected ship from the current team.
1127 		Deselect (EDIT_MELEE);
1128 		DeleteCurrentShip (pMS);
1129 		AdvanceCursor (pMS);
1130 		UpdateCurrentShip (pMS);
1131 	}
1132 	else
1133 	{
1134 		COUNT side = pMS->side;
1135 		COUNT row = pMS->row;
1136 		COUNT col = pMS->col;
1137 
1138 		if (row == NUM_MELEE_ROWS)
1139 		{
1140 			// Edit the name of the current team.
1141 			if (PulsedInputState.menu[KEY_MENU_SELECT])
1142 			{
1143 				TEXTENTRY_STATE tes;
1144 				char buf[MAX_TEAM_CHARS + 1];
1145 
1146 				// going to enter text
1147 				pMS->CurIndex = 0;
1148 				DrawTeamString (pMS, pMS->side, DTSHS_EDIT, NULL);
1149 
1150 				strncpy (buf, MeleeSetup_getTeamName (
1151 						pMS->meleeSetup, pMS->side), MAX_TEAM_CHARS);
1152 				buf[MAX_TEAM_CHARS] = '\0';
1153 
1154 				tes.Initialized = FALSE;
1155 				tes.BaseStr = buf;
1156 				tes.CursorPos = 0;
1157 				tes.MaxSize = MAX_TEAM_CHARS + 1;
1158 				tes.CbParam = pMS;
1159 				tes.ChangeCallback = OnTeamNameChange;
1160 				tes.FrameCallback = TeamNameFrameCallback;
1161 				DoTextEntry (&tes);
1162 
1163 				// done entering
1164 				pMS->CurIndex = MELEE_STATE_INDEX_DONE;
1165 				if (!tes.Success ||
1166 						!Melee_LocalChange_teamName (pMS, pMS->side, buf)) {
1167 					// The team name was not changed, so it was not redrawn.
1168 					// However, because we now leave edit mode, we still
1169 					// need to redraw.
1170 					Melee_UpdateView_teamName (pMS, pMS->side);
1171 				}
1172 
1173 				return TRUE;
1174 			}
1175 		}
1176 
1177 		{
1178 			if (PulsedInputState.menu[KEY_MENU_LEFT])
1179 			{
1180 				if (col > 0)
1181 					--col;
1182 			}
1183 			else if (PulsedInputState.menu[KEY_MENU_RIGHT])
1184 			{
1185 				if (col < NUM_MELEE_COLUMNS - 1)
1186 					++col;
1187 			}
1188 
1189 			if (PulsedInputState.menu[KEY_MENU_UP])
1190 			{
1191 				if (row-- == 0)
1192 				{
1193 					if (side == 0)
1194 						row = 0;
1195 					else
1196 					{
1197 						row = NUM_MELEE_ROWS;
1198 						side = !side;
1199 					}
1200 				}
1201 			}
1202 			else if (PulsedInputState.menu[KEY_MENU_DOWN])
1203 			{
1204 				if (row++ == NUM_MELEE_ROWS)
1205 				{
1206 					if (side == 1)
1207 						row = NUM_MELEE_ROWS;
1208 					else
1209 					{
1210 						row = 0;
1211 						side = !side;
1212 					}
1213 				}
1214 			}
1215 		}
1216 
1217 		if (col != pMS->col || row != pMS->row || side != pMS->side)
1218 		{
1219 			Deselect (EDIT_MELEE);
1220 			pMS->side = side;
1221 			pMS->row = row;
1222 			pMS->col = col;
1223 
1224 			UpdateCurrentShip (pMS);
1225 		}
1226 	}
1227 
1228 #ifdef NETPLAY
1229 	flushPacketQueues ();
1230 #endif
1231 
1232 	Melee_flashSelection (pMS);
1233 
1234 	SleepThreadUntil (TimeIn + ONE_SECOND / 30);
1235 
1236 	return TRUE;
1237 }
1238 
1239 #ifdef NETPLAY
1240 // Returns -1 if a connection has been aborted.
1241 static ssize_t
numPlayersReady(void)1242 numPlayersReady (void)
1243 {
1244 	size_t player;
1245 	size_t numDone;
1246 
1247 	numDone = 0;
1248 	for (player = 0; player < NUM_PLAYERS; player++)
1249 	{
1250 		if (!(PlayerControl[player] & NETWORK_CONTROL))
1251 		{
1252 			numDone++;
1253 			continue;
1254 		}
1255 
1256 		{
1257 			NetConnection *conn;
1258 
1259 			conn = netConnections[player];
1260 
1261 			if (conn == NULL || !NetConnection_isConnected (conn))
1262 				return -1;
1263 
1264 			if (NetConnection_getState (conn) > NetState_inSetup)
1265 				numDone++;
1266 		}
1267 	}
1268 
1269 	return numDone;
1270 }
1271 #endif  /* NETPLAY */
1272 
1273 // The player has pressed "Start Game", and all Network players are
1274 // connected. We're now just waiting for the confirmation of the other
1275 // party.
1276 // When the other party changes something in the settings, the confirmation
1277 // is cancelled.
1278 static BOOLEAN
DoConfirmSettings(MELEE_STATE * pMS)1279 DoConfirmSettings (MELEE_STATE *pMS)
1280 {
1281 #ifdef NETPLAY
1282 	ssize_t numDone;
1283 #endif
1284 
1285 	/* Cancel any presses of the Pause key. */
1286 	GamePaused = FALSE;
1287 
1288 	if (PulsedInputState.menu[KEY_MENU_CANCEL])
1289 	{
1290 		// The connection is explicitely cancelled, locally.
1291 		pMS->InputFunc = DoMelee;
1292 #ifdef NETPLAY
1293 		cancelConfirmations ();
1294 		DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 4));
1295 				// "Confirmation cancelled. Press FIRE to reconfirm."
1296 #endif
1297 		return TRUE;
1298 	}
1299 
1300 	if (PulsedInputState.menu[KEY_MENU_LEFT] ||
1301 			PulsedInputState.menu[KEY_MENU_UP] ||
1302 			PulsedInputState.menu[KEY_MENU_DOWN])
1303 	{
1304 		// The player moves the cursor; cancel the confirmation.
1305 		pMS->InputFunc = DoMelee;
1306 #ifdef NETPLAY
1307 		cancelConfirmations ();
1308 		DrawMeleeStatusMessage ("");
1309 #endif
1310 		return DoMelee (pMS);
1311 				// Let the pressed keys take effect immediately.
1312 	}
1313 
1314 #ifndef NETPLAY
1315 	pMS->InputFunc = DoMelee;
1316 	SeedRandomNumbers ();
1317 	pMS->meleeStarted = TRUE;
1318 	StartMelee (pMS);
1319 	pMS->meleeStarted = FALSE;
1320 	if (GLOBAL (CurrentActivity) & CHECK_ABORT)
1321 		return FALSE;
1322 	return TRUE;
1323 #else
1324 	closeDisconnectedConnections ();
1325 	netInput ();
1326 	SleepThread (ONE_SECOND / 120);
1327 
1328 	numDone = numPlayersReady ();
1329 	if (numDone == -1)
1330 	{
1331 		// Connection aborted
1332 		cancelConfirmations ();
1333 		flushPacketQueues ();
1334 		pMS->InputFunc = DoMelee;
1335 		return TRUE;
1336 	}
1337 	else if (numDone != NUM_SIDES)
1338 	{
1339 		// Still waiting for some confirmation.
1340 		return TRUE;
1341 	}
1342 
1343 	// All sides have confirmed.
1344 
1345 	// Send our own prefered frame delay.
1346 	Netplay_NotifyAll_inputDelay (netplayOptions.inputDelay);
1347 
1348 	// Synchronise the RNGs:
1349 	{
1350 		COUNT player;
1351 
1352 		for (player = 0; player < NUM_PLAYERS; player++)
1353 		{
1354 			NetConnection *conn;
1355 
1356 			if (!(PlayerControl[player] & NETWORK_CONTROL))
1357 				continue;
1358 
1359 			conn = netConnections[player];
1360 			assert (conn != NULL);
1361 
1362 			if (!NetConnection_isConnected (conn))
1363 				continue;
1364 
1365 			if (NetConnection_getDiscriminant (conn))
1366 				Netplay_Notify_seedRandom (conn, SeedRandomNumbers ());
1367 		}
1368 		flushPacketQueues ();
1369 	}
1370 
1371 	{
1372 		// One side will send the seed followed by 'Done' and wait
1373 		// for the other side to report 'Done'.
1374 		// The other side will report 'Done' and will wait for the other
1375 		// side to report 'Done', but before the reception of 'Done'
1376 		// it will have received the seed.
1377 		bool allOk = negotiateReadyConnections (true, NetState_interBattle);
1378 		if (!allOk)
1379 			return FALSE;
1380 	}
1381 
1382 	// The maximum value for all connections is used.
1383 	{
1384 		bool ok = setupInputDelay (netplayOptions.inputDelay);
1385 		if (!ok)
1386 			return FALSE;
1387 	}
1388 
1389 	pMS->InputFunc = DoMelee;
1390 
1391 	StartMelee (pMS);
1392 	if (GLOBAL (CurrentActivity) & CHECK_ABORT)
1393 		return FALSE;
1394 
1395 	return TRUE;
1396 #endif  /* defined (NETPLAY) */
1397 }
1398 
1399 static void
LoadMeleeInfo(MELEE_STATE * pMS)1400 LoadMeleeInfo (MELEE_STATE *pMS)
1401 {
1402 	BuildPickMeleeFrame ();
1403 	MeleeFrame = CaptureDrawable (LoadGraphic (MELEE_SCREEN_PMAP_ANIM));
1404 	BuildBuildPickFrame ();
1405 
1406 	InitSpace ();
1407 
1408 	LoadTeamList (pMS);
1409 }
1410 
1411 static void
FreeMeleeInfo(MELEE_STATE * pMS)1412 FreeMeleeInfo (MELEE_STATE *pMS)
1413 {
1414 	DestroyDirEntryTable (ReleaseDirEntryTable (pMS->load.dirEntries));
1415 	pMS->load.dirEntries = 0;
1416 
1417 	if (pMS->hMusic)
1418 	{
1419 		DestroyMusic (pMS->hMusic);
1420 		pMS->hMusic = 0;
1421 	}
1422 
1423 	UninitSpace ();
1424 
1425 	DestroyPickMeleeFrame ();
1426 	DestroyDrawable (ReleaseDrawable (MeleeFrame));
1427 	MeleeFrame = 0;
1428 	DestroyBuildPickFrame ();
1429 
1430 #ifdef NETPLAY
1431 	closeAllConnections ();
1432 	// Clear the input delay in case we will go into the full game later.
1433 	// Must be done after the net connections are closed.
1434 	setupInputDelay (0);
1435 #endif
1436 }
1437 
1438 static void
BuildAndDrawShipList(MELEE_STATE * pMS)1439 BuildAndDrawShipList (MELEE_STATE *pMS)
1440 {
1441 	FillPickMeleeFrame (pMS->meleeSetup);
1442 			// XXX TODO: This also builds the race_q for each player.
1443 			// This should be split off.
1444 }
1445 
1446 static void
StartMelee(MELEE_STATE * pMS)1447 StartMelee (MELEE_STATE *pMS)
1448 {
1449 	{
1450 		FadeMusic (0, ONE_SECOND / 2);
1451 		SleepThreadUntil (FadeScreen (FadeAllToBlack, ONE_SECOND / 2)
1452 				+ ONE_SECOND / 60);
1453 		FlushColorXForms ();
1454 		StopMusic ();
1455 	}
1456 	FadeMusic (NORMAL_VOLUME, 0);
1457 	if (pMS->hMusic)
1458 	{
1459 		DestroyMusic (pMS->hMusic);
1460 		pMS->hMusic = 0;
1461 	}
1462 
1463 	do
1464 	{
1465 		if (!SetPlayerInputAll ())
1466 			break;
1467 		BuildAndDrawShipList (pMS);
1468 
1469 		WaitForSoundEnd (TFBSOUND_WAIT_ALL);
1470 
1471 		load_gravity_well ((BYTE)((COUNT)TFB_Random () %
1472 					NUMBER_OF_PLANET_TYPES));
1473 		Battle (NULL);
1474 		free_gravity_well ();
1475 		ClearPlayerInputAll ();
1476 
1477 		if (GLOBAL (CurrentActivity) & CHECK_ABORT)
1478 			return;
1479 
1480 		SleepThreadUntil (FadeScreen (FadeAllToBlack, ONE_SECOND / 2)
1481 				+ ONE_SECOND / 60);
1482 		FlushColorXForms ();
1483 
1484 	} while (0 /* !(GLOBAL (CurrentActivity) & CHECK_ABORT) */);
1485 	GLOBAL (CurrentActivity) = SUPER_MELEE;
1486 
1487 	pMS->Initialized = FALSE;
1488 }
1489 
1490 static void
StartMeleeButtonPressed(MELEE_STATE * pMS)1491 StartMeleeButtonPressed (MELEE_STATE *pMS)
1492 {
1493 	// Either fleet must at least have one ship.
1494 	if (MeleeSetup_getFleetValue (pMS->meleeSetup, 0) == 0 ||
1495 			MeleeSetup_getFleetValue (pMS->meleeSetup, 1) == 0)
1496 	{
1497 		PlayMenuSound (MENU_SOUND_FAILURE);
1498 		return;
1499 	}
1500 
1501 #ifdef NETPLAY
1502 	if ((PlayerControl[0] & NETWORK_CONTROL) &&
1503 			(PlayerControl[1] & NETWORK_CONTROL))
1504 	{
1505 		DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 32));
1506 				// "Only one side at a time can be network controlled."
1507 		return;
1508 	}
1509 
1510 	if (((PlayerControl[0] & NETWORK_CONTROL) &&
1511 			(PlayerControl[1] & COMPUTER_CONTROL)) ||
1512 			((PlayerControl[0] & COMPUTER_CONTROL) &&
1513 			(PlayerControl[1] & NETWORK_CONTROL)))
1514 	{
1515 		DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 33));
1516 				// "Netplay with a computer-controlled side is currently
1517 				// not possible."
1518 		return;
1519 	}
1520 
1521 	// Check whether all network parties are ready;
1522 	{
1523 		COUNT player;
1524 		bool netReady = true;
1525 
1526 		// We collect all error conditions, instead of only reporting
1527 		// the first one.
1528 		for (player = 0; player < NUM_PLAYERS; player++)
1529 		{
1530 			NetConnection *conn;
1531 
1532 			if (!(PlayerControl[player] & NETWORK_CONTROL))
1533 				continue;
1534 
1535 			conn = netConnections[player];
1536 			if (conn == NULL || !NetConnection_isConnected (conn))
1537 			{
1538 				// Connection for player not established.
1539 				netReady = false;
1540 				if (player == 0)
1541 					DrawMeleeStatusMessage (
1542 							GAME_STRING (NETMELEE_STRING_BASE + 5));
1543 							// "Connection for bottom player not "
1544 							// "established."
1545 				else
1546 					DrawMeleeStatusMessage (
1547 							GAME_STRING (NETMELEE_STRING_BASE + 6));
1548 							// "Connection for top player not "
1549 							// "established."
1550 			}
1551 			else if (NetConnection_getState (conn) != NetState_inSetup)
1552 			{
1553 				// This side may be in the setup, but the network connection
1554 				// is not in a state that setup information can be sent.
1555 				netReady = false;
1556 				if (player == 0)
1557 					DrawMeleeStatusMessage (
1558 							GAME_STRING (NETMELEE_STRING_BASE + 14));
1559 							// "Connection for bottom player not ready."
1560 				else
1561 					DrawMeleeStatusMessage (
1562 							GAME_STRING (NETMELEE_STRING_BASE + 15));
1563 							// "Connection for top player not ready."
1564 
1565 			}
1566 		}
1567 		if (!netReady)
1568 		{
1569 			PlayMenuSound (MENU_SOUND_FAILURE);
1570 			return;
1571 		}
1572 
1573 		if (numPlayersReady () != NUM_PLAYERS)
1574 			DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 7));
1575 					// "Waiting for remote confirmation."
1576 		confirmConnections ();
1577 	}
1578 #endif
1579 
1580 	pMS->InputFunc = DoConfirmSettings;
1581 }
1582 
1583 #ifdef NETPLAY
1584 
1585 static BOOLEAN
DoConnectingDialog(MELEE_STATE * pMS)1586 DoConnectingDialog (MELEE_STATE *pMS)
1587 {
1588 	DWORD TimeIn = GetTimeCounter ();
1589 	COUNT which_side = (pMS->MeleeOption == NET_TOP) ? 1 : 0;
1590 	NetConnection *conn;
1591 
1592 	/* Cancel any presses of the Pause key. */
1593 	GamePaused = FALSE;
1594 
1595 	if (GLOBAL (CurrentActivity) & CHECK_ABORT)
1596 		return FALSE;
1597 
1598 	SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE);
1599 	if (!pMS->Initialized)
1600 	{
1601 		RECT r;
1602 		FONT oldfont;
1603 		Color oldcolor;
1604 		TEXT t;
1605 
1606 		// Build a network connection.
1607 		if (netConnections[which_side] != NULL)
1608 			closePlayerNetworkConnection (which_side);
1609 
1610 		pMS->Initialized = TRUE;
1611 		conn = openPlayerNetworkConnection (which_side, pMS);
1612 		pMS->InputFunc = DoConnectingDialog;
1613 
1614 		/* Draw the dialog box here */
1615 		oldfont = SetContextFont (StarConFont);
1616 		oldcolor = SetContextForeGroundColor (BLACK_COLOR);
1617 		BatchGraphics ();
1618 		r.extent.width = 200;
1619 		r.extent.height = 30;
1620 		r.corner.x = (SCREEN_WIDTH - r.extent.width) >> 1;
1621 		r.corner.y = (SCREEN_HEIGHT - r.extent.height) >> 1;
1622 		DrawShadowedBox (&r, SHADOWBOX_BACKGROUND_COLOR,
1623 				SHADOWBOX_DARK_COLOR, SHADOWBOX_MEDIUM_COLOR);
1624 
1625 		if (NetConnection_getPeerOptions (conn)->isServer)
1626 		{
1627 			t.pStr = GAME_STRING (NETMELEE_STRING_BASE + 1);
1628 					/* "Awaiting incoming connection */
1629 		}
1630 		else
1631 		{
1632 			t.pStr = GAME_STRING (NETMELEE_STRING_BASE + 2);
1633 					/* "Awaiting outgoing connection */
1634 		}
1635 		t.baseline.y = r.corner.y + 10;
1636 		t.baseline.x = SCREEN_WIDTH >> 1;
1637 		t.align = ALIGN_CENTER;
1638 		t.CharCount = ~0;
1639 		font_DrawText (&t);
1640 
1641 		t.pStr = GAME_STRING (NETMELEE_STRING_BASE + 18);
1642 				/* "Press SPACE to cancel" */
1643 		t.baseline.y += 16;
1644 		font_DrawText (&t);
1645 
1646 		// Restore original graphics
1647 		SetContextFont (oldfont);
1648 		SetContextForeGroundColor (oldcolor);
1649 		UnbatchGraphics ();
1650 	}
1651 
1652 	netInput ();
1653 
1654 	if (PulsedInputState.menu[KEY_MENU_CANCEL])
1655 	{
1656 		// Terminate a network connection.
1657 		if (netConnections[which_side] != NULL) {
1658 			closePlayerNetworkConnection (which_side);
1659 			UpdateMeleeStatusMessage (which_side);
1660 		}
1661 		RedrawMeleeFrame ();
1662 		pMS->InputFunc = DoMelee;
1663 		pMS->LastInputTime = GetTimeCounter ();
1664 
1665 		flushPacketQueues ();
1666 
1667 		return TRUE;
1668 	}
1669 
1670 	conn = netConnections[which_side];
1671 	if (conn != NULL)
1672 	{
1673 		NetState status = NetConnection_getState (conn);
1674 		if ((status == NetState_init) ||
1675 		    (status == NetState_inSetup))
1676 		{
1677 			/* Connection complete! */
1678 			PlayerControl[which_side] = NETWORK_CONTROL | STANDARD_RATING;
1679 			DrawControls (which_side, TRUE);
1680 
1681 			RedrawMeleeFrame ();
1682 
1683 			UpdateMeleeStatusMessage (which_side);
1684 			pMS->InputFunc = DoMelee;
1685 			pMS->LastInputTime = GetTimeCounter ();
1686 			Deselect (pMS->MeleeOption);
1687 			pMS->MeleeOption = START_MELEE;
1688 		}
1689 	}
1690 
1691 	flushPacketQueues ();
1692 
1693 	SleepThreadUntil (TimeIn + ONE_SECOND / 30);
1694 
1695 	return TRUE;
1696 }
1697 
1698 /* Check for disconnects, and revert to human control if there is one */
1699 static void
check_for_disconnects(MELEE_STATE * pMS)1700 check_for_disconnects (MELEE_STATE *pMS)
1701 {
1702 	COUNT player;
1703 
1704 	for (player = 0; player < NUM_PLAYERS; player++)
1705 	{
1706 		NetConnection *conn;
1707 
1708 		if (!(PlayerControl[player] & NETWORK_CONTROL))
1709 			continue;
1710 
1711 		conn = netConnections[player];
1712 		if (conn == NULL || !NetConnection_isConnected (conn))
1713 		{
1714 			PlayerControl[player] = HUMAN_CONTROL | STANDARD_RATING;
1715 			DrawControls (player, FALSE);
1716 			log_add (log_User, "Player %d has disconnected; shifting "
1717 					"controls\n", player);
1718 		}
1719 	}
1720 
1721 	(void) pMS;
1722 }
1723 
1724 #endif
1725 
1726 static void
nextControlType(COUNT which_side)1727 nextControlType (COUNT which_side)
1728 {
1729 	switch (PlayerControl[which_side])
1730 	{
1731 		case HUMAN_CONTROL | STANDARD_RATING:
1732 			PlayerControl[which_side] = COMPUTER_CONTROL | STANDARD_RATING;
1733 			break;
1734 		case COMPUTER_CONTROL | STANDARD_RATING:
1735 			PlayerControl[which_side] = COMPUTER_CONTROL | GOOD_RATING;
1736 			break;
1737 		case COMPUTER_CONTROL | GOOD_RATING:
1738 			PlayerControl[which_side] = COMPUTER_CONTROL | AWESOME_RATING;
1739 			break;
1740 		case COMPUTER_CONTROL | AWESOME_RATING:
1741 			PlayerControl[which_side] = HUMAN_CONTROL | STANDARD_RATING;
1742 			break;
1743 
1744 #ifdef NETPLAY
1745 		case NETWORK_CONTROL | STANDARD_RATING:
1746 			if (netConnections[which_side] != NULL)
1747 				closePlayerNetworkConnection (which_side);
1748 			UpdateMeleeStatusMessage (-1);
1749 			PlayerControl[which_side] =  HUMAN_CONTROL | STANDARD_RATING;
1750 			break;
1751 #endif  /* NETPLAY */
1752 		default:
1753 			log_add (log_Error, "Error: Bad control type (%d) in "
1754 					"nextControlType().\n", PlayerControl[which_side]);
1755 			PlayerControl[which_side] =  HUMAN_CONTROL | STANDARD_RATING;
1756 			break;
1757 	}
1758 
1759 	DrawControls (which_side, TRUE);
1760 }
1761 
1762 static MELEE_OPTIONS
MeleeOptionDown(MELEE_OPTIONS current)1763 MeleeOptionDown (MELEE_OPTIONS current) {
1764 	if (current == QUIT_BOT)
1765 		return QUIT_BOT;
1766 	return current + 1;
1767 }
1768 
1769 static MELEE_OPTIONS
MeleeOptionUp(MELEE_OPTIONS current)1770 MeleeOptionUp (MELEE_OPTIONS current)
1771 {
1772 	if (current == TOP_ENTRY)
1773 		return TOP_ENTRY;
1774 	return current - 1;
1775 }
1776 
1777 static void
MeleeOptionSelect(MELEE_STATE * pMS)1778 MeleeOptionSelect (MELEE_STATE *pMS)
1779 {
1780 	switch (pMS->MeleeOption)
1781 	{
1782 		case START_MELEE:
1783 			StartMeleeButtonPressed (pMS);
1784 			break;
1785 		case LOAD_TOP:
1786 		case LOAD_BOT:
1787 			pMS->Initialized = FALSE;
1788 			pMS->side = pMS->MeleeOption == LOAD_TOP ? 0 : 1;
1789 			DoLoadTeam (pMS);
1790 			break;
1791 		case SAVE_TOP:
1792 		case SAVE_BOT:
1793 			pMS->side = pMS->MeleeOption == SAVE_TOP ? 0 : 1;
1794 			if (MeleeSetup_getFleetValue (pMS->meleeSetup, pMS->side) > 0)
1795 				DoSaveTeam (pMS);
1796 			else
1797 				PlayMenuSound (MENU_SOUND_FAILURE);
1798 			break;
1799 		case QUIT_BOT:
1800 			GLOBAL (CurrentActivity) |= CHECK_ABORT;
1801 			break;
1802 #ifdef NETPLAY
1803 		case NET_TOP:
1804 		case NET_BOT:
1805 		{
1806 			COUNT which_side;
1807 			BOOLEAN confirmed;
1808 
1809 			which_side = pMS->MeleeOption == NET_TOP ? 1 : 0;
1810 			confirmed = MeleeConnectDialog (which_side);
1811 			RedrawMeleeFrame ();
1812 			pMS->LastInputTime = GetTimeCounter ();
1813 			if (confirmed)
1814 			{
1815 				pMS->Initialized = FALSE;
1816 				pMS->InputFunc = DoConnectingDialog;
1817 			}
1818 			break;
1819 		}
1820 #endif  /* NETPLAY */
1821 		case CONTROLS_TOP:
1822 		case CONTROLS_BOT:
1823 		{
1824 			COUNT which_side = (pMS->MeleeOption == CONTROLS_TOP) ? 1 : 0;
1825 			nextControlType (which_side);
1826 			break;
1827 		}
1828 	}
1829 }
1830 
1831 BOOLEAN
DoMelee(MELEE_STATE * pMS)1832 DoMelee (MELEE_STATE *pMS)
1833 {
1834 	DWORD TimeIn = GetTimeCounter ();
1835 	BOOLEAN force_select = FALSE;
1836 
1837 	/* Cancel any presses of the Pause key. */
1838 	GamePaused = FALSE;
1839 
1840 	if (GLOBAL (CurrentActivity) & CHECK_ABORT)
1841 		return FALSE;
1842 
1843 	SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
1844 	if (!pMS->Initialized)
1845 	{
1846 		if (pMS->hMusic)
1847 		{
1848 			StopMusic ();
1849 			DestroyMusic (pMS->hMusic);
1850 			pMS->hMusic = 0;
1851 		}
1852 		pMS->hMusic = LoadMusic (MELEE_MUSIC);
1853 		pMS->Initialized = TRUE;
1854 
1855 		pMS->MeleeOption = START_MELEE;
1856 		PlayMusic (pMS->hMusic, TRUE, 1);
1857 		InitMelee (pMS);
1858 
1859 		FadeScreen (FadeAllToColor, ONE_SECOND / 2);
1860 		pMS->LastInputTime = GetTimeCounter ();
1861 		return TRUE;
1862 	}
1863 
1864 #ifdef NETPLAY
1865 	netInput ();
1866 #endif
1867 
1868 	if (PulsedInputState.menu[KEY_MENU_CANCEL] ||
1869 			PulsedInputState.menu[KEY_MENU_LEFT])
1870 	{
1871 		// Start editing the teams.
1872 		pMS->LastInputTime = GetTimeCounter ();
1873 		Deselect (pMS->MeleeOption);
1874 		pMS->MeleeOption = EDIT_MELEE;
1875 		pMS->Initialized = FALSE;
1876 		if (PulsedInputState.menu[KEY_MENU_CANCEL])
1877 		{
1878 			pMS->side = 0;
1879 			pMS->row = 0;
1880 			pMS->col = 0;
1881 		}
1882 		else
1883 		{
1884 			pMS->side = 0;
1885 			pMS->row = NUM_MELEE_ROWS - 1;
1886 			pMS->col = NUM_MELEE_COLUMNS - 1;
1887 		}
1888 		DoEdit (pMS);
1889 	}
1890 	else
1891 	{
1892 		MELEE_OPTIONS NewMeleeOption;
1893 
1894 		NewMeleeOption = pMS->MeleeOption;
1895 		if (PulsedInputState.menu[KEY_MENU_UP])
1896 		{
1897 			pMS->LastInputTime = GetTimeCounter ();
1898 			NewMeleeOption = MeleeOptionUp (pMS->MeleeOption);
1899 		}
1900 		else if (PulsedInputState.menu[KEY_MENU_DOWN])
1901 		{
1902 			pMS->LastInputTime = GetTimeCounter ();
1903 			NewMeleeOption = MeleeOptionDown (pMS->MeleeOption);
1904 		}
1905 
1906 		if ((PlayerControl[0] & PlayerControl[1] & PSYTRON_CONTROL)
1907 				&& GetTimeCounter () - pMS->LastInputTime > ONE_SECOND * 10)
1908 		{
1909 			force_select = TRUE;
1910 			NewMeleeOption = START_MELEE;
1911 		}
1912 
1913 		if (NewMeleeOption != pMS->MeleeOption)
1914 		{
1915 #ifdef NETPLAY
1916 			if (pMS->MeleeOption == CONTROLS_TOP ||
1917 					pMS->MeleeOption == CONTROLS_BOT)
1918 				UpdateMeleeStatusMessage (-1);
1919 #endif
1920 			Deselect (pMS->MeleeOption);
1921 			pMS->MeleeOption = NewMeleeOption;
1922 			Select (pMS->MeleeOption);
1923 #ifdef NETPLAY
1924 			if (NewMeleeOption == CONTROLS_TOP ||
1925 					NewMeleeOption == CONTROLS_BOT)
1926 			{
1927 				COUNT side = (NewMeleeOption == CONTROLS_TOP) ? 1 : 0;
1928 				if (PlayerControl[side] & NETWORK_CONTROL)
1929 					UpdateMeleeStatusMessage (side);
1930 				else
1931 					UpdateMeleeStatusMessage (-1);
1932 			}
1933 #endif
1934 		}
1935 
1936 		if (PulsedInputState.menu[KEY_MENU_SELECT] || force_select)
1937 		{
1938 			MeleeOptionSelect (pMS);
1939 			if (GLOBAL (CurrentActivity) & CHECK_ABORT)
1940 				return FALSE;
1941 		}
1942 	}
1943 
1944 #ifdef NETPLAY
1945 	flushPacketQueues ();
1946 
1947 	check_for_disconnects (pMS);
1948 #endif
1949 
1950 	Melee_flashSelection (pMS);
1951 
1952 	SleepThreadUntil (TimeIn + ONE_SECOND / 30);
1953 
1954 	return TRUE;
1955 }
1956 
1957 static int
LoadMeleeConfig(MELEE_STATE * pMS)1958 LoadMeleeConfig (MELEE_STATE *pMS)
1959 {
1960 	uio_Stream *stream;
1961 	int status;
1962 	COUNT side;
1963 
1964 	stream = uio_fopen (configDir, "melee.cfg", "rb");
1965 	if (stream == NULL)
1966 		goto err;
1967 
1968 	{
1969 		struct stat sb;
1970 
1971 		if (uio_fstat(uio_streamHandle(stream), &sb) == -1)
1972 			goto err;
1973 		if ((size_t) sb.st_size != (1 + MeleeTeam_serialSize) * NUM_SIDES)
1974 			goto err;
1975 	}
1976 
1977 	for (side = 0; side < NUM_SIDES; side++)
1978 	{
1979 		status = uio_getc (stream);
1980 		if (status == EOF)
1981 			goto err;
1982 		PlayerControl[side] = (BYTE) status;
1983 		// XXX: insert sanity check on PlanetControl here.
1984 
1985 		if (MeleeSetup_deserializeTeam (pMS->meleeSetup, side, stream) == -1)
1986 			goto err;
1987 
1988 		/* Do not allow netplay mode at the start. */
1989 		if (PlayerControl[side] & NETWORK_CONTROL)
1990 			PlayerControl[side] = HUMAN_CONTROL | STANDARD_RATING;
1991 	}
1992 
1993 	uio_fclose (stream);
1994 	return 0;
1995 
1996 err:
1997 	if (stream)
1998 		uio_fclose (stream);
1999 	return -1;
2000 }
2001 
2002 static int
WriteMeleeConfig(MELEE_STATE * pMS)2003 WriteMeleeConfig (MELEE_STATE *pMS)
2004 {
2005 	uio_Stream *stream;
2006 	COUNT side;
2007 
2008 	stream = res_OpenResFile (configDir, "melee.cfg", "wb");
2009 	if (stream == NULL)
2010 		goto err;
2011 
2012 	for (side = 0; side < NUM_SIDES; side++)
2013 	{
2014 		if (uio_putc (PlayerControl[side], stream) == EOF)
2015 			goto err;
2016 
2017 		if (MeleeSetup_serializeTeam (pMS->meleeSetup, side, stream) == -1)
2018 			goto err;
2019 	}
2020 
2021 	if (!res_CloseResFile (stream))
2022 		goto err;
2023 
2024 	return 0;
2025 
2026 err:
2027 	if (stream)
2028 	{
2029 		res_CloseResFile (stream);
2030 		DeleteResFile (configDir, "melee.cfg");
2031 	}
2032 	return -1;
2033 }
2034 
2035 void
Melee(void)2036 Melee (void)
2037 {
2038 	InitGlobData ();
2039 	{
2040 		MELEE_STATE MenuState;
2041 
2042 		pMeleeState = &MenuState;
2043 		memset (pMeleeState, 0, sizeof (*pMeleeState));
2044 
2045 		MenuState.InputFunc = DoMelee;
2046 		MenuState.Initialized = FALSE;
2047 
2048 		MenuState.meleeSetup = MeleeSetup_new ();
2049 
2050 		MenuState.randomContext = RandomContext_New ();
2051 		RandomContext_SeedRandom (MenuState.randomContext,
2052 				GetTimeCounter ());
2053 				// Using the current time still leaves the random state a bit
2054 				// predictable, but it is good enough.
2055 
2056 #ifdef NETPLAY
2057 		{
2058 			COUNT player;
2059 			for (player = 0; player < NUM_PLAYERS; player++)
2060 				netConnections[player] = NULL;
2061 		}
2062 #endif
2063 
2064 		MenuState.currentShip = MELEE_NONE;
2065 		MenuState.CurIndex = MELEE_STATE_INDEX_DONE;
2066 		InitMeleeLoadState (&MenuState);
2067 
2068 		GLOBAL (CurrentActivity) = SUPER_MELEE;
2069 
2070 		GameSounds = CaptureSound (LoadSound (GAME_SOUNDS));
2071 		LoadMeleeInfo (&MenuState);
2072 		if (LoadMeleeConfig (&MenuState) == -1)
2073 		{
2074 			PlayerControl[0] = HUMAN_CONTROL | STANDARD_RATING;
2075 			Melee_LocalChange_team (&MenuState, 0,
2076 					MenuState.load.preBuiltList[0]);
2077 			PlayerControl[1] = COMPUTER_CONTROL | STANDARD_RATING;
2078 			Melee_LocalChange_team (&MenuState, 1,
2079 					MenuState.load.preBuiltList[1]);
2080 		}
2081 
2082 		MenuState.side = 0;
2083 		SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
2084 		DoInput (&MenuState, TRUE);
2085 
2086 		StopMusic ();
2087 		WaitForSoundEnd (TFBSOUND_WAIT_ALL);
2088 
2089 		WriteMeleeConfig (&MenuState);
2090 		FreeMeleeInfo (&MenuState);
2091 		DestroySound (ReleaseSound (GameSounds));
2092 		GameSounds = 0;
2093 
2094 		UninitMeleeLoadState (&MenuState);
2095 
2096 		RandomContext_Delete (MenuState.randomContext);
2097 
2098 		MeleeSetup_delete (MenuState.meleeSetup);
2099 
2100 		FlushInput ();
2101 	}
2102 }
2103 
2104 #ifdef NETPLAY
2105 void
updateRandomSeed(MELEE_STATE * pMS,COUNT side,DWORD seed)2106 updateRandomSeed (MELEE_STATE *pMS, COUNT side, DWORD seed)
2107 {
2108 	TFB_SeedRandom (seed);
2109 	(void) pMS;
2110 	(void) side;
2111 }
2112 
2113 // The remote player has done something which invalidates our confirmation.
2114 void
confirmationCancelled(MELEE_STATE * pMS,COUNT side)2115 confirmationCancelled (MELEE_STATE *pMS, COUNT side)
2116 {
2117 	if (side == 0)
2118 		DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 16));
2119 				// "Bottom player changed something -- need to reconfirm."
2120 	else
2121 		DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 17));
2122 				// "Top player changed something -- need to reconfirm."
2123 
2124 	if (pMS->InputFunc == DoConfirmSettings)
2125 		pMS->InputFunc = DoMelee;
2126 }
2127 
2128 static void
connectionFeedback(NetConnection * conn,const char * str,bool forcePopup)2129 connectionFeedback (NetConnection *conn, const char *str, bool forcePopup) {
2130 	struct battlestate_struct *bs = NetMelee_getBattleState (conn);
2131 
2132 	if (bs == NULL && !forcePopup)
2133 	{
2134 		// bs == NULL means the game has not started yet.
2135 		DrawMeleeStatusMessage (str);
2136 	}
2137 	else
2138 	{
2139 		DoPopupWindow (str);
2140 	}
2141 }
2142 
2143 void
connectedFeedback(NetConnection * conn)2144 connectedFeedback (NetConnection *conn) {
2145 	if (NetConnection_getPlayerNr (conn) == 0)
2146 		connectionFeedback (conn, GAME_STRING (NETMELEE_STRING_BASE + 8),
2147 				false);
2148 				// "Bottom player is connected."
2149 	else
2150 		connectionFeedback (conn, GAME_STRING (NETMELEE_STRING_BASE + 9),
2151 				false);
2152 				// "Top player is connected."
2153 
2154 	PlayMenuSound (MENU_SOUND_INVOKED);
2155 }
2156 
2157 static const char *
abortReasonString(NetplayAbortReason reason)2158 abortReasonString (NetplayAbortReason reason)
2159 {
2160 	switch (reason)
2161 	{
2162 		case AbortReason_unspecified:
2163 			return GAME_STRING (NETMELEE_STRING_BASE + 25);
2164 					// "Disconnect for an unspecified reason.'
2165 		case AbortReason_versionMismatch:
2166 			return GAME_STRING (NETMELEE_STRING_BASE + 26);
2167 					// "Connection aborted due to version mismatch."
2168 		case AbortReason_invalidHash:
2169 			return GAME_STRING (NETMELEE_STRING_BASE + 27);
2170 					// "Connection aborted because the remote side sent a "
2171 					// "fake signature."
2172 		case AbortReason_protocolError:
2173 			return GAME_STRING (NETMELEE_STRING_BASE + 28);
2174 					// "Connection aborted due to an internal protocol "
2175 					// "error."
2176 	}
2177 
2178 	return NULL;
2179 			// Should not happen.
2180 }
2181 
2182 void
abortFeedback(NetConnection * conn,NetplayAbortReason reason)2183 abortFeedback (NetConnection *conn, NetplayAbortReason reason)
2184 {
2185 	const char *msg;
2186 
2187 	msg = abortReasonString (reason);
2188 	if (msg != NULL)
2189 		connectionFeedback (conn, msg, true);
2190 }
2191 
2192 static const char *
resetReasonString(NetplayResetReason reason)2193 resetReasonString (NetplayResetReason reason)
2194 {
2195 	switch (reason)
2196 	{
2197 		case ResetReason_unspecified:
2198 			return GAME_STRING (NETMELEE_STRING_BASE + 29);
2199 					// "Game aborted for an unspecified reason."
2200 		case ResetReason_syncLoss:
2201 			return GAME_STRING (NETMELEE_STRING_BASE + 30);
2202 					// "Game aborted due to loss of synchronisation."
2203 		case ResetReason_manualReset:
2204 			return GAME_STRING (NETMELEE_STRING_BASE + 31);
2205 					// "Game aborted by the remote player."
2206 	}
2207 
2208 	return NULL;
2209 			// Should not happen.
2210 }
2211 
2212 void
resetFeedback(NetConnection * conn,NetplayResetReason reason,bool byRemote)2213 resetFeedback (NetConnection *conn, NetplayResetReason reason,
2214 		bool byRemote)
2215 {
2216 	const char *msg;
2217 
2218 	flushPacketQueues ();
2219 			// If the local side queued a reset packet as a result of a
2220 			// remote reset, that packet will not have been sent yet.
2221 			// We flush the queue now, so that the remote side won't be
2222 			// waiting for the reset packet while this side is waiting
2223 			// for an acknowledgement of the feedback message.
2224 
2225 	if (reason == ResetReason_manualReset && !byRemote) {
2226 		// No message needed, the player initiated the reset.
2227 		return;
2228 	}
2229 
2230 	msg = resetReasonString (reason);
2231 	if (msg != NULL)
2232 		connectionFeedback (conn, msg, false);
2233 
2234 	// End supermelee. This must not be done before connectionFeedback(),
2235 	// otherwise the message will immediately disappear.
2236 	GLOBAL (CurrentActivity) |= CHECK_ABORT;
2237 }
2238 
2239 void
errorFeedback(NetConnection * conn)2240 errorFeedback (NetConnection *conn)
2241 {
2242 	if (NetConnection_getPlayerNr (conn) == 0)
2243 		connectionFeedback (conn, GAME_STRING (NETMELEE_STRING_BASE + 10),
2244 				false);
2245 				// "Bottom player: connection failed."
2246 	else
2247 		connectionFeedback (conn, GAME_STRING (NETMELEE_STRING_BASE + 11),
2248 				false);
2249 				// "Top player: connection failed."
2250 }
2251 
2252 void
closeFeedback(NetConnection * conn)2253 closeFeedback (NetConnection *conn)
2254 {
2255 	if (NetConnection_getPlayerNr (conn) == 0)
2256 		connectionFeedback (conn, GAME_STRING (NETMELEE_STRING_BASE + 12),
2257 				false);
2258 				// "Bottom player: connection closed."
2259 	else
2260 		connectionFeedback (conn, GAME_STRING (NETMELEE_STRING_BASE + 13),
2261 				false);
2262 				// "Top player: connection closed."
2263 }
2264 
2265 #endif  /* NETPLAY */
2266 
2267 
2268 ///////////////////////////////////////////////////////////////////////////
2269 
2270 // Melee_UpdateView_xxx() functions are called when some value in the
2271 // supermelee fleet setup screen needs to be updated visually.
2272 
2273 static void
Melee_UpdateView_fleetValue(MELEE_STATE * pMS,COUNT side)2274 Melee_UpdateView_fleetValue (MELEE_STATE *pMS, COUNT side)
2275 {
2276 	if (pMS->meleeStarted)
2277 		return;
2278 
2279 	DrawFleetValue (pMS, side, DTSHS_REPAIR);
2280 			// BUG: The fleet value is always drawn as deselected.
2281 }
2282 
2283 static void
Melee_UpdateView_ship(MELEE_STATE * pMS,COUNT side,FleetShipIndex index)2284 Melee_UpdateView_ship (MELEE_STATE *pMS, COUNT side, FleetShipIndex index)
2285 {
2286 	MeleeShip ship;
2287 
2288 	if (pMS->meleeStarted)
2289 		return;
2290 
2291 	ship = MeleeSetup_getShip (pMS->meleeSetup, side, index);
2292 
2293 	if (ship == MELEE_NONE)
2294 	{
2295 		ClearShipBox (side, index);
2296 	}
2297 	else
2298 	{
2299 		DrawShipBox (side, index, ship, FALSE);
2300 	}
2301 }
2302 
2303 static void
Melee_UpdateView_teamName(MELEE_STATE * pMS,COUNT side)2304 Melee_UpdateView_teamName (MELEE_STATE *pMS, COUNT side)
2305 {
2306 	if (pMS->meleeStarted)
2307 		return;
2308 
2309 	DrawTeamString (pMS, side, DTSHS_REPAIR, NULL);
2310 }
2311 
2312 ///////////////////////////////////////////////////////////////////////////
2313 
2314 // Melee_Change_xxx() functions are helper functions, called when some value
2315 // in the supermelee fleet setup screen has changed, eithed because of a
2316 // local change, or a remote change.
2317 
2318 static bool
Melee_Change_ship(MELEE_STATE * pMS,COUNT side,FleetShipIndex index,MeleeShip ship)2319 Melee_Change_ship (MELEE_STATE *pMS, COUNT side, FleetShipIndex index,
2320 		MeleeShip ship)
2321 {
2322 	if (!MeleeSetup_setShip (pMS->meleeSetup, side, index, ship))
2323 	{
2324 		// No change.
2325 		return false;
2326 	}
2327 
2328 	// Update the view
2329 	Melee_UpdateView_ship (pMS, side, index);
2330 	Melee_UpdateView_fleetValue (pMS, side);
2331 
2332 	// If the modified slot is currently selected, display the new ship icon
2333 	// on the right of the screen.
2334 	if (isShipSlotSelected (pMS, side, index))
2335 	{
2336 		pMS->currentShip = ship;
2337 		DrawMeleeShipStrings (pMS, ship);
2338 	}
2339 
2340 	return true;
2341 }
2342 
2343 // Pre: 'name' is '\0'-terminated
2344 static bool
Melee_Change_teamName(MELEE_STATE * pMS,COUNT side,const char * name)2345 Melee_Change_teamName (MELEE_STATE *pMS, COUNT side, const char *name)
2346 {
2347 	MeleeSetup *setup = pMS->meleeSetup;
2348 
2349 	if (!MeleeSetup_setTeamName (setup, side, name))
2350 	{
2351 		// No change.
2352 		return false;
2353 	}
2354 
2355 	if (pMS->row != NUM_MELEE_ROWS || pMS->side != side ||
2356 				pMeleeState->CurIndex == MELEE_STATE_INDEX_DONE)
2357 	{
2358 		// The team name is not currently being edited, so we can
2359 		// update it on screen. If it was edited, then this function
2360 		// will be called again after it is done.
2361 		Melee_UpdateView_teamName (pMS, side);
2362 	}
2363 
2364 	return true;
2365 }
2366 
2367 ///////////////////////////////////////////////////////////////////////////
2368 
2369 // Melee_LocalChange_xxx() functions are called when some value in the
2370 // supermelee fleet setup screen has changed because of a local action.
2371 // The behavior of these functions (and the comments therein) follow the
2372 // description in doc/devel/netplay/protocol.
2373 
2374 bool
Melee_LocalChange_ship(MELEE_STATE * pMS,COUNT side,FleetShipIndex index,MeleeShip ship)2375 Melee_LocalChange_ship (MELEE_STATE *pMS, COUNT side, FleetShipIndex index,
2376 		MeleeShip ship)
2377 {
2378 	if (!Melee_Change_ship (pMS, side, index, ship))
2379 		return false;
2380 
2381 #ifdef NETPLAY
2382 	{
2383 		MeleeSetup *setup = pMS->meleeSetup;
2384 
2385 		MeleeShip sentShip = MeleeSetup_getSentShip (setup, side, index);
2386 		if (sentShip == MELEE_UNSET)
2387 		{
2388 			// State 1.
2389 			// Notify network connections of the change.
2390 			Netplay_NotifyAll_setShip (pMS, side, index);
2391 			MeleeSetup_setSentShip (setup, side, index, ship);
2392 		}
2393 	}
2394 #endif  /* NETPLAY */
2395 
2396 	return true;
2397 }
2398 
2399 
2400 // Pre: 'name' is '\0'-terminated
2401 bool
Melee_LocalChange_teamName(MELEE_STATE * pMS,COUNT side,const char * name)2402 Melee_LocalChange_teamName (MELEE_STATE *pMS, COUNT side, const char *name)
2403 {
2404 	if (!Melee_Change_teamName (pMS, side, name))
2405 		return false;
2406 
2407 #ifdef NETPLAY
2408 	{
2409 		MeleeSetup *setup = pMS->meleeSetup;
2410 
2411 		const char *sentName = MeleeSetup_getSentTeamName (setup, side);
2412 		if (sentName == NULL)
2413 		{
2414 			// State 1.
2415 			// Notify network connections of the change.
2416 			Netplay_NotifyAll_setTeamName (pMS, side);
2417 			MeleeSetup_setSentTeamName (setup, side, name);
2418 		}
2419 	}
2420 #endif  /* NETPLAY */
2421 
2422 	return true;
2423 }
2424 
2425 bool
Melee_LocalChange_fleet(MELEE_STATE * pMS,size_t teamNr,const MeleeShip * fleet)2426 Melee_LocalChange_fleet (MELEE_STATE *pMS, size_t teamNr,
2427 		const MeleeShip *fleet)
2428 {
2429 	FleetShipIndex slotI;
2430 	bool changed = false;
2431 
2432 	for (slotI = 0; slotI < MELEE_FLEET_SIZE; slotI++)
2433 	{
2434 		if (Melee_LocalChange_ship (pMS, teamNr, slotI, fleet[slotI]))
2435 			changed = true;
2436 	}
2437 	return changed;
2438 }
2439 
2440 bool
Melee_LocalChange_team(MELEE_STATE * pMS,size_t teamNr,const MeleeTeam * team)2441 Melee_LocalChange_team (MELEE_STATE *pMS, size_t teamNr,
2442 		const MeleeTeam *team)
2443 {
2444 	const MeleeShip *fleet = MeleeTeam_getFleet (team);
2445 	const char *name = MeleeTeam_getTeamName (team);
2446 	bool changed = false;
2447 
2448 	if (Melee_LocalChange_fleet (pMS, teamNr, fleet))
2449 		changed = true;
2450 	if (Melee_LocalChange_teamName (pMS, teamNr, name))
2451 		changed = true;
2452 
2453 	return changed;
2454 }
2455 
2456 ///////////////////////////////////////////////////////////////////////////
2457 
2458 #ifdef NETPLAY
2459 
2460 // Send the entire team to the remote side. Used when the connection has
2461 // just been established, or after the setup menu is reentered after battle.
2462 void
Melee_bootstrapSyncTeam(MELEE_STATE * meleeState,size_t teamNr)2463 Melee_bootstrapSyncTeam (MELEE_STATE *meleeState, size_t teamNr)
2464 {
2465 	MeleeSetup *setup = meleeState->meleeSetup;
2466 	FleetShipIndex slotI;
2467 	const char *teamName;
2468 
2469 	// Send the current fleet.
2470 	Netplay_NotifyAll_setFleet(meleeState, teamNr);
2471 
2472 	// Update the last sent fleet.
2473 	for (slotI = 0; slotI < MELEE_FLEET_SIZE; slotI++)
2474 	{
2475 		MeleeShip ship = MeleeSetup_getShip (setup, teamNr, slotI);
2476 		assert (MeleeSetup_getSentShip (setup, teamNr, slotI) == MELEE_UNSET);
2477 		MeleeSetup_setSentShip (setup, teamNr, slotI, ship);
2478 	}
2479 
2480 	// Send the current team name.
2481 	Netplay_NotifyAll_setTeamName (meleeState, teamNr);
2482 
2483 	// Update the last sent team name.
2484 	teamName = MeleeSetup_getTeamName (setup, teamNr);
2485 	MeleeSetup_setSentTeamName (setup, teamNr, teamName);
2486 }
2487 
2488 ///////////////////////////////////////////////////////////////////////////
2489 
2490 // Melee_RemoteChange_xxx() functions are called when some value in the
2491 // supermelee fleet setup screen has changed remotely.
2492 // The behavior of these functions (and the comments therein) follow the
2493 // description in doc/devel/netplay/protocol.
2494 
2495 void
Melee_RemoteChange_ship(MELEE_STATE * pMS,NetConnection * conn,COUNT side,FleetShipIndex index,MeleeShip ship)2496 Melee_RemoteChange_ship (MELEE_STATE *pMS, NetConnection *conn, COUNT side,
2497 		FleetShipIndex index, MeleeShip ship)
2498 {
2499 	MeleeSetup *setup = pMS->meleeSetup;
2500 
2501 	MeleeShip sentShip = MeleeSetup_getSentShip (setup, side, index);
2502 	MeleeShip currentShip;
2503 
2504 	if (sentShip == MELEE_UNSET)
2505 	{
2506 		// State 1
2507 
2508 		// Change the ship locally.
2509 		Melee_Change_ship (pMS, side, index, ship);
2510 
2511 		// Notify the remote side.
2512 		Netplay_NotifyAll_setShip (pMS, side, index);
2513 
2514 		// A packet has now been received and sent. End of turn.
2515 		return;
2516 	}
2517 
2518 	// A packet has been sent and received. End of turn.
2519 	MeleeSetup_setSentShip (setup, side, index, MELEE_UNSET);
2520 
2521 	if (ship != sentShip)
2522 	{
2523 		// Rule 2c or 3d. The value which we sent is different from the value
2524 		// which the opponent sent. We need a tie-breaker to determine which
2525 		// value prevails.
2526 		if (NetConnection_getPlayerNr (conn) != side)
2527 		{
2528 			// Rule 2c+ or 3d+
2529 			// We win the tie-breaker. The value which we sent prevails.
2530 		}
2531 		else
2532 		{
2533 			// Rule 2c- or 3d-.
2534 			// We lose the tie-breaker. We adopt the remote value.
2535 			Melee_Change_ship (pMS, side, index, ship);
2536 			return;
2537 		}
2538 	}
2539 	/*
2540 	else
2541 	{
2542 		// Rule 2b or 3c. The value which we sent is the value which
2543 		// the opponent sent. This confirms the value.
2544 	}
2545 	*/
2546 
2547 	// Rule 2b, 2c+, 3c, or 3d+. The value which we sent is confirmed.
2548 
2549 	currentShip = MeleeSetup_getShip (setup, side, index);
2550 	if (currentShip != sentShip)
2551 	{
2552 		// Rule 3c or 3d+. We had a local change which was yet
2553 		// unreported.
2554 
2555 		// Notify the remote side and keep track of what we sent.
2556 		Netplay_NotifyAll_setShip (pMS, side, index);
2557 		MeleeSetup_setSentShip (setup, side, index, ship);
2558 	}
2559 }
2560 
2561 void
Melee_RemoteChange_teamName(MELEE_STATE * pMS,NetConnection * conn,COUNT side,const char * newName)2562 Melee_RemoteChange_teamName (MELEE_STATE *pMS, NetConnection *conn,
2563 		COUNT side, const char *newName)
2564 {
2565 	MeleeSetup *setup = pMS->meleeSetup;
2566 
2567 	const char *sentName = MeleeSetup_getSentTeamName (setup, side);
2568 	const char *currentName;
2569 
2570 	if (sentName == NULL)
2571 	{
2572 		// State 1
2573 
2574 		// Change the team name locally.
2575 		Melee_Change_teamName (pMS, side, newName);
2576 
2577 		// Notify the remote side.
2578 		Netplay_NotifyAll_setTeamName (pMS, side);
2579 
2580 		// A packet has now been received and sent. End of turn.
2581 		// The sent team name is still unset, so we don't have to reset it.
2582 		return;
2583 	}
2584 
2585 	if (strcmp (newName, sentName) == 0)
2586 	{
2587 		// Rule 2c or 3d. The value which we sent is different from the value
2588 		// which the opponent sent. We need a tie-breaker to determine which
2589 		// value prevails.
2590 		if (NetConnection_getPlayerNr (conn) != side)
2591 		{
2592 			// Rule 2c+ or 3d+
2593 			// We win the tie-breaker. The value which we sent prevails.
2594 		}
2595 		else
2596 		{
2597 			// Rule 2c- or 3d-.
2598 			// We lose the tie-breaker. We adopt the remote value.
2599 			Melee_Change_teamName (pMS, side, newName);
2600 			MeleeSetup_setSentTeamName (setup, side, NULL);
2601 			return;
2602 		}
2603 	}
2604 	/*
2605 	else
2606 	{
2607 		// Rule 2b or 3c. The value which we sent is the value which
2608 		// the opponent sent. This confirms the value.
2609 	}
2610 	*/
2611 
2612 	// Rule 2b, 2c+, 3c, or 3d+. The value which we sent is confirmed.
2613 
2614 	currentName = MeleeSetup_getTeamName (setup, side);
2615 	if (strcmp (currentName, sentName) != 0)
2616 	{
2617 		// Rule 3c or 3d+. We had a local change which was yet
2618 		// unreported.
2619 
2620 		// A packet has been sent and received, which ends the turn.
2621 		// We don't bother clearing the sent team name, as we're going
2622 		// to send a new packet immediately.
2623 
2624 		// Notify the remote side and keep track of what we sent.
2625 		Netplay_NotifyAll_setTeamName (pMS, side);
2626 
2627 		// Update the last sent message.
2628 		MeleeSetup_setSentTeamName (setup, side, newName);
2629 	}
2630 	else
2631 	{
2632 		// A packet has been sent and received. End of turn.
2633 		MeleeSetup_setSentTeamName (setup, side, NULL);
2634 	}
2635 }
2636 
2637 ///////////////////////////////////////////////////////////////////////////
2638 
2639 #endif  /* NETPLAY */
2640 
2641