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