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 "battle.h"
20
21 #include "battlecontrols.h"
22 #include "controls.h"
23 #include "init.h"
24 #include "element.h"
25 #include "ship.h"
26 #include "process.h"
27 #include "tactrans.h"
28 // for flee_preprocess()
29 #include "intel.h"
30 #ifdef NETPLAY
31 # include "supermelee/netplay/netmelee.h"
32 # ifdef NETPLAY_CHECKSUM
33 # include "supermelee/netplay/checksum.h"
34 # endif
35 # include "supermelee/netplay/notifyall.h"
36 #endif
37 #include "supermelee/pickmele.h"
38 #include "resinst.h"
39 #include "nameref.h"
40 #include "setup.h"
41 #include "settings.h"
42 #include "sounds.h"
43 #include "libs/async.h"
44 #include "libs/graphics/gfx_common.h"
45 #include "libs/log.h"
46 #include "libs/mathlib.h"
47
48
49 BYTE battle_counter[NUM_SIDES];
50 // The number of ships still available for battle to each side.
51 // A ship that has warped out is no longer available.
52 BOOLEAN instantVictory;
53 size_t battleInputOrder[NUM_SIDES];
54 // Indices of the sides in the order their input is processed.
55 // Network sides are last so that the sides will never be waiting
56 // on eachother, and games with a 0 frame delay are theoretically
57 // possible.
58 #ifdef NETPLAY
59 BattleFrameCounter battleFrameCount;
60 // Used for synchronisation purposes during netplay.
61 #endif
62
63 static BOOLEAN
RunAwayAllowed(void)64 RunAwayAllowed (void)
65 {
66 return (LOBYTE (GLOBAL (CurrentActivity)) == IN_ENCOUNTER
67 || LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE)
68 && GET_GAME_STATE (STARBASE_AVAILABLE)
69 && !GET_GAME_STATE (BOMB_CARRIER);
70 }
71
72 static void
DoRunAway(STARSHIP * StarShipPtr)73 DoRunAway (STARSHIP *StarShipPtr)
74 {
75 ELEMENT *ElementPtr;
76
77 LockElement (StarShipPtr->hShip, &ElementPtr);
78 if (GetPrimType (&DisplayArray[ElementPtr->PrimIndex]) == STAMP_PRIM
79 && ElementPtr->life_span == NORMAL_LIFE
80 && !(ElementPtr->state_flags & FINITE_LIFE)
81 && ElementPtr->mass_points != MAX_SHIP_MASS * 10
82 && !(ElementPtr->state_flags & APPEARING))
83 {
84 battle_counter[0]--;
85
86 ElementPtr->turn_wait = 3;
87 ElementPtr->thrust_wait = 4;
88 ElementPtr->colorCycleIndex = 0;
89 ElementPtr->preprocess_func = flee_preprocess;
90 ElementPtr->mass_points = MAX_SHIP_MASS * 10;
91 ZeroVelocityComponents (&ElementPtr->velocity);
92 StarShipPtr->cur_status_flags &=
93 ~(SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED);
94
95 SetPrimColor (&DisplayArray[ElementPtr->PrimIndex],
96 BUILD_COLOR (MAKE_RGB15 (0x0B, 0x00, 0x00), 0x2E));
97 // XXX: I think this is supposed to be the same as the
98 // first entry of the color cycle table in flee_preeprocess,
99 // but it is slightly different (0x0A as red value). - SvdB.
100 SetPrimType (&DisplayArray[ElementPtr->PrimIndex], STAMPFILL_PRIM);
101
102 StarShipPtr->ship_input_state = 0;
103 }
104 UnlockElement (StarShipPtr->hShip);
105 }
106
107 static void
setupBattleInputOrder(void)108 setupBattleInputOrder(void)
109 {
110 size_t i;
111
112 #ifndef NETPLAY
113 for (i = 0; i < NUM_SIDES; i++)
114 battleInputOrder[i] = i;
115 #else
116 int j;
117
118 i = 0;
119 // First put the locally controlled players in the array.
120 for (j = 0; j < NUM_SIDES; j++) {
121 if (!(PlayerControl[j] & NETWORK_CONTROL)) {
122 battleInputOrder[i] = j;
123 i++;
124 }
125 }
126
127 // Next put the network controlled players in the array.
128 for (j = 0; j < NUM_SIDES; j++) {
129 if (PlayerControl[j] & NETWORK_CONTROL) {
130 battleInputOrder[i] = j;
131 i++;
132 }
133 }
134 #endif
135 }
136
137 BATTLE_INPUT_STATE
frameInputHuman(HumanInputContext * context,STARSHIP * StarShipPtr)138 frameInputHuman (HumanInputContext *context, STARSHIP *StarShipPtr)
139 {
140 (void) StarShipPtr;
141 return CurrentInputToBattleInput (context->playerNr);
142 }
143
144 static void
ProcessInput(void)145 ProcessInput (void)
146 {
147 BOOLEAN CanRunAway;
148 size_t sideI;
149
150 #ifdef NETPLAY
151 netInput ();
152 #endif
153
154 CanRunAway = RunAwayAllowed ();
155
156 for (sideI = 0; sideI < NUM_SIDES; sideI++)
157 {
158 HSTARSHIP hBattleShip, hNextShip;
159 size_t cur_player = battleInputOrder[sideI];
160
161 for (hBattleShip = GetHeadLink (&race_q[cur_player]);
162 hBattleShip != 0; hBattleShip = hNextShip)
163 {
164 BATTLE_INPUT_STATE InputState;
165 STARSHIP *StarShipPtr;
166
167 StarShipPtr = LockStarShip (&race_q[cur_player], hBattleShip);
168 hNextShip = _GetSuccLink (StarShipPtr);
169
170 if (StarShipPtr->hShip)
171 {
172 // TODO: review and see if we have to do this every frame, or
173 // if we can do this once somewhere
174 StarShipPtr->control = PlayerControl[cur_player];
175
176 InputState = PlayerInput[cur_player]->handlers->frameInput (
177 PlayerInput[cur_player], StarShipPtr);
178
179 #if CREATE_JOURNAL
180 JournalInput (InputState);
181 #endif /* CREATE_JOURNAL */
182 #ifdef NETPLAY
183 if (!(PlayerControl[cur_player] & NETWORK_CONTROL))
184 {
185 BattleInputBuffer *bib = getBattleInputBuffer(cur_player);
186 Netplay_NotifyAll_battleInput (InputState);
187 flushPacketQueues ();
188
189 BattleInputBuffer_push (bib, InputState);
190 // Add this input to the end of the buffer.
191 BattleInputBuffer_pop (bib, &InputState);
192 // Get the input from the front of the buffer.
193 }
194 #endif
195
196 StarShipPtr->ship_input_state = 0;
197 if (StarShipPtr->RaceDescPtr->ship_info.crew_level)
198 {
199 if (InputState & BATTLE_LEFT)
200 StarShipPtr->ship_input_state |= LEFT;
201 else if (InputState & BATTLE_RIGHT)
202 StarShipPtr->ship_input_state |= RIGHT;
203 if (InputState & BATTLE_THRUST)
204 StarShipPtr->ship_input_state |= THRUST;
205 if (InputState & BATTLE_WEAPON)
206 StarShipPtr->ship_input_state |= WEAPON;
207 if (InputState & BATTLE_SPECIAL)
208 StarShipPtr->ship_input_state |= SPECIAL;
209
210 if (CanRunAway && cur_player == 0 &&
211 (InputState & BATTLE_ESCAPE))
212 DoRunAway (StarShipPtr);
213 }
214 }
215
216 UnlockStarShip (&race_q[cur_player], hBattleShip);
217 }
218 }
219
220 #ifdef NETPLAY
221 flushPacketQueues ();
222 #endif
223
224 if (GLOBAL (CurrentActivity) & (CHECK_LOAD | CHECK_ABORT))
225 GLOBAL (CurrentActivity) &= ~IN_BATTLE;
226 }
227
228 #if DEMO_MODE || CREATE_JOURNAL
229 DWORD BattleSeed;
230 #endif /* DEMO_MODE */
231
232 static MUSIC_REF BattleRef;
233
234 void
BattleSong(BOOLEAN DoPlay)235 BattleSong (BOOLEAN DoPlay)
236 {
237 if (BattleRef == 0)
238 {
239 if (inHyperSpace ())
240 BattleRef = LoadMusic (HYPERSPACE_MUSIC);
241 else if (inQuasiSpace ())
242 BattleRef = LoadMusic (QUASISPACE_MUSIC);
243 else
244 BattleRef = LoadMusic (BATTLE_MUSIC);
245 }
246
247 if (DoPlay)
248 PlayMusic (BattleRef, TRUE, 1);
249 }
250
251 void
FreeBattleSong(void)252 FreeBattleSong (void)
253 {
254 DestroyMusic (BattleRef);
255 BattleRef = 0;
256 }
257
258 static BOOLEAN
DoBattle(BATTLE_STATE * bs)259 DoBattle (BATTLE_STATE *bs)
260 {
261 extern UWORD nth_frame;
262 RECT r;
263 BYTE battle_speed;
264
265 SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE);
266
267 #if defined (NETPLAY) && defined (NETPLAY_CHECKSUM)
268 if (getNumNetConnections() > 0 &&
269 battleFrameCount % NETPLAY_CHECKSUM_INTERVAL == 0)
270 {
271 crc_State state;
272 Checksum checksum;
273
274 crc_init(&state);
275 crc_processState (&state);
276 checksum = (Checksum) crc_finish (&state);
277
278 Netplay_NotifyAll_checksum ((uint32) battleFrameCount,
279 (uint32) checksum);
280 flushPacketQueues ();
281 addLocalChecksum (battleFrameCount, checksum);
282 }
283 #endif
284 ProcessInput ();
285 // Also calls NetInput()
286 #if defined (NETPLAY) && defined (NETPLAY_CHECKSUM)
287 if (getNumNetConnections() > 0)
288 {
289 size_t delay = getBattleInputDelay();
290
291 if (battleFrameCount >= delay
292 && (battleFrameCount - delay) % NETPLAY_CHECKSUM_INTERVAL == 0)
293 {
294 if (!(GLOBAL (CurrentActivity) & CHECK_ABORT))
295 {
296 if (!verifyChecksums (battleFrameCount - delay)) {
297 GLOBAL(CurrentActivity) |= CHECK_ABORT;
298 resetConnections (ResetReason_syncLoss);
299 }
300 }
301 }
302 }
303 #endif
304
305 if (bs->first_time)
306 {
307 r.corner.x = SIS_ORG_X;
308 r.corner.y = SIS_ORG_Y;
309 r.extent.width = SIS_SCREEN_WIDTH;
310 r.extent.height = SIS_SCREEN_HEIGHT;
311 SetTransitionSource (&r);
312 }
313 BatchGraphics ();
314
315 // Call the callback function, if set
316 if (bs->frame_cb)
317 bs->frame_cb ();
318
319 RedrawQueue (TRUE);
320
321 if (bs->first_time)
322 {
323 bs->first_time = FALSE;
324 ScreenTransition (3, &r);
325 }
326 UnbatchGraphics ();
327 if ((!(GLOBAL (CurrentActivity) & IN_BATTLE)) ||
328 (GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
329 {
330 return FALSE;
331 }
332
333 battle_speed = HIBYTE (nth_frame);
334 if (battle_speed == (BYTE)~0)
335 { // maximum speed, nothing rendered at all
336 Async_process ();
337 TaskSwitch ();
338 }
339 else
340 {
341 SleepThreadUntil (bs->NextTime
342 + BATTLE_FRAME_RATE / (battle_speed + 1));
343 bs->NextTime = GetTimeCounter ();
344 }
345
346 if ((GLOBAL (CurrentActivity) & IN_BATTLE) == 0)
347 return FALSE;
348
349 #ifdef NETPLAY
350 battleFrameCount++;
351 #endif
352 return TRUE;
353 }
354
355 #ifdef NETPLAY
356 COUNT
GetPlayerOrder(COUNT i)357 GetPlayerOrder (COUNT i)
358 {
359 // Iff 'myTurn' is set on a connection, the local party will be
360 // processed first.
361 // If neither is network controlled, the top player (1) is handled
362 // first.
363 if (((PlayerControl[0] & NETWORK_CONTROL) &&
364 !NetConnection_getDiscriminant (netConnections[0])) ||
365 ((PlayerControl[1] & NETWORK_CONTROL) &&
366 NetConnection_getDiscriminant (netConnections[1])))
367 return i;
368 else
369 return 1 - i;
370 }
371 #endif
372
373 // Let each player pick his ship.
374 static BOOLEAN
selectAllShips(SIZE num_ships)375 selectAllShips (SIZE num_ships)
376 {
377 if (num_ships == 1) {
378 // HyperSpace in full game.
379 return GetNextStarShip (NULL, 0);
380 }
381
382 #ifdef NETPLAY
383 if ((PlayerControl[0] & NETWORK_CONTROL) &&
384 (PlayerControl[1] & NETWORK_CONTROL))
385 {
386 log_add (log_Error, "Only one side at a time can be network "
387 "controlled.\n");
388 return FALSE;
389 }
390 #endif
391
392 return GetInitialStarShips ();
393 }
394
395 BOOLEAN
Battle(BattleFrameCallback * callback)396 Battle (BattleFrameCallback *callback)
397 {
398 SIZE num_ships;
399
400
401 #if !(DEMO_MODE || CREATE_JOURNAL)
402 if (LOBYTE (GLOBAL (CurrentActivity)) != SUPER_MELEE) {
403 // In Supermelee, the RNG is already initialised.
404 TFB_SeedRandom (GetTimeCounter ());
405 }
406 #else /* DEMO_MODE */
407 if (BattleSeed == 0)
408 BattleSeed = TFB_Random ();
409 TFB_SeedRandom (BattleSeed);
410 BattleSeed = TFB_Random (); /* get next battle seed */
411 #endif /* DEMO_MODE */
412
413 BattleSong (FALSE);
414
415 num_ships = InitShips ();
416
417 if (instantVictory)
418 {
419 num_ships = 0;
420 battle_counter[0] = 1;
421 battle_counter[1] = 0;
422 instantVictory = FALSE;
423 }
424
425 if (num_ships)
426 {
427 BATTLE_STATE bs;
428
429 GLOBAL (CurrentActivity) |= IN_BATTLE;
430 battle_counter[0] = CountLinks (&race_q[0]);
431 battle_counter[1] = CountLinks (&race_q[1]);
432
433 if (optMeleeScale != TFB_SCALE_STEP)
434 SetGraphicScaleMode (optMeleeScale);
435
436 setupBattleInputOrder ();
437 #ifdef NETPLAY
438 initBattleInputBuffers ();
439 #ifdef NETPLAY_CHECKSUM
440 initChecksumBuffers ();
441 #endif /* NETPLAY_CHECKSUM */
442 battleFrameCount = 0;
443 ResetWinnerStarShip ();
444 setBattleStateConnections (&bs);
445 #endif /* NETPLAY */
446
447 if (!selectAllShips (num_ships)) {
448 GLOBAL (CurrentActivity) |= CHECK_ABORT;
449 goto AbortBattle;
450 }
451
452 BattleSong (TRUE);
453 bs.NextTime = 0;
454 #ifdef NETPLAY
455 initBattleStateDataConnections ();
456 {
457 bool allOk = negotiateReadyConnections (true, NetState_inBattle);
458 if (!allOk) {
459 GLOBAL (CurrentActivity) |= CHECK_ABORT;
460 goto AbortBattle;
461 }
462 }
463 #endif /* NETPLAY */
464 bs.InputFunc = DoBattle;
465 bs.frame_cb = callback;
466 bs.first_time = inHQSpace ();
467
468 DoInput (&bs, FALSE);
469
470 AbortBattle:
471 if (LOBYTE (GLOBAL (CurrentActivity)) == SUPER_MELEE)
472 {
473 if (GLOBAL (CurrentActivity) & CHECK_ABORT)
474 {
475 // Do not return to the main menu when a game is aborted,
476 // (just to the supermelee menu).
477 #ifdef NETPLAY
478 waitResetConnections(NetState_inSetup);
479 // A connection may already be in inSetup (set from
480 // GetMeleeStarship). This is not a problem, although
481 // it will generate a warning in debug mode.
482 #endif
483
484 GLOBAL (CurrentActivity) &= ~CHECK_ABORT;
485 }
486 else
487 {
488 // Show the result of the battle.
489 MeleeGameOver ();
490 }
491 }
492
493 #ifdef NETPLAY
494 uninitBattleInputBuffers();
495 #ifdef NETPLAY_CHECKSUM
496 uninitChecksumBuffers ();
497 #endif /* NETPLAY_CHECKSUM */
498 setBattleStateConnections (NULL);
499 #endif /* NETPLAY */
500
501 StopDitty ();
502 StopMusic ();
503 StopSound ();
504 }
505
506 UninitShips ();
507 FreeBattleSong ();
508
509
510 return (BOOLEAN) (num_ships < 0);
511 }
512
513