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