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