Maelstrom: Added a new kids mode where you have air brakes and automatic shields

https://github.com/libsdl-org/Maelstrom/commit/2fc35f8e59dfe3e5eda73d658492b0bfcc3e703e

From 2fc35f8e59dfe3e5eda73d658492b0bfcc3e703e Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 25 Nov 2011 11:02:40 -0500
Subject: [PATCH] Added a new kids mode where you have air brakes and automatic
 shields

---
 game/Maelstrom_Globals.h |  1 +
 game/game.cpp            | 23 +++++++++++++-----
 game/gameinfo.cpp        | 18 ++++++++++++--
 game/gameinfo.h          | 15 +++++++++++-
 game/lobby.cpp           |  3 ++-
 game/main.cpp            |  4 ++--
 game/object.h            | 12 +++++++---
 game/player.cpp          | 51 +++++++++++++++++++++-------------------
 game/player.h            |  8 ++++++-
 9 files changed, 95 insertions(+), 40 deletions(-)

diff --git a/game/Maelstrom_Globals.h b/game/Maelstrom_Globals.h
index 0a93dfc7..37650180 100644
--- a/game/Maelstrom_Globals.h
+++ b/game/Maelstrom_Globals.h
@@ -46,6 +46,7 @@
 // Preferences keys
 #define PREFERENCES_HANDLE "Handle"
 #define PREFERENCES_DEATHMATCH "Network.Deathmatch"
+#define PREFERENCES_KIDMODE "Cheat.KidMode"
 
 // The Font Server :)
 extern FontServ *fontserv;
diff --git a/game/game.cpp b/game/game.cpp
index b9e03efc..463ef41f 100644
--- a/game/game.cpp
+++ b/game/game.cpp
@@ -188,7 +188,7 @@ GamePanelDelegate::OnShow()
 		if (!gPlayers[i]->IsValid()) {
 			continue;
 		}
-		gPlayers[i]->NewGame(gGameInfo.lives, gGameInfo.deathMatch);
+		gPlayers[i]->NewGame(gGameInfo.lives);
 	}
 	gLastStar = STAR_DELAY;
 	gLastDrawn = 0L;
@@ -209,7 +209,7 @@ GamePanelDelegate::OnShow()
 			m_multiplayerColor->Hide();
 		}
 	}
-	if ( gGameInfo.deathMatch ) {
+	if ( gGameInfo.IsDeathmatch() ) {
 		if (m_fragsLabel) {
 			m_fragsLabel->Show();
 		}
@@ -321,6 +321,17 @@ GamePanelDelegate::OnTick()
 		if ( ! gPlayers[j]->Alive() )
 			continue;
 
+		if (gGameInfo.IsKidMode()) {
+			bool enableShield = false;
+			OBJ_LOOP(i, gNumSprites) {
+				if (gSprites[i]->Collide(gPlayers[j], false)) {
+					enableShield = true;
+					break;
+				}
+			}
+			gPlayers[j]->SetKidShield(enableShield);
+		}
+
 		/* This loop looks funny because gNumSprites can change 
 		   dynamically during the loop as sprites are killed/created.
 		   This same logic is used whenever looping where sprites
@@ -332,7 +343,7 @@ GamePanelDelegate::OnTick()
 				gSprites[i] = gSprites[gNumSprites];
 			}
 		}
-		if ( gGameInfo.deathMatch ) {
+		if ( gGameInfo.IsDeathmatch() ) {
 			OBJ_LOOP(i, MAX_PLAYERS) {
 				if (!gPlayers[i]->IsValid()) {
 					continue;
@@ -536,7 +547,7 @@ GamePanelDelegate::DrawStatus(Bool first)
 			m_score->SetText(numbuf);
 		}
 
-		if (!gGameInfo.deathMatch) {
+		if (!gGameInfo.IsDeathmatch()) {
 			if (lastScores[i] == Score)
 				continue;
 
@@ -1011,7 +1022,7 @@ static void DoGameOver(void)
 		final[i].Score = gPlayers[i]->GetScore();
 		final[i].Frags = gPlayers[i]->GetFrags();
 	}
-	if ( gGameInfo.deathMatch )
+	if ( gGameInfo.IsDeathmatch() )
 		qsort(final,MAX_PLAYERS,sizeof(struct FinalScore),cmp_byfrags);
 	else
 		qsort(final,MAX_PLAYERS,sizeof(struct FinalScore),cmp_byscore);
@@ -1041,7 +1052,7 @@ static void DoGameOver(void)
 			if (!label) {
 				continue;
 			}
-			if (gGameInfo.deathMatch) {
+			if (gGameInfo.IsDeathmatch()) {
 				sprintf(num1, "%7d", final[i].Score);
 				sprintf(num2, "%3d", final[i].Frags);
 				sprintf(buffer, "Player %d: %s Points, %s Frags", final[i].Player, num1, num2);
diff --git a/game/gameinfo.cpp b/game/gameinfo.cpp
index 17393959..b0bc786c 100644
--- a/game/gameinfo.cpp
+++ b/game/gameinfo.cpp
@@ -43,21 +43,30 @@ GameInfo::Reset()
 	lives = 0;
 	turbo = 0;
 	deathMatch = 0;
+	gameMode = 0;
+	deathMatch = 0;
 	numNodes = 0;
 	SDL_zero(nodes);
 	SDL_zero(players);
 }
 
 void
-GameInfo::SetHost(Uint8 wave, Uint8 lives, Uint8 turbo, Uint8 deathMatch)
+GameInfo::SetHost(Uint8 wave, Uint8 lives, Uint8 turbo, Uint8 deathMatch, bool kidMode)
 {
 	Reset();
 
 	this->gameID = localID;
 	this->seed = GetRandSeed();
 	this->wave = wave;
-	this->lives = lives;
+	this->lives = deathMatch ? deathMatch : lives;
 	this->turbo = turbo;
+	this->gameMode = 0;
+	if (kidMode) {
+		this->gameMode |= GAME_MODE_KIDS;
+	}
+	if (deathMatch) {
+		this->gameMode |= GAME_MODE_DEATHMATCH;
+	}
 	this->deathMatch = deathMatch;
 
 	// We are the host node
@@ -136,6 +145,7 @@ GameInfo::CopyFrom(const GameInfo &rhs)
 	wave = rhs.wave;
 	lives = rhs.lives;
 	turbo = rhs.turbo;
+	gameMode = rhs.gameMode;
 	deathMatch = rhs.deathMatch;
 
 	for (i = 0; i < MAX_NODES; ++i) {
@@ -199,6 +209,9 @@ GameInfo::ReadFromPacket(DynamicPacket &packet)
 	if (!packet.Read(turbo)) {
 		return false;
 	}
+	if (!packet.Read(gameMode)) {
+		return false;
+	}
 	if (!packet.Read(deathMatch)) {
 		return false;
 	}
@@ -251,6 +264,7 @@ GameInfo::WriteToPacket(DynamicPacket &packet)
 	packet.Write(wave);
 	packet.Write(lives);
 	packet.Write(turbo);
+	packet.Write(gameMode);
 	packet.Write(deathMatch);
 
 	packet.Write(numNodes);
diff --git a/game/gameinfo.h b/game/gameinfo.h
index f30f7183..d211e54b 100644
--- a/game/gameinfo.h
+++ b/game/gameinfo.h
@@ -47,6 +47,11 @@ enum PLAYER_CONTROL {
 #endif
 };
 
+enum GAME_MODE {
+	GAME_MODE_DEATHMATCH = 0x01,
+	GAME_MODE_KIDS       = 0x02,
+};
+
 #define IS_LOCAL_CONTROL(X)	(X != CONTROL_NONE && X != CONTROL_NETWORK && X != CONTROL_REPLAY)
 
 enum NODE_STATE_FLAG {
@@ -118,7 +123,7 @@ class GameInfo
 		localID = uniqueID;
 	}
 
-	void SetHost(Uint8 wave, Uint8 lives, Uint8 turbo, Uint8 deathMatch);
+	void SetHost(Uint8 wave, Uint8 lives, Uint8 turbo, Uint8 deathMatch, bool kidMode);
 
 	void SetPlayerSlot(int slot, const char *name, Uint8 controlMask);
 	void SetPlayerName(int slot, const char *name);
@@ -186,6 +191,13 @@ class GameInfo
 
 	bool IsFull() const;
 
+	bool IsDeathmatch() const {
+		return (gameMode & GAME_MODE_DEATHMATCH) != 0;
+	}
+	bool IsKidMode() const {
+		return (gameMode & GAME_MODE_KIDS) != 0;
+	}
+
 	void SetNodeState(int index, Uint8 state);
 	Uint8 GetNodeState(int index) const;
 
@@ -212,6 +224,7 @@ class GameInfo
 	Uint8 wave;
 	Uint8 lives;
 	Uint8 turbo;
+	Uint8 gameMode;
 	Uint8 deathMatch;
 
 	Uint32 localID;
diff --git a/game/lobby.cpp b/game/lobby.cpp
index 20f88a4e..4dbfb305 100644
--- a/game/lobby.cpp
+++ b/game/lobby.cpp
@@ -438,7 +438,8 @@ LobbyDialogDelegate::SetState(LOBBY_STATE state)
 		m_game.SetHost(DEFAULT_START_WAVE,
 				DEFAULT_START_LIVES,
 				DEFAULT_START_TURBO,
-				prefs->GetNumber(PREFERENCES_DEATHMATCH));
+				prefs->GetNumber(PREFERENCES_DEATHMATCH),
+				prefs->GetBool(PREFERENCES_KIDMODE));
 
 		// Set up the controls for this game
 		for (i = 0; i < MAX_PLAYERS; ++i) {
diff --git a/game/main.cpp b/game/main.cpp
index 6e1d670f..84fc64d0 100644
--- a/game/main.cpp
+++ b/game/main.cpp
@@ -72,7 +72,7 @@ static void RunSinglePlayerGame()
 }
 static void RunPlayGame(void*)
 {
-	gGameInfo.SetHost(DEFAULT_START_WAVE, DEFAULT_START_LIVES, DEFAULT_START_TURBO, 0);
+	gGameInfo.SetHost(DEFAULT_START_WAVE, DEFAULT_START_LIVES, DEFAULT_START_TURBO, 0, prefs->GetBool(PREFERENCES_KIDMODE));
 	gGameInfo.SetPlayerSlot(0, prefs->GetString(PREFERENCES_HANDLE), CONTROL_LOCAL);
 	RunSinglePlayerGame();
 }
@@ -173,7 +173,7 @@ static void CheatDialogDone(UIDialog *dialog, int status)
 		Delay(SOUND_DELAY);
 		sound->PlaySound(gNewLife, 5);
 		Delay(SOUND_DELAY);
-		gGameInfo.SetHost(wave, lives, turbo, 0);
+		gGameInfo.SetHost(wave, lives, turbo, 0, prefs->GetBool(PREFERENCES_KIDMODE));
 		gGameInfo.SetPlayerSlot(0, prefs->GetString(PREFERENCES_HANDLE), CONTROL_LOCAL);
 		RunSinglePlayerGame();
 	}
diff --git a/game/object.h b/game/object.h
index a6cf8d0d..bf933525 100644
--- a/game/object.h
+++ b/game/object.h
@@ -69,14 +69,21 @@ class Object {
 		/* This function is called to see if we shot something */
 		return(NULL);
 	}
-	virtual int Collide(Object *object) {
+	virtual int Collide(Object *object, bool exact = true) {
 		/* Set up the location rectangles */
 		Rect *R1=&HitRect, *R2=&object->HitRect;
 
+		if ( ! solid || ! object->solid )
+			return(0);
+
 		/* No collision if no overlap */
 		if ( ! Overlap(R1, R2) )
 			return(0);
 
+		/* See if that's enough to check collision */
+		if ( ! exact )
+			return(1);
+
 		/* Check the bitmasks to see if the sprites really intersect */
 		int  xoff1, xoff2;
 		int  roff;
@@ -129,8 +136,7 @@ class Object {
 			if ( BeenShot(ship, shot) > 0 )
 				return(-1);
 		}
-		if ( (solid && ship->solid) && 
-				Collide(ship) && (BeenRunOver(ship) > 0) )
+		if ( Collide(ship) && (BeenRunOver(ship) > 0) )
 			return(-1);
 		return(0);
 	}
diff --git a/game/player.cpp b/game/player.cpp
index fcf233d8..ee8e0259 100644
--- a/game/player.cpp
+++ b/game/player.cpp
@@ -26,8 +26,6 @@
 #include "player.h"
 #include "objects.h"
 
-// Define this to be invincible
-//#define KID_MODE
 
 /* ----------------------------------------------------------------- */
 /* -- The thrust sound callback */
@@ -74,10 +72,10 @@ Player::~Player()
 
 /* Note that the lives argument is ignored during deathmatches */
 void
-Player::NewGame(int lives, int deathMatch)
+Player::NewGame(int lives)
 {
 	Playing = 1;
-	if ( deathMatch )
+	if ( gGameInfo.IsDeathmatch() )
 		Lives = 1;
 	else
 		Lives = lives;
@@ -147,8 +145,14 @@ Player::NewShip(void)
 	Dead = 0;
 	Exploding = 0;
 	Set_TTL(-1);
-	if ( ! gGameInfo.deathMatch )
+	if ( ! gGameInfo.IsDeathmatch() )
 		--Lives;
+
+	// In Kid Mode you automatically get air brakes and full shields
+	if ( gGameInfo.IsKidMode() ) {
+		special |= AIR_BRAKES;
+		ShieldLevel = MAX_SHIELD;
+	}
 	return(Lives);
 }
 
@@ -157,7 +161,7 @@ void
 Player::IncrFrags(void)
 {
 	++Frags;
-	if ( gGameInfo.deathMatch && (Frags >= gGameInfo.deathMatch) ) {
+	if ( gGameInfo.IsDeathmatch() && (Frags >= gGameInfo.deathMatch) ) {
 		/* Game over, we got a stud. :) */
 		int i;
 		OBJ_LOOP(i, MAX_PLAYERS) {
@@ -176,7 +180,7 @@ error("Killing player %d\n", i+1);
 void
 Player::IncrLives(int lives)
 {
-	if ( gGameInfo.deathMatch && (lives > 0) )
+	if ( gGameInfo.IsDeathmatch() && (lives > 0) )
 		return;
 	Lives += lives;
 }
@@ -185,9 +189,6 @@ Player::IncrLives(int lives)
 int
 Player::BeenShot(Object *ship, Shot *shot)
 {
-#ifdef KID_MODE
-	return(0);
-#else
 	if ( Exploding || !Alive() )
 		return(0);
 	if ( AutoShield || (ShieldOn && (ShieldLevel > 0)) )
@@ -197,15 +198,12 @@ Player::BeenShot(Object *ship, Shot *shot)
 		return(0);
 	}
 	return(Object::BeenShot(ship, shot));
-#endif
 }
 
 /* We've been run over!  (returns 1 if we are dead) */
 int
-Player::BeenRunOver(Object *ship) {
-#ifdef KID_MODE
-	return(0);
-#else
+Player::BeenRunOver(Object *ship)
+{
 	if ( Exploding || !Alive() )
 		return(0);
 	if ( AutoShield || (ShieldOn && (ShieldLevel > 0)) )
@@ -217,16 +215,12 @@ Player::BeenRunOver(Object *ship) {
 		return(0);
 	}
 	return(Object::BeenRunOver(ship));
-#endif
 }
 
 /* We've been run over by a rock or something */
 int
 Player::BeenDamaged(int damage)
 {
-#ifdef KID_MODE
-	return(0);
-#else
 	if ( Exploding || !Alive() )
 		return(0);
 	if ( AutoShield || (ShieldOn && (ShieldLevel > 0)) )
@@ -236,7 +230,6 @@ Player::BeenDamaged(int damage)
 		return(0);
 	}
 	return(Object::BeenDamaged(damage));
-#endif
 }
 
 /* We expired (returns -1 if our sprite should be deleted) */
@@ -248,7 +241,7 @@ Player::BeenTimedOut(void)
 		((SCREEN_WIDTH/2-((gGameInfo.GetNumPlayers()/2-Index)*(2*SPRITES_WIDTH)))*SCALE_FACTOR),
 		((SCREEN_HEIGHT/2)*SCALE_FACTOR)
 	);
-	if ( gGameInfo.deathMatch )
+	if ( gGameInfo.IsDeathmatch() )
 		Dead = (DEAD_DELAY/2);
 	else
 		Dead = DEAD_DELAY;
@@ -475,7 +468,7 @@ printf("\n");
 					WasShielded = 1;
 				}
 				--ShieldLevel;
-			} else {
+			} else if ( ShieldOn & SHIELD_MANUAL ) {
 				sound->PlaySound(gNoShieldSound, 2);
 			}
 		} else
@@ -549,7 +542,7 @@ Player::HandleKeys(void)
 					Rotating |= 0x10;
 					break;
 				case SHIELD_KEY:
-					ShieldOn = 1;
+					ShieldOn |= SHIELD_MANUAL;
 					break;
 				case FIRE_KEY:
 					Shooting = 1;
@@ -571,7 +564,7 @@ Player::HandleKeys(void)
 					Rotating &= ~0x10;
 					break;
 				case SHIELD_KEY:
-					ShieldOn = 0;
+					ShieldOn &= ~SHIELD_MANUAL;
 					break;
 				case FIRE_KEY:
 					Shooting = 0;
@@ -631,6 +624,16 @@ Player::ExplodeSound(void)
 	sound->PlaySound(gShipHitSound, 3);
 }
 
+void
+Player::SetKidShield(bool enabled)
+{
+	if (enabled) {
+		ShieldOn |= SHIELD_KIDS;
+	} else {
+		ShieldOn &= ~SHIELD_KIDS;
+	}
+}
+
 void
 Player::SetControlType(Uint8 controlType)
 {
diff --git a/game/player.h b/game/player.h
index 359e8e3b..33095dfa 100644
--- a/game/player.h
+++ b/game/player.h
@@ -31,6 +31,10 @@
 #define LONG_RANGE	0x08
 #define LUCKY_IRISH	0x80
 
+/* Different shield modes */
+#define SHIELD_MANUAL	0x01
+#define SHIELD_KIDS	0x02
+
 class Player : public Object {
 
 public:
@@ -49,7 +53,7 @@ class Player : public Object {
 	virtual int Kicking(void) {
 		return(Playing);
 	}
-	virtual void NewGame(int lives, int deathMatch);
+	virtual void NewGame(int lives);
 	virtual void NewWave(void);
 	/* NewShip() MUST be called before Move() */
 	virtual int NewShip(void);
@@ -123,6 +127,8 @@ class Player : public Object {
 	virtual void HitSound(void);
 	virtual void ExplodeSound(void);
 
+	void SetKidShield(bool enabled);
+
 	void SetControlType(Uint8 controlType);
 	Uint8 GetControlType() {
 		return controlType;