/*
* MeteorMadness.cs
* Authors: August Zinsser
*
* Copyright Matthew Belmonte 2007
*/
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Pina3D;
using Pina3D.Particles;
using tAC_Engine;
namespace Astropolis
{
///
/// The main class for the meteor madness minigame.
/// This class should be initialized after the colonygame is saved.
/// Once meteor madness is complete, the colonygame is reloaded and the scoresheet is displayed.
/// Meteor madness consists of 2 major phases. First is the shooter phase which is a retro-style
/// cylinder-based space shooter. The second is a field of dots that move randomly with a variable
/// percentage moving coherently. The game jumps between these phases based on the mTrialScript variable.
/// Between each major phase, a small transition phase also occurs.
///
class MeteorMadness : MiniGame
{
private enum GameState { Menu, Tutorial, Game, Paused }
private GameState mGameState; // The state of the minigame
private Space3D mGameBoard; // Defines a basic 3D coordinate system, although in this game most objects are constrained to a cylinder.
private bool mInitialized; // Flag for reinitializing between phases
private bool mAllIsLost; // True when the player has lost, signalling that the game can still update logic even though the player is dead. This is mostly aesthetic.
private List mDots; // Holds all of the dots used for the dot test phase
private List mDotOpenIndices; // Indices of dead (invisible) dots
private Random random = new Random(); // Basic random number generator
private MeteorPhaseQueue mPhaseQueue; // Defines the order in which phases occur as well as the trials per phase
private string mCurrentPhase; // The current phase of the minigame
private float mMaxShooterZ; // Max distance that the shooter game objects can go from the camera
private float mMinShooterZ; // The closest that shooter game objects can get to the camera
private float mMaxDotZ; // Max distance that the dot bojects can go from the camera
private float mMinDotZ; // The closest that a dot object can get to the camera
private float mMaxDotY; // Max Y value for dots. Just tweak to an appropriate number instead of calculating it exactly.
private float mNearPlane; // A cutoff for rendering close to the camera
private float mShipZ; // The depth where the player's ship flies (constant throughout the game)
private Vector3 mDirection; // Direction of dot coherence (if any)
private int mLastNumDots; // The number of dots during the last update cycle
private float mLastDotLifespan; // Lifespan of each dot in the dot phase
private float mLastScreenPhysicalWidth; // If the screen size changes, dot calculations need to be adjusted
private float mLastScreenPhysicalHeight; // ditto
private float mLastScreenPhysicalDistance; // ditto
private float mShooterDotLifeSpan; // Lifespan of the purely aesthetic dots that fly past the ship in the shooter phase
private float mShooterSpeed; // Affects scrolling of shooter entities, which is one of the ways in which the shooter phase's difficulty is increased
private float mInitialShooterSpeed; // Scrolling speed to start with in level 1
private CheetahFighter mPlayerShip; // The cheetah fighter ship that the player controls
private int mPlayerLives; // The number of lives before the game ends
private Wormhole mPlayerWormhole; // The wormhole that spawns the cheetah fighter at the start of each shooter phase
private Wormhole mUnitWormhole; // Wormholes that spawn UFOs (there can only be one at a time)
private Flotsam mCollectible; // The powerup that UFOs leave behind either by "throwing" it to the player or dieing.
private bool mCollectibleThrown; // True if a collectible is out
private float mUnitWormholeMinInterval; // Minimum amount of seconds to elapse between the appearance of UFO wormholes
private float mUnitWormholeMaxInterval; // Maximum amount of seconds between UFO wormholes, although more time could elapse if the wormhole is waiting for meteors to get out of the way
private float mUnitWormholeCounter; // Counts the seconds between UFO wormhole appearances
private bool mUnitIsWarping; // If a UFO is currently warping through a wormhole
private bool mUnitHasWarped; // If a UFO has already warped through a wormhole (used for fading out of wormholes)
private float mUnitWarpCounter; // Used for wormhole fade in/out calculations
private bool mPlayerOpenedWormhole; // Also used for wormhole fade in/out calculations
private float mWarpInTime; // ditto
private float mShipScale; // Tweak the size of all ships
private float mWarpDelay; // Time (in arbitrary units) between coming out of warp and "moving forward" (should be at least double the amount of time it takes for a dot to go from -maxz to maxz)
private float mWarpCounter; // Counts intervals used for warping of wormholes
private List mMeteors; // Holds all the meteors in the shooter phase
private float mMeteorSpawnRate; // Average Meteors/second that are spawned
private float mMeteorSpawnCounter; // Counts seconds between meteors spawning
private float mMaxMeteorSize; // In Space3D coordinates
private float mMinMeteorSize; // In Space3D coordinates
private float mShieldRechargeRate; // Percentage / second
private float mInvulnerableTimer; // Used to prevent multiple boss rockets from hitting the player at once
private float mBossProjectileDamageCooldown; // If one boss rocket hits the player, the time that must elapse before another rocket actually damages the player
private int mMeteorDamage; // How much damage the shields take when the player hits a meteor
private int mEnemyShotDamage; // Between 0 and 100, usually
private float mDotDamage; // How much damage is taken for a wrong turn in the dot phase (in percentage)
private float mEnemySpeed; // Multiplier relative to meteor speed
private int mPlayerShotDamage; // Amount shield damage each player's shot does
private int mEnemyShields; // Starting value of enemy shields
private float mWeaponCooldownCounter; // Seconds between each player's shot
private List mProjectiles; // All projectiles fired from both enemy ships and the player
private UFO mUnit; // The units that come out of the wormholes
private Boss mBoss; // The final boss
private Emitter mFlashEmitter; // Muzzle flare for boss and player
private int mRemainingTrials; // Remaining trials for each phase
private float mFreezeTimer; // Counts down the time the player's ship remains frozen after opening a wormhole
private float mWormholeOpenFreezeTime; // The number of seconds that must elapse after opening a wormhole before the player can move again
private float mWeaponCooldownUpgrade; // The percentage to reduce the player's shot cooldown when an upgrade is collected
private float mLoseDelay; // How many seconds occur between the player dying and the score screen displaying
private bool mDoneWithEndingDialog; // If the player has finished reading the ending dialog
private Texture2D mFriendlyTexture; // Holds the image of the friendly ship
private Texture2D mBogieTexture; // Holds the image of the enemy ship
private Texture2D mFriendlySchematic; // '' (schematic)
private Texture2D mBogieSchematic; // ''
private Texture2D mDotTexture; // '' dot
private float mWormholeConnectionBeamCooldown; // Minimum seconds that must elapse between firings of the wormhole connection beam
private float mWormholeConnectionBeamCounter; // Seconds that have elapsed since the last firing of the wormhole connection beam
private bool mLeftArrowWasPressedLastCycle; // Used to prevent overcounting of responses in the dot phase
private bool mRightArrowWasPressedLastCycle; // ditto
private bool mScrollingStarField; // Signals if stars should scroll by quickly to give the player a sense of flying through space
private float mStarSpawnCounter; // Keeps track of the time until spawning a new star for scrolling
private float mStarSpawnCooldown; // The average interval between each spawning of a star
private List mStars; // Holds all scrolling stars in the shooter phase
private float mPlayerJumpCountdown; // Counts down until the player jumps into the wormhole for the shooter to dot transition phase
private float mDotTestBeginDelay; // How long to wait before starting the first trial
private float mDotTrialLength; // The average length of a dot test trial
private float mDotTrialJitter; // The maximum amount of seconds to randomly add to each dot trial
private float mDotTrialCountdown; // The time remaining in the current dot test
private bool mDotCoherence; // False on trials where dots are not correlated at all
private bool mFirstDotTrial; // True for the first dot trial in a phase to prevent logging it or including it in PEST
private float mShooterDifficulty; // Value that scales up the shooter speed, amount of meteors that spawn, and rate of fire of AI
private float mShooterDifficultyIncrease; // Amount to increase the difficulty of each successive shooter hpase
private float mInitialWeaponCooldown; // For the player (in seconds)
private BestPEST mPester; // Used to calculate the trial values in the dot phase
private int mCurrentLevel; // Tells the user what level they are on
private Entity mSpace3DCollider1; // Used to collide entities whose position/size are in space3d coordinates
private Entity mSpace3DCollider2; // ...
private int mMaxGimmeDelay; // Give the player a "Gimme Trial" every so often in the dot phase
private int mGimmeCounter; // ''
private bool mWaitingForNextTrial; // True indicates that a new dot trial should start as soon as the HUD is stable
private float mFakeCoherence; // Used to "fade" the dot coherence shift right after a turn
private Vector3 mFakeDirection; // ''
private float mNewRoundWaitTimer; // Wait at the beginning of a round before doing anything
private float mVictoryTimer; // Let the player revel in victory for a couple seconds before ending the game
private float mFlyAwayTimer; // Time before the ship flies off in the distance
private int mCollectedUpgrades; // The number of weapon upgrades collected
private float mJackpotChance; // Chance of a meteor exploding into a jackpot of credits
private float mLuckyChance; // Chance of a meteor exploding into a small reward of credits
private List mJackpotCollectibles; // The reward spawned on a jackpot
private float mDisplayWaitTimer; // Used to prevent a flood of "+Credits" messages
private float mDisplayWaitTime; // ''
private Cockpit mCockpit; // HUD element
private BossHealthBar2D mBossMeter; // ''
private HealthBar2D mPlayerHealthBar; // ''
private bool mPlayerHealthAboveLastTime; // ''
private float mPlayerHealthTimer; // ''
private float mPlayerHealthTime; // ''
private float mHoldCoherenceTimer; // Holds the coherence for extra time to allow better EEG readings
private Entity mMenuBackground; // The background image for the main menu
private Button mStartButton; // For the main menu
private Button mTutorialButton; // ''
private Button mBackButton; // ''
private bool mLRPressed; // Used by the tutorial script
private bool mAllowTurns; // ''
private bool mAllowFire; // ''
private bool mAllowConnect; // ''
private bool mForceFriendlySpawn; // ''
private bool mForceBogieSpawn; // ''
private bool mReadyForID; // Used by ShipIdentifier
private bool mEnemyOnLeft; // ''
private Entity mEnemyID; // ''
private Entity mFriendlyID; // ''
private Entity mArrowID; // ''
private Emitter mSplosionID1; // ''
private Emitter mSplosionID2; // ''
private float mArrowLeftRot; // ''
private float mArrowRightRot; // ''
private float mArrowAngularVel; // ''
private int mArrowSelected; // ''
private float mTimerID; // ''
public float WeaponCooldownUpgrade { set { mWeaponCooldownUpgrade = value; } get { return mWeaponCooldownUpgrade; } }
public Space3D GameBoard { get { return mGameBoard; } }
public int CollectedUpgrades { set { mCollectedUpgrades = value; } get { return mCollectedUpgrades; } }
public List Projectiles { set { mProjectiles = value; } get { return mProjectiles; } }
public float ShooterSpeed { get { return mShooterSpeed; } }
public float DisplayWaitTimer { get { return mDisplayWaitTimer; } }
/*
* Functions used by the tutorial
*/
[LuaCallback("GetFriendlySchematic")]
public Texture2D GetFriendlySchematic()
{
return mFriendlySchematic;
}
[LuaCallback("GetEnemySchematic")]
public Texture2D GetEnemySchematic()
{
return mBogieSchematic;
}
[LuaCallback("MMSetPhase")]
public void SetGamePhase(string phaseName)
{
mCurrentPhase = phaseName;
}
[LuaCallback("MMSetDrift")]
public void SetDrift(string direction)
{
AstroBaseApplication.GameManager.DotCoherencePercentage = .75f;
if (direction.ToLower() == "right")
mDirection = Vector3.UnitX;
else if (direction.ToLower() == "left")
mDirection = -Vector3.UnitX;
else
AstroBaseApplication.GameManager.DotCoherencePercentage = 0f;
}
[LuaCallback("MMTurn")]
public void ForceTurn(string direction)
{
if (direction.ToLower() == "right")
mCockpit.TurnRight();
else if (direction.ToLower() == "left")
mCockpit.TurnLeft();
}
[LuaCallback("MMClearKeyWaitList")]
public void ClearLRPressed()
{
mLRPressed = false;
}
[LuaCallback("MMOnlyAllowTurns")]
public void DisallowFireAndConnect()
{
mAllowFire = false;
mAllowConnect = false;
}
[LuaCallback("MMAllowTurns")]
public void AllowTurns()
{
mAllowTurns = true;
}
[LuaCallback("MMDisallowTurns")]
public void DisallowTurns()
{
mAllowTurns = false;
}
[LuaCallback("MMAllowFire")]
public void AllowFire()
{
mAllowFire = true;
}
[LuaCallback("MMAllowConnect")]
public void AllowConnect()
{
mAllowConnect = true;
}
[LuaCallback("MMSpawnMeteors")]
public void SpawnMeteors(int amount)
{
for (int i = 0; i < amount; i++)
{
// Randomize the meteor's appearance
float newMeteorSize = (mMinMeteorSize + (mMaxMeteorSize - mMinMeteorSize) * (float)random.NextDouble());
// Spawn a new meteor in an area that is not close to a unit wormhole
Meteor newMeteor = new Meteor(newMeteorSize, newMeteorSize, newMeteorSize, mShooterSpeed, mMaxShooterZ, -mMaxShooterZ);
newMeteor.FadeIn(4);
newMeteor.Z -= (i * 0.25f);
mMeteors.Add(newMeteor);
mGameBoard.AddEntity(newMeteor);
}
}
///
/// Spawn a Wormhole that spawns a Unit.
///
/// The Rotation in pies.
[LuaCallback("MMSpawnUnitWormhole")]
public void SpawnUnitWormhole(float positionMultiplier)
{
// It's time for a new wormhole
float arcPosition = (float)Math.PI * positionMultiplier;
mGameBoard.RemoveEntity(mUnitWormhole);
mUnitWormhole = new Wormhole(
mShipScale * .9f,
mShipScale * .9f,
0f,
arcPosition,
false);
Vector2 PositionFromOrigin = new Vector2((float)Math.Cos(arcPosition), (float)Math.Sin(arcPosition));
mUnitWormhole.X = .8F * PositionFromOrigin.X;
mUnitWormhole.Y = .8F * PositionFromOrigin.Y;
mUnitWormhole.Z = mMaxShooterZ - .1f;
mUnitWormhole.Visible = false;
mUnitIsWarping = false;
mUnitHasWarped = false;
mPlayerOpenedWormhole = false;
mUnitWormhole.FadeIn(mWarpInTime);
mGameBoard.AddEntity(mUnitWormhole);
}
[LuaCallback("MMSpawnFriendly")]
public void SpawnFriendly()
{
mUnitIsWarping = true;
mForceFriendlySpawn = true;
mForceBogieSpawn = false;
mUnitWormholeCounter = 2f;
if (mUnit != null)
mGameBoard.RemoveEntity(mUnit);
}
[LuaCallback("MMSpawnEnemy")]
public void SpawnBogie()
{
mUnitIsWarping = true;
mForceBogieSpawn = true;
mForceFriendlySpawn = false;
mUnitWormholeCounter = 2f;
if (mUnit != null)
mGameBoard.RemoveEntity(mUnit);
}
[LuaCallback("MMSpawnPlayerWormhole")]
public void SpawnPlayerWormhole()
{
mPlayerWormhole = new Wormhole(
1F * mShipScale,
1F * mShipScale,
0F,
(float)mPlayerShip.ArcPosition,
true);
mPlayerWormhole.X = mPlayerShip.X;
mPlayerWormhole.Y = mPlayerShip.Y;
mPlayerWormhole.Z = mMaxShooterZ;
mGameBoard.AddEntity(mPlayerWormhole);
mPlayerWormhole.FadeIn(mWarpDelay);
mPlayerJumpCountdown = mWarpDelay;
}
[LuaCallback("MMStartBoss")]
public void StartBoss()
{
mBoss.Start();
}
[LuaCallback("MMDoneWithEndingDialog")]
public void DoneWithEndingDialog()
{
mDoneWithEndingDialog = true;
}
[LuaCallback("GoToMMMenu")]
public void SetGameStateToMenu()
{
// TODO: gracefully return to the main menu when the main gameflow supports it
//mGameState = GameState.Menu;
GenericBaseApplication.GameManager.Exit();
}
[LuaCallback("MMReadyForShipID")]
public void ReadyForShipIdentityTest()
{
mReadyForID = true;
}
/*
* Regular game functions
*/
///
/// Constructor
///
public MeteorMadness()
{
// Basic game initialization
Sparx.Flush();
HUD.ShowMouseCursor = false;
mGameBoard = new Space3D();
mInitialized = false;
mAllIsLost = false;
Logger.LogCode(Logger.ExperimentalCode.MM_GameBegin);
LuaHelper.RegisterLuaCallbacks(this);
// Get the phase queue from the config file
mPhaseQueue = new MeteorPhaseQueue(GenericBaseApplication.GameManager.GetPhaseQueueForMiniGame("MeteorMadness"));
mGameState = GameState.Menu;
// Initialize game params
mDots = new List();
mDotOpenIndices = new List();
mShipScale = .5F;
mMaxShooterZ = mGameBoard.MaxZ = 5.0F;
mMinShooterZ = mGameBoard.MinZ = -1.0F;
mMaxDotZ = 6F; // Adjusted manually to ensure that dots do not get too large or small
mMinDotZ = 1F; // Also adjusted manually
mMaxDotY = 4F;
mNearPlane = -1.0F;
mDirection = Vector3.Zero;
// I believe a value of 0.75f is incorrect, as it causes a Trial to be recorded at the beginning without user input - should be 0;
//AstroBaseApplication.GameManager.DotCoherencePercentage = 0.75f;
mLastDotLifespan = AstroBaseApplication.GameManager.DotLifeSpan;
mLastNumDots = -1;
mLastScreenPhysicalWidth = AstroBaseApplication.GameManager.ScreenPhysicalWidth;
mLastScreenPhysicalHeight = AstroBaseApplication.GameManager.ScreenPhysicalHeight;
mLastScreenPhysicalDistance = AstroBaseApplication.GameManager.ScreenPhysicalDistance;
mInitialShooterSpeed = .5f;
mShooterDotLifeSpan = (mMaxShooterZ * 2 + AstroBaseApplication.GameManager.MeteorCameraFocalLength) / mShooterSpeed;
mWarpDelay = 4F;
mMeteors = new List();
mMeteorSpawnRate = .75F;
mMeteorSpawnCounter = 0F;
mMaxMeteorSize = .5F;
mMinMeteorSize = .1F;
mShipZ = 0F;
mShieldRechargeRate = .01F;
mMeteorDamage = 10;
mEnemyShotDamage = 10;
mPlayerShotDamage = 10;
mDotDamage = .1f;
mWeaponCooldownCounter = 0;
mProjectiles = new List();
mUnitWormholeMinInterval = 5f;
mUnitWormholeMaxInterval = 10f;
mUnitWormholeCounter = mUnitWormholeMaxInterval;
mUnitIsWarping = false;
mUnitHasWarped = false;
mUnitWarpCounter = 0f;
mPlayerOpenedWormhole = false;
mWarpInTime = 3f;
mUnit = null;
mBoss = null;
mEnemyShields = 75;
mEnemySpeed = .5f;
mFreezeTimer = 0;
mWormholeOpenFreezeTime = 6f;
mWeaponCooldownUpgrade = 1f;
mLoseDelay = 10f;
mDoneWithEndingDialog = false;
mDotTexture = TextureManager.Load(@"content\MiniGames\Whitedot");
mWormholeConnectionBeamCooldown = .5f;
mStarSpawnCooldown = .05f;
mStars = new List();
mDotTrialCountdown = 0f;
mDotTrialLength = 2f;
mDotTrialJitter = .25f;
mShooterDifficulty = 1f;
mShooterDifficultyIncrease = .5f;
mInitialWeaponCooldown = .25f; // powerup grants *= .75
AstroBaseApplication.GameManager.DotWeaponCooldown = mInitialWeaponCooldown;
mPester = new BestPEST();
mCurrentLevel = 1;
mSpace3DCollider1 = new Entity();
mSpace3DCollider2 = new Entity();
mMaxGimmeDelay = 6;
mGimmeCounter = 0;
mWaitingForNextTrial = false;
mNewRoundWaitTimer = 0f;
mHoldCoherenceTimer = 0;
mCockpit = new Cockpit();
mCockpit.Visible = false;
HUD.Add(mCockpit);
mBossMeter = new BossHealthBar2D(
"BOSS",
new Rectangle(
(int)(GenericBaseApplication.GameManager.ScreenWidth * .50f),
(int)(GenericBaseApplication.GameManager.ScreenHeight * .05f),
(int)(GenericBaseApplication.GameManager.ScreenWidth * .90f),
(int)(GenericBaseApplication.GameManager.ScreenHeight * .05f)),
TextureManager.Load(@"Content\MiniGames\MMBossHealthbar"),
new Color(0, 255, 255),
new Color(196, 196, 196),
new Color(246, 167, 0));
mBossMeter.Visible = false;
HUD.Add(mBossMeter);
mMenuBackground = new Entity(TextureManager.Load(@"Content\MiniGames\MMMenuBackground"));
mStartButton = new Button(
TextureManager.Load(@"Content\MiniGames\MMStartButton"),
TextureManager.Load(@"Content\MiniGames\MMStartButtonOn"));
mTutorialButton = new Button(
TextureManager.Load(@"Content\MiniGames\MMTutorialButton"),
TextureManager.Load(@"Content\MiniGames\MMTutorialButtonOn"));
mBackButton = new Button(
TextureManager.Load(@"Content\MiniGames\MMBackButton"),
TextureManager.Load(@"Content\MiniGames\MMBackButtonOn"));
int screenWidth = GenericBaseApplication.GameManager.ScreenWidth;
int screenHeight = GenericBaseApplication.GameManager.ScreenHeight;
mMenuBackground.Size = new Vector3(screenWidth, screenHeight, 0);
mMenuBackground.Position = new Vector3(screenWidth / 2, screenHeight / 2, 0);
mStartButton.Size = new Vector3(screenWidth * .22f, screenHeight * .09f, 0f);
mTutorialButton.Size = mStartButton.Size;
mBackButton.Size = mStartButton.Size;
mStartButton.Position = new Vector3(screenWidth * .12f, screenHeight * .935f, 0f);
mTutorialButton.Position = mStartButton.Position + new Vector3(screenWidth * .24f, 0f, 0f);
mBackButton.Position = mTutorialButton.Position + new Vector3(screenWidth * .24f, 0f, 0f);
mAllowTurns = true;
mAllowFire = true;
mAllowConnect = true;
mForceFriendlySpawn = false;
mForceBogieSpawn = false;
mReadyForID = false;
mArrowLeftRot = (float)Math.PI * -1f / 4f;
mArrowRightRot = (float)Math.PI * 1f / 4f;
mPlayerHealthBar = new HealthBar2D(0, 0, (int)(screenWidth * .10f), (int)(Math.Max(screenHeight * .01f, 4)));
mPlayerHealthBar.BorderColor = Color.Gray;
mPlayerHealthBar.Visible = false;
HUD.Add(mPlayerHealthBar);
mPlayerHealthTimer = 0f;
mPlayerHealthTime = 8f;
mPlayerLives = 3;
mBossProjectileDamageCooldown = .2f;
mFlashEmitter = Sparx.LoadParticleEffect(@"Content\Particle Effects\Flash.spx");
mFlashEmitter.RenderScale = .1f;
mJackpotChance = .075f;
mLuckyChance = .2f;
mJackpotCollectibles = new List();
mDisplayWaitTime = .25f;
mDisplayWaitTimer = 0f;
// Randomize the friendly/enemy ships
List shipTextures = new List();
List schematicTextures = new List();
shipTextures.Add(TextureManager.Load(@"Content\MiniGames\Drone"));
schematicTextures.Add(TextureManager.Load(@"Content\MiniGames\MMDroneSchematic"));
shipTextures.Add(TextureManager.Load(@"Content\MiniGames\Wasp"));
schematicTextures.Add(TextureManager.Load(@"Content\MiniGames\MMWaspSchematic"));
int firstIndex = GenericBaseApplication.GameManager.RandomNumber(0, shipTextures.Count - 1);
int secondIndex = GenericBaseApplication.GameManager.RandomNumber(0, shipTextures.Count - 1);
while (firstIndex == secondIndex)
secondIndex = GenericBaseApplication.GameManager.RandomNumber(0, shipTextures.Count - 1);
mFriendlyTexture = shipTextures[firstIndex];
mFriendlySchematic = schematicTextures[firstIndex];
mBogieTexture = shipTextures[secondIndex];
mBogieSchematic = schematicTextures[secondIndex];
}
///
/// Transitions the game into the next phase or ends the game and displays the score
///
private void CompletePhase()
{
if (mPhaseQueue.Count <= 0)
{
// We are out of trials, which means the player has beaten all of the levels so declare this a successful ending
Logger.LogCode(Logger.ExperimentalCode.MM_GameEndSuccess);
Sparx.Flush();
Score.Gather(AstroResources.Bonus, 500);
Score.Display(true, "MISSION ACCOMPLISHED!");
Score.CashOut(AstroBaseApplication.Game);
HUD.Clear();
HUD.ShowMouseCursor = true;
AstroBaseApplication.SwitchMode(AstroBaseApplication.Modes.ModeSelector);
// Lock the controls, relying on the score display to unlock them
InputState.Locked = true;
// Record the range-adjusted coherence
Logger.LogCode(Logger.ExperimentalCode.MM_DotCoherenceEstimate, "coh=" + (float)(mPester.NextValue() + 1) * .5f);
Logger.FlushLog();
}
else
{
// Update Trial script
string lastPhase = mCurrentPhase;
MeteorTest curTest = mPhaseQueue.Dequeue();
mCurrentPhase = curTest.PhaseName;
mRemainingTrials = curTest.Trials;
// Signal the appropriate transition to take place in the next several update cycles
if (mCurrentPhase == MeteorPhases.DotTest)
{
Logger.LogCode(Logger.ExperimentalCode.MM_DotPhaseBegin);
if (lastPhase == MeteorPhases.Shooter)
mCurrentPhase = MeteorPhases.Shooter2DotTestTransition;
}
else if (mCurrentPhase == MeteorPhases.Shooter)
{
Logger.LogCode(Logger.ExperimentalCode.MM_ShooterPhaseBegin);
if (lastPhase == MeteorPhases.DotTest)
mCurrentPhase = MeteorPhases.DotTest2ShooterTransition;
else
mCurrentPhase = MeteorPhases.Shooter;
}
else if (mCurrentPhase == MeteorPhases.Boss)
{
Logger.LogCode(Logger.ExperimentalCode.MM_BossPhaseBegin);
mCurrentPhase = MeteorPhases.Shooter2BossTransition;
}
Logger.FlushLog();
}
}
///
/// Resets the timer which must countdown before jackpot "+Credits" are displayed
///
public void ResetDisplayWaitTimer()
{
mDisplayWaitTimer = mDisplayWaitTime;
}
///
/// Let the game continue to run in the background for a while before going back to the colony mode or respawn the ship
///
private void GotoAfterlife()
{
// Die only once per death
if (!mAllIsLost)
{
DestroyPlayer();
mPlayerLives--;
}
// Respawn the player?
if (mPlayerLives > 0)
{
AstroBaseApplication.GameManager.DotShields = 1f;
mAllIsLost = false;
mInitialized = false;
mNewRoundWaitTimer = 4f;
mPlayerHealthTimer = -2f;
mGameBoard.RemoveEntity(mPlayerShip);
mPlayerShip = null;
}
else if (!mAllIsLost)
{
// Lose the game once
mAllIsLost = true;
mPlayerHealthTimer = -2f;
mGameBoard.RemoveEntity(mPlayerShip);
mPlayerShip = null;
GenericBaseApplication.GameManager.InterpretLuaFile(@"Astropolis\MeteorMadness\Defeat.lua");
}
}
///
/// Let the game continue to run in the background for a while before going back to the colony mode
///
private void LoseGameFromDotTest()
{
// Lose only once
if (!mAllIsLost)
{
CrashInSubspace();
mPlayerLives--;
}
// Respawn the player?
if (mPlayerLives > 0)
{
AstroBaseApplication.GameManager.DotShields = 1f;
mAllIsLost = false;
mInitialized = false;
CompletePhase();
}
else if (!mAllIsLost)
{
// Lose the game once
mAllIsLost = true;
GenericBaseApplication.GameManager.InterpretLuaFile(@"Astropolis\MeteorMadness\Defeat.lua");
}
}
///
/// Perform necessary logic updates
///
///
public override void Update()
{
float dt = AstroBaseApplication.GameManager.ElapsedSeconds;
if (mGameState == GameState.Menu)
{
SoundManager.SetMusic("Maritime Defender Theme");
HUD.ShowMouseCursor = true;
mStartButton.Update();
mTutorialButton.Update();
mBackButton.Update();
if (mStartButton.IsLeftClicked || (InputState.IsKeyDown(Keys.Enter) && !InputState.WasKeyDown(Keys.Enter)))
{
// Start the first phase
MeteorTest curTest = mPhaseQueue.Dequeue();
mCurrentPhase = curTest.PhaseName;
mRemainingTrials = curTest.Trials;
if (mCurrentPhase == MeteorPhases.DotTest)
Logger.LogCode(Logger.ExperimentalCode.MM_DotPhaseBegin);
else if (mCurrentPhase == MeteorPhases.Shooter)
Logger.LogCode(Logger.ExperimentalCode.MM_ShooterPhaseBegin);
GenericBaseApplication.GameManager.InterpretLuaFile(@"Astropolis/MeteorMadness/ShipIdentifier");
mGameState = GameState.Game;
HUD.ShowMouseCursor = false;
}
if (mTutorialButton.IsLeftClicked || (InputState.IsKeyDown(Keys.T) && !InputState.WasKeyDown(Keys.T)))
{
// Begin the tutorial
GenericBaseApplication.GameManager.InterpretLuaFile(@"Astropolis/MeteorMadness/Tutorial");
mGameState = GameState.Tutorial;
}
if (mBackButton.IsLeftClicked || (InputState.IsKeyDown(Keys.Escape) && !InputState.WasKeyDown(Keys.Escape)))
{
// TODO: when gameflow between the main game and minigames exist, go back to main menu
// but for now, just quit
//GenericBaseApplication.GameManager.Exit();
// Go back to the main menu
AstroBaseApplication.SwitchMode(AstroBaseApplication.Modes.ModeSelector);
}
}
if (mGameState == GameState.Game || mGameState == GameState.Tutorial)
{
#region common
mNewRoundWaitTimer -= dt;
mPlayerHealthTimer -= dt;
mInvulnerableTimer -= dt;
mDisplayWaitTimer -= dt;
if (mGameState == GameState.Game && mRemainingTrials <= 0 && mCurrentPhase != MeteorPhases.Boss)
CompletePhase();
// Handle losing game transition
if (mAllIsLost)
{
mLoseDelay -= dt;
if (mLoseDelay < 0 && mDoneWithEndingDialog)
{
Logger.LogCode(Logger.ExperimentalCode.MM_GameEndFailure);
Sparx.Flush();
Score.Display(false, "SENDING REINFORCEMENTS");
Score.CashOut(AstroBaseApplication.Game);
HUD.Clear();
HUD.ShowMouseCursor = true;
AstroBaseApplication.SwitchMode(AstroBaseApplication.Modes.ModeSelector);
// Lock the controls, relying on the score display to unlock them
InputState.Locked = true;
// Record the range-adjusted coherence
Logger.LogCode(Logger.ExperimentalCode.MM_DotCoherenceEstimate, "coh=" + (float)(mPester.NextValue() + 1) * .5f);
Logger.FlushLog();
}
}
float distanceToMove = 0;
List dotDeathRow = new List();
List meteorDeathRow = new List();
List projectileDeathRow = new List();
// Mark dead things for removal
for (int i = 0; i < mDots.Count; i++)
{
Dot dot = mDots[i];
dot.LifeSpan -= dt;
if (dot.LifeSpan < 0)
{
dot.Visible = false;
if (mDotOpenIndices.BinarySearch(i) < 0)
{
mDotOpenIndices.Add(i);
}
}
}
mDotOpenIndices.Sort();
foreach (Meteor meteor in mMeteors)
{
if (!meteor.Alive)
meteorDeathRow.Add(meteor);
}
foreach (PhotonTorpedo projectile in mProjectiles)
{
if (!projectile.Alive)
projectileDeathRow.Add(projectile);
}
// Remove dead things
foreach (Meteor corpse in meteorDeathRow)
{
mMeteors.Remove(corpse);
mGameBoard.RemoveEntity(corpse);
}
foreach (PhotonTorpedo corpse in projectileDeathRow)
{
mProjectiles.Remove(corpse);
mGameBoard.RemoveEntity(corpse);
}
// Update Shields
if (AstroBaseApplication.GameManager.DotShields < 1.0F)
AstroBaseApplication.GameManager.DotShields += mShieldRechargeRate * dt;
mPlayerHealthBar.Life = AstroBaseApplication.GameManager.DotShields;
if (mPlayerHealthTimer > 0f)
{
if (mPlayerWormhole != null && mPlayerWormhole.Visible)
mPlayerHealthBar.Opacity = 0f;
else
mPlayerHealthBar.Opacity = 1f;
mPlayerHealthBar.Visible = true;
}
else
{
// fade?
if (mPlayerHealthTimer > -1f)
mPlayerHealthBar.Opacity = 1 + mPlayerHealthTimer;
else
mPlayerHealthBar.Visible = false;
}
// Udpate and kill old scrolling stars
List starCorpses = new List();
foreach (ProjectedEmitter star in mStars)
{
// Project from Space3D coordinates to screen coordinates
star.Space3DCoords = new Vector3(star.Space3DCoords.X, star.Space3DCoords.Y, star.Space3DCoords.Z - mInitialShooterSpeed * .5f);
Rectangle proj = Space3D.Project(star.Space3DCoords.X, star.Space3DCoords.Y, star.Space3DCoords.Z, 1f, 1f, mMinShooterZ);
star.Emitter.Transform = Matrix.CreateTranslation(new Vector3(proj.X, proj.Y, 1f));
star.Emitter.RenderScale = proj.Width * .002f;
if (!star.Emitter.Alive || star.Space3DCoords.Z < mMinShooterZ)
starCorpses.Add(star);
}
foreach (ProjectedEmitter corpse in starCorpses)
mStars.Remove(corpse);
// Check if we need new scrolling stars
mStarSpawnCounter -= dt;
if (mStarSpawnCounter < 0 && mScrollingStarField)
{
// Spawn a new star, performing necessary coordinate conversions
// TODO (awz): Clone one star instead of relaoding it each time
Emitter starEmitter = Sparx.LoadParticleEffect(@"Content\Particle Effects\ShooterStar.spx");
starEmitter.Transform = Matrix.CreateTranslation(200, 200, 0);
ProjectedEmitter newStar = new ProjectedEmitter(ref starEmitter);
double arcPosition = random.NextDouble() * Math.PI * 2;
newStar.Space3DCoords = new Vector3((float)Math.Cos(arcPosition), (float)Math.Sin(arcPosition), mMaxShooterZ * 3f);
mStars.Add(newStar);
Sparx.AddEmitter(newStar.Emitter);
mStarSpawnCounter = (float)random.NextDouble() * mStarSpawnCooldown * 2;
}
// Update scrolling meteors
foreach (Meteor meteor in mMeteors)
{
meteor.Update();
}
// Update "the unit" (the UFO that may have come out of a wormhole)
if (mUnit != null)
{
if (!mUnit.Alive)
{
// See if the player has killed a friendly before it could give him/her a bonus
if (mUnit is Friendly && !mCollectibleThrown)
{
mRemainingTrials--;
}
// If it was an enemy, then leave some (very sturdy) money behind
if (mUnit is SpacePirate)
{
mGameBoard.RemoveEntity(mCollectible);
mCollectible = new Flotsam(AstroResources.Credits, 100, TextureManager.Load(@"Content\General\Credits"), .1F * mShipScale, .1F * mShipScale, mShipScale);
mCollectible.HitPoints = 100;
mCollectible.Position = mUnit.Position;
mCollectible.dZ = mUnit.dZ * 2f;
mGameBoard.AddEntity(mCollectible);
}
// Remove the unit from the game
mGameBoard.RemoveEntity(mUnit);
mUnit = null;
}
else
{
mUnit.Update();
bool itShootsAtYou = mUnit.React(mPlayerShip);
if (itShootsAtYou)
{
if (mUnit is SpacePirate && mUnit.Z > mPlayerShip.Z)
{
// Space pirates try to kill the player by shooting a photon torpedo
SoundManager.PlayEffect("Pew");
PhotonTorpedo newPhoton = new PhotonTorpedo(true, Color.Violet, 0, mUnit.Position, mUnit.Width * .1f, mUnit.Width * .1f, mUnit.Width * .1f, mShooterSpeed * -10, mMaxShooterZ - .11f, mNearPlane);
newPhoton.Friendly = false;
newPhoton.Damage = mEnemyShotDamage;
newPhoton.Tint = new Color(255, 100, 100, 128);
mProjectiles.Add(newPhoton);
mGameBoard.AddEntity(newPhoton);
Rectangle screenCoords = Space3D.Project(newPhoton.X, newPhoton.Y, newPhoton.Z, newPhoton.Width, newPhoton.Height, mMinShooterZ);
Emitter flash = (Emitter)mFlashEmitter.Clone();
flash.RegularEmissionType.Modifiers[0].FinalTint = Color.Violet;
flash.Position2D = new Vector2(screenCoords.X, screenCoords.Y);
Sparx.AddEmitter(flash);
Logger.LogCode(Logger.ExperimentalCode.MM_ShooterEnemyWeaponFired);
}
else if (mUnit is Friendly)
{
// Friendlies shoot powerups at the player
mGameBoard.RemoveEntity(mCollectible);
if (mCollectedUpgrades < mCurrentLevel && GenericBaseApplication.GameManager.RandomNumber() < .75f)
mCollectible = new Flotsam("", 100, TextureManager.Load(@"Content\MiniGames\Crate"), .3F * mShipScale, .3F * mShipScale, mShipScale);
else
mCollectible = new Flotsam(AstroResources.Carbon, 100, TextureManager.Load(@"Content\MiniGames\Crate"), .3F * mShipScale, .3F * mShipScale, mShipScale);
mCollectible.Position = mUnit.Position;
mCollectible.dZ = mUnit.dZ * 10f;
mGameBoard.AddEntity(mCollectible);
mCollectibleThrown = true;
Logger.LogCode(Logger.ExperimentalCode.MM_ShooterCollectibleSpawned);
}
}
// Check for player-enemy collision (do not collide with friendlies)
if (mPlayerShip != null)
{
mSpace3DCollider1.Position = mUnit.Position;
mSpace3DCollider1.Size = mUnit.Size;
mSpace3DCollider2.Position = mPlayerShip.Position;
mSpace3DCollider2.Size = mPlayerShip.Size;
if (Entity.Collides3D(mSpace3DCollider1, mSpace3DCollider2) && mPlayerShip.Visible && mUnit is SpacePirate)
{
mUnit.CollideWithPlayer(mPlayerShip);
}
}
// See if the ship has passed the player (ending the trial)
if (mUnit.Position.Z < mMinShooterZ * 4)
{
// Friendly trials end when the powerup dies, but enemies may fly off the screen without getting killed
if (mUnit is SpacePirate)
mRemainingTrials--;
mGameBoard.RemoveEntity(mUnit);
mUnit = null;
}
}
}
// Update the player wormhole
if (mPlayerWormhole != null)
mPlayerWormhole.Update();
if (mWarpCounter < 0 && mPlayerShip != null)
{
mWarpCounter = 0;
mPlayerShip.FadeIn(.5F);
if (mPlayerWormhole != null)
mPlayerWormhole.FadeOut(mWarpDelay / 4);
}
else if (mWarpCounter > 0)
{
mWarpCounter -= dt;
}
// Update any jackpot coins from exploded meteors
for (int i = mJackpotCollectibles.Count - 1; i >= 0; i--)
{
Flotsam coin = mJackpotCollectibles[i];
if (coin.Alive)
{
// Update the collectible's position and test for collision
coin.Update(mPlayerShip);
if (mPlayerShip != null)
{
mSpace3DCollider1.Position = coin.Position;
mSpace3DCollider1.Size = coin.Size;
mSpace3DCollider2.Position = mPlayerShip.Position;
mSpace3DCollider2.Size = mPlayerShip.Size;
if (coin.Z < mMinShooterZ)
coin.Alive = false;
if (Entity.Collides3D(mSpace3DCollider1, mSpace3DCollider2) && mPlayerShip.Visible)
{
coin.CollideWithPlayer(mPlayerShip);
}
}
else
{
// No ship, so just get rid of the coins
coin.Alive = false;
}
}
else
{
// Coin has been collected so remove it from the game
mGameBoard.RemoveEntity(coin);
mJackpotCollectibles.Remove(coin);
}
}
// Camera calculations
float trapezoidBase = mMaxShooterZ * (AstroBaseApplication.GameManager.MeteorCameraFocalLength + 2.0F * mMaxShooterZ) / (AstroBaseApplication.GameManager.MeteorCameraFocalLength + mMaxShooterZ);
float screenRatio = AstroBaseApplication.GameManager.ScreenWidth / AstroBaseApplication.GameManager.ScreenHeight;
float diameter = AstroBaseApplication.GameManager.DotDiameter;
mHoldCoherenceTimer -= dt;
mDotTestBeginDelay -= dt;
#endregion
// Update the appropriate phase of the game
if (mCurrentPhase == MeteorPhases.DotTest)
{
#region DotTest
// Adjust the dots' intervals if lifespan, screen dimensions or numdots was changed
if (AstroBaseApplication.GameManager.NumDots != mLastNumDots ||
AstroBaseApplication.GameManager.DotLifeSpan != mLastDotLifespan ||
AstroBaseApplication.GameManager.ScreenPhysicalWidth != mLastScreenPhysicalWidth ||
AstroBaseApplication.GameManager.ScreenPhysicalHeight != mLastScreenPhysicalHeight ||
AstroBaseApplication.GameManager.ScreenPhysicalDistance != mLastScreenPhysicalDistance)
{
mInitialized = false;
mLastScreenPhysicalDistance = AstroBaseApplication.GameManager.ScreenPhysicalDistance;
mLastScreenPhysicalWidth = AstroBaseApplication.GameManager.ScreenPhysicalWidth;
mLastScreenPhysicalHeight = AstroBaseApplication.GameManager.ScreenPhysicalHeight;
// Calculate the dot density so that 200 dots fall within 8x8 degress of visual angle (10258.8 dots per square radian)
mLastNumDots = (int)(10258.8 * 4 * (Math.Atan(mLastScreenPhysicalWidth / (2 * mLastScreenPhysicalDistance))) * (Math.Atan(mLastScreenPhysicalHeight / (2 * mLastScreenPhysicalDistance))));
AstroBaseApplication.GameManager.NumDots = mLastNumDots;
mLastDotLifespan = AstroBaseApplication.GameManager.DotLifeSpan;
}
// Reinitialize stuff
if (!mInitialized && mNewRoundWaitTimer < 0f)
{
if (mGameState == GameState.Game)
{
// Setup logging parameters
InputState.DecodeAll();
InputState.Encode(Keys.Left, InputState.KeyState.Down, Logger.ExperimentalCode.MM_DotTrialUserRespondLeft);
InputState.Encode(Keys.Right, InputState.KeyState.Down, Logger.ExperimentalCode.MM_DotTrialUserRespondRight);
}
Sparx.Flush();
// Set the gameboard's near and far clipping planes to that of the dot phase
mGameBoard.MinZ = mMinDotZ;
mGameBoard.MaxZ = mMaxDotZ;
// Refresh any old playerships or player wormholes
if (mPlayerShip != null)
mGameBoard.RemoveEntity(mPlayerShip);
if (mPlayerWormhole != null)
mGameBoard.RemoveEntity(mPlayerWormhole);
//Space out the dots' lifespans so they appear at regular intervals
float interval = AstroBaseApplication.GameManager.DotLifeSpan / AstroBaseApplication.GameManager.NumDots;
float secondCounter = 0;
for (int i = 0; i < mDots.Count; i++)
{
mDots[i].LifeSpan = secondCounter;
secondCounter += interval;
}
// Adjust the HUD
mCockpit.Visible = true;
mFirstDotTrial = true;
mInitialized = true;
}
// Test for death
if (AstroBaseApplication.GameManager.DotShields < 0)
{
LoseGameFromDotTest();
mCockpit.Visible = false;
}
// See if a trial should begin
if (mWaitingForNextTrial && mDotTestBeginDelay < 0)
{
if (mHoldCoherenceTimer <= 0)
{
// "Fade" the dot drift back to center
mFakeCoherence *= .90f;
if (mCockpit.IsStable())
{
// Begin the next trial
mWaitingForNextTrial = false;
if (AstroBaseApplication.GameManager.DotCoherencePercentage > 0)
Logger.LogCode(Logger.ExperimentalCode.MM_DotTrialBegin, "coh=" + AstroBaseApplication.GameManager.DotCoherencePercentage + ", dir=" + mDirection);
else
Logger.LogCode(Logger.ExperimentalCode.MM_DotTrialBegin, "coh=0");
}
}
}
if (mGameState == GameState.Game && !mWaitingForNextTrial)
{
// Trial is in progress
// Check to see if the trial has timed-out
mDotTrialCountdown -= dt;
if (mDotTrialCountdown < 0)
{
// Ignore the coherence on the first trial
float lastCycleCoherence = AstroBaseApplication.GameManager.DotCoherencePercentage;
if (mFirstDotTrial)
{
lastCycleCoherence = 0f;
mRemainingTrials++;
}
// Prep the next trial
if (--mRemainingTrials >= 0)
{
if (!mDotCoherence)
{
// Correctly ruled out motion
PESTAdjust(lastCycleCoherence, false);
}
else
{
// Did not react to the present motion
PESTAdjust(lastCycleCoherence, false);
DotShieldHit();
}
}
// Don't count the first trial since it times out before the user can respond
// PestAdjust should have ignored the first trial since it thought coherence was 0
if (!mFirstDotTrial)
{
Logger.LogCode(Logger.ExperimentalCode.MM_DotTrialExpire);
}
mFirstDotTrial = false;
}
// Check to see if the user has responded (thus ending the trial)
if (!mAllIsLost)
{
// For PEST responses:
// Correct direction when motion is present = +
// Incorrect direction when motion is present = -
// Any direction when no motion is present = +
// Correctly abstain when no motion is present = - (already handled at this point)
if (InputState.IsKeyDown(Keys.Left))
{
if (!mLeftArrowWasPressedLastCycle)
{
// Left arrow was pressed, so move onto the next trial
if (--mRemainingTrials > 0)
{
if (mDirection.X < 0 && mDotCoherence)
// Correct Identification
PESTAdjust(AstroBaseApplication.GameManager.DotCoherencePercentage, true);
else if (mDotCoherence)
{
// Incorrect Identification
PESTAdjust(AstroBaseApplication.GameManager.DotCoherencePercentage, false);
DotShieldHit();
}
else
{
// Identified motion when there was none
PESTAdjust(AstroBaseApplication.GameManager.DotCoherencePercentage, true);
DotShieldHit();
}
// HUD
mCockpit.TurnLeft();
// Wait for EEG to read activity for this coherence
mHoldCoherenceTimer = AstroBaseApplication.GameManager.DotCoherenceRefreshDelay;
}
}
mLeftArrowWasPressedLastCycle = true;
}
else
mLeftArrowWasPressedLastCycle = false;
if (InputState.IsKeyDown(Keys.Right))
{
if (!mRightArrowWasPressedLastCycle)
{
// Right arrow was pressed, so move onto the next trial
if (--mRemainingTrials > 0)
{
if (mDirection.X > 0 && mDotCoherence)
PESTAdjust(AstroBaseApplication.GameManager.DotCoherencePercentage, true);
else if (mDotCoherence)
{
PESTAdjust(AstroBaseApplication.GameManager.DotCoherencePercentage, false);
DotShieldHit();
}
else
{
PESTAdjust(AstroBaseApplication.GameManager.DotCoherencePercentage, true);
DotShieldHit();
}
// HUD
mCockpit.TurnRight();
// Wait for EEG to read activity for this coherence
mHoldCoherenceTimer = AstroBaseApplication.GameManager.DotCoherenceRefreshDelay;
}
}
mRightArrowWasPressedLastCycle = true;
}
else
mRightArrowWasPressedLastCycle = false;
}
}
// See if we need more dots
while (!mAllIsLost && mDots.Count - mDotOpenIndices.Count < AstroBaseApplication.GameManager.NumDots)
{
Dot dot;
if (mDots.Count < AstroBaseApplication.GameManager.NumDots)
{
dot = new Dot(ref mDotTexture, diameter, diameter);
mDots.Add(dot);
mGameBoard.AddDot(dot);
}
else
{
dot = mDots[mDotOpenIndices[0]];
dot.Visible = true;
mDotOpenIndices.RemoveAt(0);
}
// Randomize the dot's position
// (the viewable volume is a trapezoid where the Z coordinates are within [mMinDotZ, mMaxDotZ] and the X and Y coordinates depend on the camera's focal length)
// Calculate Z, then make sure X and Y are within the camera's view + the distance it can cover in its life
// The camera sits at (0,0,-1), and looks at (0,0,0), with a focal lenght of .2 units
// The camera can see a box with points (-1,-1,-.8),(-1,1,-.8),(1,-1,-.8),(1,1,-.8), but cannot see any more than that box
// In otherwords, the camera's viewing plane (at a distance of .2 from the camera) is 2 units^2 centered at the origin
if (!mWaitingForNextTrial && random.NextDouble() < AstroBaseApplication.GameManager.DotCoherencePercentage)
{
// This is a coherent dot
dot.Velocity = mDirection;
}
else if (mWaitingForNextTrial && random.NextDouble() < mFakeCoherence)
{
// This isn't a real dot, but it should look coherent
dot.Velocity = mFakeDirection;
}
else
{
// This is a noise dot, so generate a uniformly random trajectory
dot.dX = ((float)random.NextDouble() * 2.0F - 1.0F);
dot.dY = ((float)random.NextDouble() * 2.0F - 1.0F);
dot.dZ = ((float)random.NextDouble() * 2.0F - 1.0F);
dot.Velocity.Normalize();
}
// Calculate Z to be >= mMinDotZ and <= mMaxDotZ
dot.Z = ((float)random.NextDouble() * (mMaxDotZ - mMinDotZ)) + mMinDotZ;
// Calculate how far up this dot lies on the viewable trapezoid's height
float trapHeightPercentage = (dot.Z - mMinDotZ) / (mMaxDotZ - mMinDotZ);
// Calculate the base and top of the viewable trapezoid
float lensWidthToHeightRatio = 2f / AstroBaseApplication.GameManager.MeteorCameraFocalLength;
float maxDepth = AstroBaseApplication.GameManager.MeteorCameraFocalLength + 1f + mMaxDotZ;
float minDepth = AstroBaseApplication.GameManager.MeteorCameraFocalLength + 1f + mMinDotZ;
float trapBase = lensWidthToHeightRatio * maxDepth;
float trapTop = lensWidthToHeightRatio * minDepth;
// Add enough buffer space to allow spawning a dot from off the screen and having it come onto the screen
// (speed * lifespan) * 2
trapTop += 1f * AstroBaseApplication.GameManager.DotLifeSpan * 2f;
trapBase += 1f * AstroBaseApplication.GameManager.DotLifeSpan * 2f;
// Calculate X and Y values that lie within the volume defined by the viewable trapezoid by interpolating between trapBase and trapTop
float maxY = mMaxDotY;
float maxX = maxY * screenRatio;
dot.X = ((float)random.NextDouble() * 2.0F - 1.0F) * maxX;
dot.Y = ((float)random.NextDouble() * 2.0F - 1.0F) * maxY;
// Add a bit of randomness to the lifespan
dot.LifeSpan = AstroBaseApplication.GameManager.DotLifeSpan * (.9f + ((float)random.NextDouble() * .2f));
}
// Update the dots' positions
distanceToMove = AstroBaseApplication.GameManager.DotSpeed * dt * .01F;
for (int i = 0; i < mDots.Count; i++)
{
mDots[i].Position += mDots[i].Velocity * distanceToMove;
}
#endregion
} // dot test phase
else if (mCurrentPhase == MeteorPhases.IdentityTest)
{
#region IdentityTest
if (!mInitialized && mReadyForID)
{
mTimerID = 0f;
mEnemyID = new Entity(mBogieTexture);
mFriendlyID = new Entity(mFriendlyTexture);
mArrowID = new Entity(TextureManager.Load(@"Content\General\UpArrow"));
mArrowID.Position = new Vector3(GenericBaseApplication.GameManager.ScreenWidth * .50f, GenericBaseApplication.GameManager.ScreenHeight * .75f, 0f);
mArrowID.Size = new Vector3(
GenericBaseApplication.GameManager.ScreenWidth * .075f,
GenericBaseApplication.GameManager.ScreenWidth * .15f,
0f);
mArrowSelected = 0;
mArrowID.Rotation = 0;
mArrowAngularVel = 0;
// Set particle system to render in screen space
Pina.Camera.SetToScreenSpace();
// Randomly place the enemy on the left
if (GenericBaseApplication.GameManager.RandomNumber() < 0.5)
mEnemyOnLeft = true;
else
mEnemyOnLeft = false;
mEnemyID.Position = new Vector3(0f, GenericBaseApplication.GameManager.ScreenHeight * .50f, 0f);
mFriendlyID.Position = new Vector3(0f, GenericBaseApplication.GameManager.ScreenHeight * .50f, 0f);
if (mEnemyOnLeft)
{
mEnemyID.X = GenericBaseApplication.GameManager.ScreenWidth * .35f;
mFriendlyID.X = GenericBaseApplication.GameManager.ScreenWidth * .65f;
Logger.LogCode(Logger.ExperimentalCode.MM_ShipIdentityTestBegin, "enemy on left");
}
else
{
mFriendlyID.X = GenericBaseApplication.GameManager.ScreenWidth * .35f;
mEnemyID.X = GenericBaseApplication.GameManager.ScreenWidth * .65f;
Logger.LogCode(Logger.ExperimentalCode.MM_ShipIdentityTestBegin, "enemy on right");
}
// Load the explosion to play when the player selects the enemy
mSplosionID1 = Sparx.LoadParticleEffect(@"Content\Particle Effects\MediumExplosion.spx");
mSplosionID1.Position2D = new Vector2(mEnemyID.Position.X, mEnemyID.Position.Y);
mSplosionID2 = Sparx.LoadParticleEffect(@"Content\Particle Effects\MediumExplosion.spx");
mSplosionID2.Position2D = new Vector2(mEnemyID.Position.X / 2, mEnemyID.Position.Y / 2);
mSplosionID2.Transform = Matrix.CreateScale(2f);
mSplosionID2.RenderScale = 2;
mInitialized = true;
}
if (mInitialized)
{
if (InputState.IsKeyDown(Keys.Left) && InputState.WasKeyUp(Keys.Left))
{
mArrowAngularVel = (float)Math.PI * -3.5f;
mArrowSelected = -1;
Logger.LogCode(Logger.ExperimentalCode.MM_ShipIdentitySelectLeft);
}
else if (InputState.IsKeyDown(Keys.Right) && InputState.WasKeyUp(Keys.Right))
{
mArrowAngularVel = (float)Math.PI * 3.5f;
mArrowSelected = 1;
Logger.LogCode(Logger.ExperimentalCode.MM_ShipIdentitySelectRight);
}
if (InputState.IsKeyDown(Keys.Enter) && InputState.WasKeyUp(Keys.Enter) ||
InputState.IsKeyDown(Keys.Space) && InputState.WasKeyUp(Keys.Space))
{
Logger.LogCode(Logger.ExperimentalCode.MM_ShipIdentityConfirmSelection);
if ((mArrowSelected == -1 && mEnemyOnLeft) || (mArrowSelected == 1 && !mEnemyOnLeft))
{
// Correct ID
Ticker.Display(
"Correct",
null,
Vector2.Zero,
new Vector2(0f, GenericBaseApplication.GameManager.ScreenHeight * -0.30f),
new Vector2(0f, 120f),
Ticker.Font.Space_Big,
Color.Blue,
1f,
3f,
true);
// Explode the bad guy
SoundManager.PlayEffect("Smash");
Sparx.AddEmitter(mSplosionID1);
Sparx.AddEmitter(mSplosionID2);
Logger.LogCode(Logger.ExperimentalCode.MM_ShipIdentityTestEndSuccess);
mInitialized = false;
mNewRoundWaitTimer = 4f;
CompletePhase();
}
else if (mArrowSelected == 0)
{
// Arrow's still in the middle
}
else
{
// Incorrect ID
Ticker.Display(
"Try Again",
null,
Vector2.Zero,
new Vector2(0f, GenericBaseApplication.GameManager.ScreenHeight * -0.30f),
new Vector2(0f, 120f),
Ticker.Font.Space_Big,
Color.Red,
1f,
3f,
true);
Logger.LogCode(Logger.ExperimentalCode.MM_ShipIdentityTestEndFailure);
mInitialized = false;
}
}
// Move the arrow
mArrowID.Rotation += mArrowAngularVel * dt;
if (mArrowID.Rotation > mArrowRightRot)
{
mArrowID.Rotation = mArrowRightRot;
mArrowAngularVel = 0f;
}
if (mArrowID.Rotation < mArrowLeftRot)
{
mArrowID.Rotation = mArrowLeftRot;
mArrowAngularVel = 0f;
}
// Display a help message
if (mTimerID <= 0f)
{
mTimerID = 2f;
Ticker.Display(
"Which one is the enemy?",
null,
Vector2.Zero,
new Vector2(0, -GenericBaseApplication.GameManager.ScreenHeight * .25f),
Vector2.Zero,
Ticker.Font.Space_Big,
Color.LimeGreen,
1f,
3f,
true);
Ticker.Display(
"Press the left or right arrow key and then Enter",
null,
Vector2.Zero,
new Vector2(0, GenericBaseApplication.GameManager.ScreenHeight * .375f),
new Vector2(0f, 0f),
Ticker.Font.Standard_Big,
Color.LimeGreen,
1f,
3f,
true);
}
}
mTimerID -= dt;
#endregion
} // Identity Test
else if (mCurrentPhase == MeteorPhases.Shooter)
{
#region Shooter
// Reset necessary values to initial shooter position
if (!mInitialized && mNewRoundWaitTimer < 0f)
{
// Flush the log to prevent the likilhood of flushing during a test
Logger.FlushLog();
if (mGameState == GameState.Game)
{
// Setup logging parameters
InputState.DecodeAll();
InputState.Encode(Keys.Left, InputState.KeyState.Down, Logger.ExperimentalCode.MM_ShooterActivateMovePort);
InputState.Encode(Keys.Left, InputState.KeyState.Up, Logger.ExperimentalCode.MM_ShooterCeaseMovePort);
InputState.Encode(Keys.Right, InputState.KeyState.Down, Logger.ExperimentalCode.MM_ShooterActivateMoveStarboard);
InputState.Encode(Keys.Right, InputState.KeyState.Up, Logger.ExperimentalCode.MM_ShooterCeaseMoveStarboard);
InputState.Encode(Keys.Up, InputState.KeyState.Down, Logger.ExperimentalCode.MM_ShooterActivateOpenWormholeBeam);
InputState.Encode(Keys.Up, InputState.KeyState.Up, Logger.ExperimentalCode.MM_ShooterCeaseOpenWormholeBeam);
InputState.Encode(Keys.Space, InputState.KeyState.Down, Logger.ExperimentalCode.MM_ShooterActivateFireWeapon);
InputState.Encode(Keys.Space, InputState.KeyState.Up, Logger.ExperimentalCode.MM_ShooterCeaseFireWeapon);
}
// Set the gameboard's near and farplanes to that of the shooter phase
mGameBoard.MinZ = mMinShooterZ;
mGameBoard.MaxZ = mMaxShooterZ;
// Set particle system to render in screen space
Pina.Camera.SetToScreenSpace();
// Make the game go faster according to increased difficulty
mShooterSpeed = mInitialShooterSpeed * mShooterDifficulty;
// Setup initial ship and player wormhole values
SoundManager.PlayEffect("WarpIn");
if (mPlayerShip != null)
mPlayerShip.KillEngineFlare();
mPlayerShip = new CheetahFighter(TextureManager.Load(@"content\MiniGames\Cheetah"), .3F * mShipScale, .24F * mShipScale, .2F * mShipScale, mShipZ);
mPlayerShip.Visible = false;
mGameBoard.AddEntity(mPlayerShip);
mPlayerWormhole = new Wormhole(
1F * mShipScale,
1F * mShipScale,
1F * mShipScale,
(float)mPlayerShip.ArcPosition,
true);
mPlayerWormhole.X = mPlayerShip.X;
mPlayerWormhole.Y = mPlayerShip.Y;
mPlayerWormhole.Z = mPlayerShip.Z;
mGameBoard.AddEntity(mPlayerWormhole);
mPlayerWormhole.FadeIn(mWarpDelay / 8);
mWarpCounter = mWarpDelay / 2;
mDirection = new Vector3(0.0F, 0.0F, -1.0F);
mScrollingStarField = true;
mGameBoard.RemoveEntity(mUnitWormhole);
mUnitWormhole = null;
// Destroy any meteors in the way
foreach (Meteor meteor in mMeteors)
{
if (Entity.Collides2D(meteor, mPlayerWormhole))
DestroyMeteor(meteor, null);
}
// Inform the player what level they are on and how many lives they have
if (mGameState == GameState.Game)
Ticker.Display("Level " + mCurrentLevel,
null,
Vector2.Zero,
new Vector2(0f, -50f),
Vector2.Zero,
Ticker.Font.Space_Big,
Color.LimeGreen,
1f,
8f,
true);
float height = GenericBaseApplication.GameManager.ScreenHeight * .10f;
Texture2D icon = TextureManager.Load(@"Content\MiniGames\CheetahIcon");
if (mGameState == GameState.Game)
Ticker.Display(" x " + mPlayerLives,
icon,
new Vector2(height * icon.Width / icon.Height, height),
new Vector2(0f, 10f),
Vector2.Zero,
Ticker.Font.Space_Big,
Color.Aquamarine,
1f,
8f,
true);
// Adjust the HUD
mCockpit.Visible = false;
mInitialized = true;
}
// Update the ship
if (mPlayerShip != null)
{
mPlayerShip.Update();
UpdateHealthBarPosition();
mWormholeConnectionBeamCounter -= dt;
mFreezeTimer -= dt;
if (mFreezeTimer < 0 && mPlayerShip.Visible)
mPlayerShip.EngineFlare = true;
else
mPlayerShip.EngineFlare = false;
if (mGameState == GameState.Tutorial)
AstroBaseApplication.GameManager.DotShields = 1f;
// Test for death
if (AstroBaseApplication.GameManager.DotShields < 0)
{
GotoAfterlife();
}
}
// Update the unit's flotsam/powerup
if (mCollectible != null)
{
if (mCollectible.Alive)
{
// Update the collectible's position and test for collision
mCollectible.Update(mPlayerShip);
if (mPlayerShip != null)
{
mSpace3DCollider1.Position = mCollectible.Position;
mSpace3DCollider1.Size = mCollectible.Size;
mSpace3DCollider2.Position = mPlayerShip.Position;
mSpace3DCollider2.Size = mPlayerShip.Size;
if (mCollectible.Z < mMinShooterZ)
mCollectible.Alive = false;
if (Entity.Collides3D(mSpace3DCollider1, mSpace3DCollider2) && mPlayerShip.Visible)
{
mCollectible.CollideWithPlayer(mPlayerShip);
}
}
}
else
{
// Collectible has hit the player, so remove it from the game
mGameBoard.RemoveEntity(mCollectible);
mCollectible = null;
mRemainingTrials--;
ScriptManager.SendEventCode("MMPowerup");
}
}
// Update the projectiles
foreach (PhotonTorpedo projectile in mProjectiles)
{
projectile.Update();
mSpace3DCollider1.Position = projectile.Position;
mSpace3DCollider1.Size = projectile.CollisionSize;
if (mUnit != null)
{
mSpace3DCollider2.Position = mUnit.Position;
mSpace3DCollider2.Size = mUnit.Size;
if (projectile.Friendly && Entity.Collides3D(mSpace3DCollider1, mSpace3DCollider2))
{
mUnit.GetHit(projectile.Damage);
projectile.Alive = false;
}
}
if (mCollectible != null)
{
mSpace3DCollider2.Position = mCollectible.Position;
mSpace3DCollider2.Size = mCollectible.Size;
if (projectile.Friendly && Entity.Collides3D(mSpace3DCollider1, mSpace3DCollider2))
{
mCollectible.GetHit(projectile.Damage);
projectile.Alive = false;
}
}
if (mPlayerShip != null)
{
mSpace3DCollider2.Position = mPlayerShip.Position;
mSpace3DCollider2.Size = mPlayerShip.Size;
if (!projectile.Friendly && Entity.Collides3D(mSpace3DCollider1, mSpace3DCollider2) && mPlayerShip.Visible)
{
ShooterShieldHit(projectile.Damage);
projectile.Alive = false;
}
}
}
// Update unit wormholes (there can only be one at a time)
if (mUnitWormhole != null)
{
mUnitWormhole.Update();
}
mUnitWormholeCounter -= dt;
if (mUnitWormholeCounter < 0)
{
if (mUnitWormhole == null && (mCollectible == null || mCollectible.Visible == false) && mGameState == GameState.Game && mRemainingTrials > 0 && (mUnit == null || (mUnit != null && !mUnit.Alive)))
{
// It's time for a new wormhole
mGameBoard.RemoveEntity(mUnitWormhole);
float tempArcPosition = (float)Math.PI * 2 * (float)random.NextDouble();
mUnitWormhole = new Wormhole(
mShipScale * .9f,
mShipScale * .9f,
0f,
tempArcPosition,
false);
Vector2 PositionFromOrigin = new Vector2((float)Math.Cos(tempArcPosition), (float)Math.Sin(tempArcPosition));
mUnitWormhole.X = .8F * PositionFromOrigin.X;
mUnitWormhole.Y = .8F * PositionFromOrigin.Y;
mUnitWormhole.Z = mMaxShooterZ - .1f;
mUnitWormhole.Visible = false;
mUnitIsWarping = false;
mUnitHasWarped = false;
mPlayerOpenedWormhole = false;
mGameBoard.AddEntity(mUnitWormhole);
// Log the wormhole as soon as it is visible
Rectangle proj = Space3D.Project(mUnitWormhole.X, mUnitWormhole.Y, mUnitWormhole.Z, 1f, 1f, mMinShooterZ);
OutputState.Register(mUnitWormhole, Logger.ExperimentalCode.MM_ShooterPresentWormhole, new Vector2(proj.X, proj.Y));
}
if (mUnitWormhole != null && !mUnitWormhole.Visible && !mUnitIsWarping && !mUnitHasWarped && mPlayerShip != null)
{
// Make sure the wormhole's area is clear of meteors before fading in (new meteors will not be spawned here)
bool areaIsClear = true;
float scaler = 5f;
// Create a dummy collision box to make sure there is extra clearance for the path from the player to the wormhole
Meteor collider = new Meteor(mPlayerShip.Width * scaler, mPlayerShip.Height * scaler, mPlayerShip.Depth * scaler, 0f, 0f, 0f);
collider.Position = new Vector3(mUnitWormhole.X, mUnitWormhole.Y, (mMaxShooterZ - mMinShooterZ) * .5f);
collider.Depth = mMaxShooterZ - mMinShooterZ;
// Test this dummy collision box agains meteors to see if the area is actually clear
foreach (Meteor meteor in mMeteors)
{
mSpace3DCollider2.Position = meteor.Position;
mSpace3DCollider2.Size = meteor.Size;
if (Entity.Collides3D(collider, mSpace3DCollider2))
areaIsClear = false;
}
if (areaIsClear)
{
mUnitWormhole.FadeIn(mWarpInTime);
mUnitIsWarping = true;
mUnitWarpCounter = mWarpInTime;
}
}
if (mUnitWormhole != null && mUnitWormhole.Visible && mUnitIsWarping && !mUnitHasWarped && mPlayerOpenedWormhole)
{
// See if it's time to warp a UFO out of a unit wormhole
mUnitWarpCounter -= dt;
if (mUnitWarpCounter < 0 || mForceFriendlySpawn || mForceBogieSpawn)
{
mUnitIsWarping = false;
mUnitHasWarped = true;
mCollectibleThrown = false;
mUnitWormhole.FadeOut(mWarpInTime);
// Warp in either a friend or foe with equal probability
if (!mForceBogieSpawn && (random.NextDouble() < .5 || mForceFriendlySpawn))
{
// Warp in a friend
mUnit = new Friendly(mFriendlyTexture, .6F * mShipScale, .6F * mShipScale, .6F * mShipScale, (int)(mEnemyShields * .20f), Friendly.PowerUpType.Resource, mShooterSpeed);
((Friendly)mUnit).ArcPosition = mPlayerShip.ArcPosition;
mUnit.Z = mUnitWormhole.Z - .5f;
mUnit.Velocity = new Vector3(0, 0, mShooterSpeed * -1);
mGameBoard.AddEntity(mUnit);
// See if the tutorial forced this entity to spawn. If so, make it impossible to kill
if (mForceFriendlySpawn)
mUnit.HitPoints *= 100;
// Log the friend as soon as it is displayed
Rectangle proj = Space3D.Project(mUnit.X, mUnit.Y, mUnit.Z, 1f, 1f, mMinShooterZ);
if (mGameState == GameState.Game)
OutputState.Register(mUnit, Logger.ExperimentalCode.MM_ShooterPresentFriendly, new Vector2(proj.X, proj.Y));
}
else
{
// Warp in a foe
mUnit = new SpacePirate(mBogieTexture, .6F * mShipScale, .6F * mShipScale, .6F * mShipScale, (int)(mEnemyShields * mShooterDifficulty), .5f / mShooterDifficulty);
((SpacePirate)mUnit).ArcPosition = mPlayerShip.ArcPosition;
mUnit.Z = mUnitWormhole.Z - .5f;
mUnit.Velocity = new Vector3(0, 0, mShooterSpeed * mEnemySpeed * -1);
mGameBoard.AddEntity(mUnit);
// See if the tutorial forced this entity to spawn. If so, make it really easy to kill
if (mForceBogieSpawn)
mUnit.HitPoints *= .5f;
// Log the foe as soon as it is displayed
Rectangle proj = Space3D.Project(mUnit.X, mUnit.Y, mUnit.Z, 1f, 1f, mMinShooterZ);
if (mGameState == GameState.Game)
OutputState.Register(mUnit, Logger.ExperimentalCode.MM_ShooterPresentEnemy, new Vector2(proj.X, proj.Y));
}
}
}
if (mUnitWormhole != null && !mUnitWormhole.Visible && !mUnitIsWarping && mUnitHasWarped)
{
// The wormhole has spawned a UFO and faded out, so get rid of it
mUnitWormholeCounter = (float)random.NextDouble() * (mUnitWormholeMaxInterval - mUnitWormholeMinInterval) + mUnitWormholeMinInterval;
mGameBoard.RemoveEntity(mUnitWormhole);
mUnitWormhole = null;
}
}
// Check for meteor collisions with the player, enemy ship or projectiles
foreach (Meteor meteor in mMeteors)
{
mSpace3DCollider1.Position = meteor.Position;
mSpace3DCollider1.Size = meteor.Size;
if (mUnit != null)
{
mSpace3DCollider2.Position = mUnit.Position;
mSpace3DCollider2.Size = mUnit.Size;
// Since meteors fade out, it cannot be assumed that every visible meteor is alive
if (Entity.Collides3D(mSpace3DCollider1, mSpace3DCollider2) && meteor.Alive)
{
DestroyMeteor(meteor, null);
mUnit.GetHit(mMeteorDamage);
}
}
if (mPlayerShip != null)
{
mSpace3DCollider2.Position = mPlayerShip.Position;
mSpace3DCollider2.Size = mPlayerShip.Size;
if (Entity.Collides3D(mSpace3DCollider1, mSpace3DCollider2) && meteor.Alive)
{
SoundManager.PlayEffect("Smash");
DestroyMeteor(meteor, mPlayerShip.Position);
ShooterShieldHit(mMeteorDamage);
}
}
foreach (PhotonTorpedo projectile in mProjectiles)
{
mSpace3DCollider2.Position = projectile.Position;
mSpace3DCollider2.Size = projectile.CollisionSize;
if (Entity.Collides3D(mSpace3DCollider1, mSpace3DCollider2) && meteor.Alive)
{
// Player shot the meteor
SoundManager.PlayEffect("Smash");
projectile.Alive = false;
DestroyMeteor(meteor, projectile.Position);
// Check for lucky reward
float luck = GenericBaseApplication.GameManager.RandomNumber();
if (luck < mLuckyChance)
{
int numCoins = GenericBaseApplication.GameManager.RandomNumber(1, 4);
float scaler = .075f;
// Check for big jackpot reward
if (luck < mJackpotChance)
{
numCoins = GenericBaseApplication.GameManager.RandomNumber(7, 10);
scaler *= 1.5f;
}
// Spawn some reward money
for (int i = 0; i < numCoins; i++)
{
float scale = scaler *mShipScale * GenericBaseApplication.GameManager.RandomNumber(.5f, .75f);
Flotsam coin = new Flotsam(AstroResources.Credits, 10, TextureManager.Load(@"Content\General\Credits"), scale, scale, mShipScale * .1f);
coin.HitPoints = 100;
coin.Position = meteor.Position;
coin.Z += i * .1f;
Vector3 velocity = new Vector3(
GenericBaseApplication.GameManager.RandomNumber(-1f, 1f),
GenericBaseApplication.GameManager.RandomNumber(-1f, 1f),
0f);
velocity.Normalize();
velocity = Vector3.Multiply(velocity, mShooterSpeed * GenericBaseApplication.GameManager.RandomNumber(.5f, 1f));
velocity.Z = -mShooterSpeed;
coin.Velocity = velocity;
coin.Tint = new Color(
(byte)(coin.Tint.R * GenericBaseApplication.GameManager.RandomNumber(.9f, 1f)),
(byte)(coin.Tint.G * GenericBaseApplication.GameManager.RandomNumber(.6f, 1f)),
(byte)(coin.Tint.B * GenericBaseApplication.GameManager.RandomNumber(.7f, 1f)));
mGameBoard.AddEntity(coin);
mJackpotCollectibles.Add(coin);
}
}
}
}
}
// Perform general game updates
if (mPlayerShip != null && mPlayerShip.Visible || mAllIsLost)
{
// See if we need more meteors
mMeteorSpawnCounter -= (float)(2 * random.NextDouble()) * mMeteorSpawnRate * dt;
if (mGameState == GameState.Game && mMeteorSpawnCounter < 0)
{
mMeteorSpawnCounter += mMeteorSpawnRate * GenericBaseApplication.GameManager.RandomNumber(.75f, 1.25f);
// Randomize the meteor's appearance
float newMeteorSize = (mMinMeteorSize + (mMaxMeteorSize - mMinMeteorSize) * (float)random.NextDouble());
// Spawn a new meteor in an area that is not close to a unit wormhole
Meteor newMeteor = new Meteor(newMeteorSize, newMeteorSize, newMeteorSize, mShooterSpeed, mMaxShooterZ, -mMaxShooterZ);
if (mUnitWormhole != null)
{
bool areaIsClear = false;
float scaler = 2f;
Meteor collider = new Meteor(mUnitWormhole.Width * scaler, mUnitWormhole.Height * scaler, 5f, 0f, 0f, 0f);
collider.Position = new Vector3(mUnitWormhole.X, mUnitWormhole.Y, (mMaxShooterZ - mMinShooterZ) * .5f);
collider.Depth = mMaxShooterZ - mMinShooterZ;
int failSafe = 0;
while (!areaIsClear && failSafe < 1000)
{
newMeteor = new Meteor(newMeteorSize, newMeteorSize, newMeteorSize, mShooterSpeed, mMaxShooterZ, -mMaxShooterZ);
mSpace3DCollider2.Position = newMeteor.Position;
mSpace3DCollider2.Size = newMeteor.Size;
if (Entity.Collides3D(collider, mSpace3DCollider2))
areaIsClear = false;
else
areaIsClear = true;
failSafe++;
}
}
newMeteor.FadeIn(4);
mMeteors.Add(newMeteor);
mGameBoard.AddEntity(newMeteor);
}
// React to player response
if (!mAllIsLost && mPlayerShip != null)
{
// Move left
if (mFreezeTimer < 0 && InputState.IsKeyDown(Keys.Left) && mAllowTurns)
{
mPlayerShip.MoveLeft(dt);
}
// Move Right
if (mFreezeTimer < 0 && InputState.IsKeyDown(Keys.Right) && mAllowTurns)
{
mPlayerShip.MoveRight(dt);
}
// Fire weapons
if (InputState.IsKeyDown(Keys.Space) && mAllowFire)
{
if (mWeaponCooldownCounter < 0)
{
SoundManager.PlayEffect("Pew");
mWeaponCooldownCounter = AstroBaseApplication.GameManager.DotWeaponCooldown * mWeaponCooldownUpgrade;
mWeaponCooldownCounter *= GenericBaseApplication.GameManager.RandomNumber(0.9f, 1.1f);
PhotonTorpedo pt = new PhotonTorpedo(
true,
new Color(50, 125, 255),
mCollectedUpgrades,
new Vector3(mPlayerShip.GetNextFiringLocation(), mShipZ),
mPlayerShip.Width * .5F,
mPlayerShip.Width * .5F,
mPlayerShip.Width * .5F,
mShooterSpeed * 10,
mShipZ + .01F,
mMaxShooterZ * 3f);
pt.Friendly = true;
pt.Damage = mPlayerShotDamage;
pt.Tint = new Color(100, 255, 100, 128);
pt.dX = ((float)random.NextDouble() - .5f) * .25f;
pt.dY = ((float)random.NextDouble() - .5f) * .25f;
mProjectiles.Add(pt);
mGameBoard.AddEntity(pt);
Rectangle screenCoords = Space3D.Project(pt.X, pt.Y, pt.Z, pt.Width, pt.Height, mMinShooterZ);
Emitter flash = (Emitter)mFlashEmitter.Clone();
flash.Position2D = new Vector2(screenCoords.X, screenCoords.Y);
Sparx.AddEmitter(flash);
Logger.LogCode(Logger.ExperimentalCode.MM_ShooterPlayerWeaponFired);
}
}
mWeaponCooldownCounter -= dt;
// Connect a wormhole
if (mFreezeTimer < 0 && InputState.IsKeyDown(Keys.Up) && mAllowConnect)
{
if (mUnitWormhole != null &&
mUnitWormhole.Visible &&
!mPlayerOpenedWormhole &&
Entity.Collides2D(mUnitWormhole, mPlayerShip))
{
mPlayerShip.Lerp(mUnitWormhole.SpawnedArcPosition, .5f);
SoundManager.PlayEffect("Wherrrrr");
mPlayerOpenedWormhole = true;
mUnitWarpCounter = mWarpInTime;
mFreezeTimer = mWormholeOpenFreezeTime;
ShootSuccessfulWormholeBeam();
// Destroy any meteors that are currently in the way
float scaler = 7.5f;
Meteor collider = new Meteor(mPlayerShip.Width * scaler, mPlayerShip.Height * scaler, (mMaxShooterZ - mMinShooterZ), 0f, 0f, 0f);
collider.Position = new Vector3(mPlayerShip.X, mPlayerShip.Y, (mMaxShooterZ - mMinShooterZ) * .5f);
foreach (Meteor meteor in mMeteors)
{
mSpace3DCollider2.Position = meteor.Position;
mSpace3DCollider2.Size = meteor.Size;
if (Entity.Collides3D(collider, mSpace3DCollider2))
DestroyMeteor(meteor, null);
}
Logger.LogCode(Logger.ExperimentalCode.MM_ShooterOpenWormholeSuccess);
ScriptManager.SendEventCode("MMConnect");
}
else
ShootDudWormholeBeam();
}
}
}
#endregion
} // Shooter phase
else if (mCurrentPhase == MeteorPhases.Boss)
{
#region Boss
mVictoryTimer -= dt;
mFlyAwayTimer -= dt;
// Reset necessary values to initial shooter position
if (!mInitialized && mNewRoundWaitTimer < 0f)
{
// Either make sure the ship looks right or respawn it
if (mPlayerShip != null)
{
// Make sure the ship looks right if it is alive
Vector3 pos = mPlayerShip.Position;
double rot = mPlayerShip.ArcPosition;
mPlayerShip.KillEngineFlare();
mGameBoard.RemoveEntity(mPlayerShip);
mPlayerShip = new CheetahFighter(TextureManager.Load(@"content\MiniGames\Cheetah"), .3F * mShipScale, .24F * mShipScale, .2F * mShipScale, mShipZ);
mPlayerShip.Visible = true;
mPlayerShip.Position = pos;
mPlayerShip.ArcPosition = rot;
mGameBoard.AddEntity(mPlayerShip);
}
else
{
// Setup initial ship and player wormhole values
SoundManager.PlayEffect("WarpIn");
if (mPlayerShip != null)
mPlayerShip.KillEngineFlare();
mPlayerShip = new CheetahFighter(TextureManager.Load(@"content\MiniGames\Cheetah"), .3F * mShipScale, .24F * mShipScale, .2F * mShipScale, mShipZ);
mPlayerShip.Visible = false;
mGameBoard.AddEntity(mPlayerShip);
mPlayerWormhole = new Wormhole(
1F * mShipScale,
1F * mShipScale,
1F * mShipScale,
(float)mPlayerShip.ArcPosition,
true);
mPlayerWormhole.X = mPlayerShip.X;
mPlayerWormhole.Y = mPlayerShip.Y;
mPlayerWormhole.Z = mPlayerShip.Z;
mGameBoard.AddEntity(mPlayerWormhole);
mPlayerWormhole.FadeIn(mWarpDelay / 8);
mWarpCounter = mWarpDelay / 2;
mDirection = new Vector3(0.0F, 0.0F, -1.0F);
mScrollingStarField = true;
mGameBoard.RemoveEntity(mUnitWormhole);
mUnitWormhole = null;
// Destroy any meteors in the way
foreach (Meteor meteor in mMeteors)
{
if (Entity.Collides2D(meteor, mPlayerWormhole))
DestroyMeteor(meteor, null);
}
// Inform the player of how many lives they have
float height = GenericBaseApplication.GameManager.ScreenHeight * .10f;
Texture2D icon = TextureManager.Load(@"Content\MiniGames\CheetahIcon");
if (mGameState == GameState.Game)
Ticker.Display(" x " + mPlayerLives,
icon,
new Vector2(height * icon.Width / icon.Height, height),
new Vector2(0f, GenericBaseApplication.GameManager.ScreenHeight * .22f),
Vector2.Zero,
Ticker.Font.Space_Big,
Color.Aquamarine,
1f,
8f,
true);
}
mInitialized = true;
}
// Update the player ship
if (mPlayerShip != null)
{
mPlayerShip.Update();
UpdateHealthBarPosition();
}
// Update the boss
if (mBoss != null)
{
mBoss.Update();
mBossMeter.Life1 = mBoss.ShieldLife;
mBossMeter.Life2 = mBoss.RocketPodLife;
mBossMeter.Life3 = mBoss.HullLife;
if (!mBoss.Alive)
{
mGameBoard.RemoveEntity(mBoss);
mBoss = null;
GenericBaseApplication.GameManager.InterpretLuaFile(@"Astropolis\MeteorMadness\Victory");
mFlyAwayTimer = 5f;
mVictoryTimer = 15f;
mAllowFire = false;
}
}
else
{
if (mPlayerShip != null && mFlyAwayTimer <= 0)
{
if (mPlayerShip.dZ == 0f)
mPlayerShip.Velocity = new Vector3(0, 0, 1f);
mPlayerShip.dZ *= 1.0075f;
}
if (mVictoryTimer <= 0 && mDoneWithEndingDialog)
CompletePhase();
}
// Test for player death
if (AstroBaseApplication.GameManager.DotShields < 0)
{
GotoAfterlife();
}
// Update the projectiles
foreach (PhotonTorpedo projectile in mProjectiles)
{
projectile.Update();
mSpace3DCollider1.Position = projectile.Position;
mSpace3DCollider1.Size = projectile.CollisionSize;
// Test for boss hit
if (mBoss != null)
{
mSpace3DCollider2.Position = new Vector3(projectile.Position.X, projectile.Position.Y, mBoss.Position.Z);
mSpace3DCollider2.Size = Vector3.Multiply(mBoss.Size, 1f);
if (projectile.Friendly && Entity.Collides3D(mSpace3DCollider1, mSpace3DCollider2))
{
// Test for a hit on one of the boss's rocket pods
List pods = mBoss.GetVulnerableRocketPods();
List potentialCollisions = new List();
foreach (RocketPod pod in pods)
{
// Pretend each rocket pod is at the same depth as the boss
mSpace3DCollider2.Position = new Vector3(pod.Entity.Position.X, pod.Entity.Position.Y, mBoss.Position.Z);
mSpace3DCollider2.Size = new Vector3(pod.Entity.Size.Y * 5f, pod.Entity.Size.Y * 5f, pod.Entity.Size.Z * 100f);
//mSpace3DCollider2.Size = Vector3.Multiply(pod.Entity.Size, 5f);
if (projectile.Friendly && Entity.Collides3D(mSpace3DCollider1, mSpace3DCollider2))
{
potentialCollisions.Add(pod);
}
}
// Find the BEST collision (in 2 Dimensions)
RocketPod closestPod = null;
float closestDistance = float.MaxValue;
foreach (RocketPod pod in potentialCollisions)
{
float thisDistance = Vector2.DistanceSquared(new Vector2(pod.Entity.Position.X, pod.Entity.Position.Y), new Vector2(projectile.Position.X, projectile.Position.Y));
if (thisDistance < closestDistance)
{
closestPod = pod;
closestDistance = thisDistance;
}
}
// Hit the pod or the ship
if (closestPod != null)
{
closestPod.GetHit(projectile.Damage, mBoss.Position);
projectile.Alive = false;
}
else
{
mBoss.GetHit(projectile.Damage, projectile.Position);
projectile.Alive = false;
}
}
}
if (mUnit != null)
{
mSpace3DCollider2.Position = mUnit.Position;
mSpace3DCollider2.Size = mUnit.Size;
if (projectile.Friendly && Entity.Collides3D(mSpace3DCollider1, mSpace3DCollider2))
{
mUnit.GetHit(projectile.Damage);
projectile.Alive = false;
}
}
if (mCollectible != null)
{
mSpace3DCollider2.Position = mCollectible.Position;
mSpace3DCollider2.Size = mCollectible.Size;
if (projectile.Friendly && Entity.Collides3D(mSpace3DCollider1, mSpace3DCollider2))
{
mCollectible.GetHit(projectile.Damage);
projectile.Alive = false;
}
}
if (mPlayerShip != null)
{
mSpace3DCollider2.Position = mPlayerShip.Position;
mSpace3DCollider2.Size = mPlayerShip.Size;
if (!projectile.Friendly && Entity.Collides3D(mSpace3DCollider1, mSpace3DCollider2))
{
ShooterShieldHit(projectile.Damage);
projectile.Alive = false;
mInvulnerableTimer = mBossProjectileDamageCooldown;
}
}
}
// React to player response
if (!mAllIsLost && mPlayerShip != null)
{
// Move left
if (InputState.IsKeyDown(Keys.Left))
{
mPlayerShip.MoveLeft(dt);
}
// Move Right
if (InputState.IsKeyDown(Keys.Right))
{
mPlayerShip.MoveRight(dt);
}
// Fire weapons
if (InputState.IsKeyDown(Keys.Space) && mAllowFire)
{
if (mWeaponCooldownCounter < 0)
{
SoundManager.PlayEffect("Pew");
mWeaponCooldownCounter = AstroBaseApplication.GameManager.DotWeaponCooldown * mWeaponCooldownUpgrade;
PhotonTorpedo pt = new PhotonTorpedo(
true,
new Color(50, 125, 255),
mCollectedUpgrades,
new Vector3(mPlayerShip.GetNextFiringLocation(), mShipZ),
mPlayerShip.Width * .5F,
mPlayerShip.Width * .5F,
mPlayerShip.Width * .5F,
mShooterSpeed * 10,
mShipZ + .01F,
mMaxShooterZ * 3f);
pt.Friendly = true;
pt.Damage = mPlayerShotDamage;
pt.Tint = new Color(100, 255, 100, 128);
pt.dX = ((float)random.NextDouble() - .5f) * .25f;
pt.dY = ((float)random.NextDouble() - .5f) * .25f;
mProjectiles.Add(pt);
mGameBoard.AddEntity(pt);
Rectangle screenCoords = Space3D.Project(pt.X, pt.Y, pt.Z, pt.Width, pt.Height, mMinShooterZ);
Emitter flash = (Emitter)mFlashEmitter.Clone();
flash.Position2D = new Vector2(screenCoords.X, screenCoords.Y);
Sparx.AddEmitter(flash);
}
}
mWeaponCooldownCounter -= dt;
}
#endregion
} // Boss
else if (mCurrentPhase == MeteorPhases.DotTest2ShooterTransition)
{
#region DotTest2Shooter
// Start the warp effect
GenericBaseApplication.GameManager.InterpretLuaFile(@"Astropolis\MeteorMadness\LeaveWarp.lua");
mNewRoundWaitTimer = 6f;
// Trigger the scrolling stars
mScrollingStarField = true;
// Update transition variables
mShooterDifficulty += mShooterDifficultyIncrease;
mCurrentLevel++;
mCurrentPhase = MeteorPhases.Shooter;
mInitialized = false;
#endregion
} // Dot to Shooter transition phase
else if (mCurrentPhase == MeteorPhases.Shooter2DotTestTransition)
{
#region Shooter2DotTest
mInitialized = false;
// If there is a new wormhole opening up, close it
if (mUnitWormhole != null)
{
mUnitWormhole.Update();
if (mUnitWormhole.Opacity == 1f)
mUnitWormhole.FadeOut(1f);
}
// Update any remaining ships
if (mPlayerShip != null)
{
mPlayerShip.Update();
UpdateHealthBarPosition();
}
if (mUnit != null)
mUnit.Update();
// Update any remainng projectiles
foreach (PhotonTorpedo projectile in mProjectiles)
projectile.Update();
// Make a wormhole for the player to jump into
if (mPlayerWormhole != null)
{
if (mPlayerWormhole.Visible == false)
{
mPlayerWormhole = new Wormhole(
1F * mShipScale,
1F * mShipScale,
0F,
(float)mPlayerShip.ArcPosition,
true);
mPlayerWormhole.X = mPlayerShip.X;
mPlayerWormhole.Y = mPlayerShip.Y;
mPlayerWormhole.Z = mMaxShooterZ;
mGameBoard.AddEntity(mPlayerWormhole);
mPlayerWormhole.FadeIn(mWarpDelay);
mPlayerJumpCountdown = mWarpDelay;
}
mPlayerWormhole.Update();
mPlayerJumpCountdown -= dt;
}
// Build-up the engine flare
float scaling = .3f * (mWarpDelay / mPlayerJumpCountdown);
mPlayerShip.EngineScale = scaling;
mPlayerShip.ColorizeEngineFlareForWarp();
Ticker.Display(
"Initiating Subspace Jump",
null,
Vector2.Zero,
new Vector2(0f, 50f),
Vector2.Zero,
Ticker.Font.Space_Big,
Color.LimeGreen,
1f,
.1f,
false);
if (mPlayerJumpCountdown < 0)
{
if (mPlayerShip.Opacity == 1f)
{
// Jump the player through the wormhole
SoundManager.PlayEffect("WarpDrive");
mPlayerShip.FadeOut(2f);
JumpThroughWormhole();
mPlayerShip.Position = mPlayerWormhole.Position;
foreach (Meteor meteor in mMeteors)
DestroyMeteor(meteor, null);
// Stop creating new stars
mScrollingStarField = false;
// If there is still a unit visible (ie, the player destroyed the collectible on the last trial), remove it without blowing it up
if (mUnit != null)
{
mGameBoard.RemoveEntity(mUnit);
mUnit = null;
}
GenericBaseApplication.GameManager.InterpretLuaFile(@"Astropolis\MeteorMadness\EnterWarp");
mDotTestBeginDelay = 9f;
}
else if (mPlayerShip.Visible == false)
{
mNewRoundWaitTimer = 4f;
mCurrentPhase = MeteorPhases.DotTest;
}
}
#endregion
} // Shooter to Dot transition phase
else if (mCurrentPhase == MeteorPhases.Shooter2BossTransition)
{
#region Shooter2Boss
// If there is a new wormhole opening up, close it
if (mUnitWormhole != null)
{
mGameBoard.RemoveEntity(mUnitWormhole);
mUnitWormhole = null;
}
if (mPlayerWormhole != null)
{
mGameBoard.RemoveEntity(mPlayerWormhole);
mPlayerWormhole = null;
}
// Update any remaining ships
mPlayerShip.Update();
UpdateHealthBarPosition();
if (mUnit != null)
mUnit.Update();
// Get the meteors out of the way of the player so he/she can focus on reading
foreach (Meteor meteor in mMeteors)
{
meteor.FadeOut(3f);
}
// Set particle system to render in screen space
Pina.Camera.SetToScreenSpace();
// Extend gameboard's farplane since the boss can go farther back than objects in the shooter phase
mGameBoard.MinZ = mMinShooterZ;
mGameBoard.MaxZ = mMaxShooterZ * 100f;
// No experiment here
InputState.DecodeAll();
// Make the game go faster according to increased difficulty
mShooterSpeed = mInitialShooterSpeed * mShooterDifficulty;
// Inform the player that the boss is approacting
GenericBaseApplication.GameManager.InterpretLuaFile(@"Astropolis\MeteorMadness\BossEncounter.lua");
Ticker.Display("Approaching Enemy Bomber ", null, Vector2.Zero, new Vector2(0f, 50f), Vector2.Zero, Ticker.Font.Space_Big, Color.LimeGreen, 1f, 5f, true);
// Spawn the boss
mBoss = new Boss(this, 3f * mShipScale, 3f * mShipScale, 15f * mShipScale, mMaxShooterZ, mMaxShooterZ * 5f, 10, 100);
mGameBoard.AddEntity(mBoss);
// Adjust the HUD
mCockpit.Visible = false;
mBossMeter.Visible = true;
mPlayerHealthTimer = mPlayerHealthTime;
mInitialized = false;
mCurrentPhase = MeteorPhases.Boss;
#endregion
} // Shooter to Boss transition phase
} // GameState == Menu || Tutorial
// Send other event codes to the tutorial script
if (mGameState == GameState.Tutorial)
{
if (InputState.IsKeyDown(Keys.Left) && !InputState.WasKeyDown(Keys.Left))
{
ScriptManager.SendEventCode("MMLeft");
if (!mLRPressed)
{
ScriptManager.SendEventCode("MMFirstKey");
mLRPressed = true;
}
else
ScriptManager.SendEventCode("MMSecondKey");
}
if (InputState.IsKeyDown(Keys.Right) && !InputState.WasKeyDown(Keys.Right))
{
ScriptManager.SendEventCode("MMRight");
if (!mLRPressed)
{
ScriptManager.SendEventCode("MMFirstKey");
mLRPressed = true;
}
else
ScriptManager.SendEventCode("MMSecondKey");
}
if (InputState.IsKeyDown(Keys.Space) && !InputState.WasKeyDown(Keys.Space))
ScriptManager.SendEventCode("MMFire");
if (mPlayerShip != null)
{
if (mPlayerShip.Y < -.5f)
ScriptManager.SendEventCode("MMUpsideDown");
if (mPlayerShip.Y > .5f)
ScriptManager.SendEventCode("MMRightsideUp");
}
}
}
///
/// Draws all objects registered with the gameboard
///
public override void Draw()
{
if (mGameState == GameState.Menu)
{
SpriteBatch sb = new SpriteBatch(GenericBaseApplication.GameManager.GraphicsDevice);
sb.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None);
// Render the main menu
mMenuBackground.Draw(sb);
mStartButton.Draw(sb);
mTutorialButton.Draw(sb);
mBackButton.Draw(sb);
sb.End();
}
else if (mGameState == GameState.Game || mGameState == GameState.Tutorial)
{
// Render the objects registered in the gameboard
mGameBoard.Render();
// Render ship identity test
if (mCurrentPhase == MeteorPhases.IdentityTest)
{
SpriteBatch sb = new SpriteBatch(GenericBaseApplication.GameManager.GraphicsDevice);
sb.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None);
if (mEnemyID != null && mFriendlyID != null && mArrowID != null)
{
mEnemyID.Draw(sb);
mFriendlyID.Draw(sb);
mArrowID.Draw(sb);
}
sb.End();
}
}
}
///
/// Performs necessary logic and visual effects for the player getting hit. If mInvulnerableTimer > 0, then player shields are unaffected.
///
///
public void ShooterShieldHit(int damage)
{
if (mInvulnerableTimer <= 0f)
{
// Projectiles actually collide with the player for two game cycles before getting removed from memory, so use this cheap hack to take away the correct amount of damage
AstroBaseApplication.GameManager.DotShields -= (float)damage / 50f;
}
mPlayerHealthTimer = mPlayerHealthTime;
mPlayerShip.GetHit();
Logger.LogCode(Logger.ExperimentalCode.MM_ShooterPlayerGetsHit);
}
///
/// Performs necessary logic and visual effects for the player taking damage
///
public void DotShieldHit()
{
AstroBaseApplication.GameManager.DotShields -= mDotDamage;
mCockpit.TakeDamage();
if (AstroBaseApplication.GameManager.DotShields < 0)
{
// Play warp death cutscene
// End game
}
}
///
/// Adjusts the difficulty of the next dot trial according to the BEST PEST algorithm. Random "Gimme Trials"
/// are built into this calculation to help the player feel better. Also flags that a new trial
/// should begin as soon as the HUD settles into its idle state.
///
private void PESTAdjust(double lastValue, bool lastResponse)
{
// Save old values for "fading"
mFakeCoherence = AstroBaseApplication.GameManager.DotCoherencePercentage;
mFakeDirection = mDirection;
// PEST's range is from -1 to 1, so we must adjust the coherence range of 0 to 1 to fit the PEST's range
double adjustedLastValue = (lastValue * 2) - 1;
// Record the last values, but ignore it if the user correctly abstained from pressing anything
//if (!(lastValue == 0 && lastResponse == false))
if (lastValue != 0)
mPester.Record(adjustedLastValue, lastResponse);
// Adjust trial length
mDotTrialCountdown = mDotTrialLength + ((float)random.NextDouble() * mDotTrialJitter);
// Is this next trial a coherent one?
mDotCoherence = (random.NextDouble() < .5);
// Pick a new direction if this is a coherent trial
if (mDotCoherence == true)
{
float gimmeMultiplier = 1f;
if (GenericBaseApplication.GameManager.RandomNumber(0, mGimmeCounter) == 0)
{
// It is time for a Gimme Trial
gimmeMultiplier = GenericBaseApplication.GameManager.RandomNumber(2f, 5f);
mGimmeCounter = mMaxGimmeDelay;
Logger.LogCode(Logger.ExperimentalCode.MM_DotGimmeNextTrial);
}
else
{
mGimmeCounter--;
}
// Map from PEST's range to the coherence range
AstroBaseApplication.GameManager.DotCoherencePercentage = (float)(mPester.NextValue() + 1) * .5f;
AstroBaseApplication.GameManager.DotCoherencePercentage = Math.Min(1f, AstroBaseApplication.GameManager.DotCoherencePercentage * gimmeMultiplier);
mDirection.X = (random.NextDouble()) < .5 ? 1.0F : -1.0F;
//mDirection.Y = ((float)random.NextDouble() * .25F - .125F);
//mDirection.Z = ((float)random.NextDouble() * .25F - .125F);
//mDirection.Normalize();
mDirection.Y = 0f;
mDirection.Z = 0f;
}
else
{
AstroBaseApplication.GameManager.DotCoherencePercentage = 0f;
}
mWaitingForNextTrial = true;
}
///
/// Kills the meteor and spawns an explosion at its previous location
///
///
private void DestroyMeteor(Meteor meteor, Vector3? hitFromPosition)
{
meteor.Alive = false;
float initOpacity = meteor.Opacity;
if (initOpacity == 1.0f)
meteor.FadeOut(1);
Rectangle proj;
float scale;
// EXPLOSION!
if (hitFromPosition.HasValue)
{
proj = Space3D.Project(hitFromPosition.Value.X, hitFromPosition.Value.Y, hitFromPosition.Value.Z, meteor.Width, meteor.Height, mMinShooterZ);
scale = proj.Width * .025f;
Emitter splosion = Sparx.LoadParticleEffect(@"Content\Particle Effects\SmallExplosion.spx");
splosion.Transform = Matrix.CreateScale(scale) * Matrix.CreateTranslation(proj.X, proj.Y, 0f);
splosion.RegularEmissionType.Modifiers[0].InitialOpacity = initOpacity;
splosion.RenderScale = scale;
Sparx.AddEmitter(splosion);
}
// Shatter the meteor
Emitter rubble = Sparx.LoadParticleEffect(@"Content\Particle Effects\MeteorShatter.spx");
proj = Space3D.Project(meteor.X, meteor.Y, meteor.Z, meteor.Width, meteor.Height, mMinShooterZ);
scale = proj.Width * .01f;
rubble.MaxSpawnRadius = scale * 20f;
rubble.DieAfter = (int)(scale * (float)rubble.DieAfter);
rubble.RegularEmissionType.Modifiers[0].InitialAngularVelocity = (float)meteor.AngularVelocity;
rubble.RegularEmissionType.Modifiers[0].FinalAngularVelocity = rubble.RegularEmissionType.Modifiers[0].InitialAngularVelocity;
rubble.RegularEmissionType.Modifiers[0].InitialScale *= scale;
rubble.RegularEmissionType.Modifiers[0].FinalScale *= scale;
rubble.RegularEmissionType.Modifiers[0].InitialOpacity = initOpacity;
rubble.Position3D = new Vector3(proj.X, proj.Y, .5f);
Sparx.AddEmitter(rubble);
Logger.LogCode(Logger.ExperimentalCode.MM_ShooterMeteorExplosion);
}
///
/// Updates appropriate variables and spawns an explosion at the player's previous location
///
private void DestroyPlayer()
{
if (!mAllIsLost)
{
SoundManager.PlayEffect("Smash");
mPlayerShip.EngineFlare = false;
Emitter splosion = Sparx.LoadParticleEffect(@"Content\Particle Effects\MediumExplosion.spx");
Rectangle proj = Space3D.Project(mPlayerShip.X, mPlayerShip.Y, mPlayerShip.Z, mPlayerShip.Width, mPlayerShip.Height, mMinShooterZ);
splosion.Position2D = new Vector2(proj.X, proj.Y);
Sparx.AddEmitter(splosion);
if (mUnit != null)
mUnit.dZ *= 3f;
}
}
///
/// Spawns particles and sounds to make it look like the player crashed in the cockpit
///
private void CrashInSubspace()
{
SoundManager.PlayEffect("Smash");
Emitter splosion = Sparx.LoadParticleEffect(@"Content\Particle Effects\MediumExplosion.spx");
splosion.Position2D = new Vector2(GenericBaseApplication.GameManager.ScreenWidth * .25f, GenericBaseApplication.GameManager.ScreenHeight * .25f);
splosion.RenderScale = 2f;
splosion.Transform = Matrix.CreateScale(2f);
Sparx.AddEmitter(splosion);
splosion = Sparx.LoadParticleEffect(@"Content\Particle Effects\MediumExplosion.spx");
splosion.Position2D = new Vector2(GenericBaseApplication.GameManager.ScreenWidth * .125f, GenericBaseApplication.GameManager.ScreenHeight * .125f);
splosion.RenderScale = 4f;
splosion.Transform = Matrix.CreateScale(4f);
Sparx.AddEmitter(splosion);
}
///
/// Draws a trail of particle effects from the player to the unit wormhole
///
private void ShootSuccessfulWormholeBeam()
{
mWormholeConnectionBeamCounter = mWormholeConnectionBeamCooldown;
Emitter emit = Sparx.LoadParticleEffect(@"Content\Particle Effects\DudBeamChunk.spx");
emit.RegularEmissionType.LifeExpectancy *= 2;
emit.RegularEmissionType.Modifiers[0].Interval *= 2;
Emitter returnEmit = Sparx.LoadParticleEffect(@"Content\Particle Effects\SuccessBeamChunk.spx");
// Calculate the endpoints of the lines
Rectangle proj1a = Space3D.Project(mUnitWormhole.X, mUnitWormhole.Y, mPlayerShip.Z, 1f, 1f, mMinShooterZ);
Rectangle proj1b = Space3D.Project(mPlayerShip.X, mPlayerShip.Y, mPlayerShip.Z, 1f, 1f, mMinShooterZ);
Rectangle proj2 = Space3D.Project(mUnitWormhole.X, mUnitWormhole.Y, mUnitWormhole.Z, 1f, 1f, mMinShooterZ);
// Variables we will need to make the connection beam appear to travel to infinity
float centerX = AstroBaseApplication.GameManager.ScreenWidth / 2;
float centerY = AstroBaseApplication.GameManager.ScreenHeight / 2;
// Prevent divide by 0 errors by nudging explodable numbers by 1 pixel
while (proj1b.X == proj2.X || proj1b.X == proj2.X || proj1b.Y == proj2.Y || proj1b.Y == proj2.Y)
{
if (proj1b.X == proj2.X)
proj2.X += 1;
if (proj1b.Y == proj2.Y)
proj2.Y += 1;
if (proj1b.X == proj2.X)
proj2.X += 1;
if (proj1b.Y == proj2.Y)
proj2.Y += 1;
}
// First draw the "there" line from the ship to the wormhole
float spacing = 5f;
float scaling;
float slope = (float)(proj2.Y - proj1a.Y) / (float)(proj2.X - proj1a.X);
if (slope < -1 || slope > 1)
{
// Vertical Line
float invSlope = 1 / slope;
// Flip the points if the line needs to go to the left
if (proj1a.Y > proj2.Y)
{
Rectangle temp = proj1a;
proj1a = proj2;
proj2 = temp;
}
float distanceToTravel = proj2.Y - proj1a.Y;
float distanceCovered = 0f;
// Draw the line
for (float yCounter = proj1a.Y; yCounter < proj2.Y; yCounter += spacing)
{
float x = invSlope * (yCounter - proj1a.Y) + proj1a.X;
if (proj1a.Y < centerY)
scaling = 1f - (distanceCovered / distanceToTravel);
else
scaling = distanceCovered / distanceToTravel;
distanceCovered += spacing;
Emitter emitClone = (Emitter)emit.Clone();
emitClone.Transform = Matrix.CreateScale(scaling * 2) * Matrix.CreateTranslation(x, yCounter, 0f);
emitClone.RenderScale = 0.25f + (scaling * .5f);
Sparx.AddEmitter(emitClone);
}
}
else
{
// Horizontal line
// Flip the points if the line needs to go to the left
if (proj1a.X > proj2.X)
{
Rectangle temp = proj1a;
proj1a = proj2;
proj2 = temp;
}
float distanceToTravel = proj2.X - proj1a.X;
float distanceCovered = 0;
// Draw the line
for (float xCounter = proj1a.X; xCounter < proj2.X; xCounter += spacing)
{
float y = slope * (xCounter - proj1a.X) + proj1a.Y;
if (proj1a.X < centerX)
scaling = 1f - (distanceCovered / distanceToTravel);
else
scaling = distanceCovered / distanceToTravel;
distanceCovered += spacing;
Emitter emitClone = (Emitter)emit.Clone();
emitClone.Transform = Matrix.CreateScale(scaling * 2) * Matrix.CreateTranslation(xCounter, y, 0f);
emitClone.RenderScale = 0.25f + (scaling * .5f);
Sparx.AddEmitter(emitClone);
}
}
// Now draw the "back" line
//proj1a = proj1b;
spacing = 5f;
slope = (float)(proj2.Y - proj1a.Y) / (float)(proj2.X - proj1a.X);
if (slope < -1 || slope > 1)
{
// Vertical Line
float invSlope = 1 / slope;
// Flip the points if the line needs to go to the left
if (proj1a.Y > proj2.Y)
{
Rectangle temp = proj1a;
proj1a = proj2;
proj2 = temp;
}
float distanceToTravel = proj2.Y - proj1a.Y;
float distanceCovered = 0f;
// Draw the line
for (float yCounter = proj1a.Y; yCounter < proj2.Y; yCounter += spacing)
{
float x = invSlope * (yCounter - proj1a.Y) + proj1a.X;
if (proj1a.Y < centerY)
scaling = 1f - (distanceCovered / distanceToTravel);
else
scaling = distanceCovered / distanceToTravel;
distanceCovered += spacing;
Emitter returnEmitClone = (Emitter)returnEmit.Clone();
returnEmitClone.Transform = Matrix.CreateScale(scaling * 2) * Matrix.CreateTranslation(x, yCounter, 0f);
returnEmitClone.RenderScale = 0.25f + (scaling * .5f);
returnEmitClone.RegularEmissionType.Age = (1 - scaling * .4f);
Sparx.AddEmitter(returnEmitClone);
}
}
else
{
// Horizontal line
// Flip the points if the line needs to go to the left
if (proj1a.X > proj2.X)
{
Rectangle temp = proj1a;
proj1a = proj2;
proj2 = temp;
}
float distanceToTravel = proj2.X - proj1a.X;
float distanceCovered = 0;
// Draw the line
for (float xCounter = proj1a.X; xCounter < proj2.X; xCounter += spacing)
{
float y = slope * (xCounter - proj1a.X) + proj1a.Y;
if (proj1a.X < centerX)
scaling = 1f - (distanceCovered / distanceToTravel);
else
scaling = distanceCovered / distanceToTravel;
distanceCovered += spacing;
Emitter returnEmitClone = (Emitter)returnEmit.Clone();
returnEmitClone.Transform = Matrix.CreateScale(scaling * 2) * Matrix.CreateTranslation(xCounter, y, 0f);
returnEmitClone.RenderScale = 0.25f + (scaling * .5f);
returnEmitClone.RegularEmissionType.Age = (1 - scaling * .4f);
Sparx.AddEmitter(returnEmitClone);
}
}
}
///
/// Draws a trail of particle effects from the player to the center of the screen, appearing to trail off to infinity
///
private void ShootDudWormholeBeam()
{
if (mWormholeConnectionBeamCounter > 0)
return;
mWormholeConnectionBeamCounter = mWormholeConnectionBeamCooldown;
Emitter emit = Sparx.LoadParticleEffect(@"Content\Particle Effects\DudBeamChunk.spx");
// Calculate the endpoints of the line from the player to the point down the cylinder from the player
Rectangle proj1 = Space3D.Project(mPlayerShip.X, mPlayerShip.Y, mPlayerShip.Z, 1f, 1f, mMinShooterZ);
Rectangle proj2 = Space3D.Project(mPlayerShip.X, mPlayerShip.Z, 20f, 1f, 1f, mMinShooterZ);
// Variables we will need to make the connection beam appear to travel to infinity
float centerX = AstroBaseApplication.GameManager.ScreenWidth / 2;
float centerY = AstroBaseApplication.GameManager.ScreenHeight / 2;
// Prevent divide by 0 errors by nudging explodable numbers by 1 pixel
if (proj1.X == proj2.X)
proj2.X += 1;
if (proj1.Y == proj2.Y)
proj2.Y += 1;
// Make a line of particles between the 2 points
float spacing = 5f;
float scaling;
float slope = (float)(proj2.Y - proj1.Y) / (float)(proj2.X - proj1.X);
if (slope < -1 || slope > 1)
{
// Vertical Line
float invSlope = 1 / slope;
// Flip the points if the line needs to go to the left
if (proj1.Y > proj2.Y)
{
Rectangle temp = proj1;
proj1 = proj2;
proj2 = temp;
}
float distanceToTravel = proj2.Y - proj1.Y;
float distanceCovered = 0;
// Draw the line
for (float yCounter = proj1.Y; yCounter < proj2.Y; yCounter += spacing)
{
float x = invSlope * (yCounter - proj1.Y) + proj1.X;
if (proj1.Y < centerY)
scaling = 1f - (distanceCovered / distanceToTravel);
else
scaling = distanceCovered / distanceToTravel;
distanceCovered += spacing;
Emitter emitClone = (Emitter)emit.Clone();
emitClone.Transform = Matrix.CreateScale(scaling) * Matrix.CreateTranslation(x, yCounter, 1 - scaling);
emitClone.RenderScale = 0.1f + (scaling * .5f);
Sparx.AddEmitter(emitClone);
}
}
else
{
// Horizontal line
// Flip the points if the line needs to go to the left
if (proj1.X > proj2.X)
{
Rectangle temp = proj1;
proj1 = proj2;
proj2 = temp;
}
float distanceToTravel = proj2.X - proj1.X;
float distanceCovered = 0;
// Draw the line
for (float xCounter = proj1.X; xCounter < proj2.X; xCounter += spacing)
{
float y = slope * (xCounter - proj1.X) + proj1.Y;
if (proj1.X < centerX)
scaling = 1f - (distanceCovered / distanceToTravel);
else
scaling = distanceCovered / distanceToTravel;
distanceCovered += spacing;
Emitter emitClone = (Emitter)emit.Clone();
emitClone.Transform = Matrix.CreateScale(scaling) * Matrix.CreateTranslation(xCounter, y, 1 - scaling);
emitClone.RenderScale = 0.1f + (scaling * .5f);
Sparx.AddEmitter(emitClone);
}
}
}
///
/// Draws a trail of ship particle effects from the player position to the player wormhole, as if the player just enterd warp drive
///
private void JumpThroughWormhole()
{
Emitter emit = Sparx.LoadParticleEffect(@"Content\Particle Effects\DudBeamChunk.spx");
emit.RegularEmissionType.Modifiers[0].FinalTint = Color.Violet;
Rectangle proj1 = Space3D.Project(mPlayerShip.X, mPlayerShip.Y, mPlayerShip.Z, 1f, 1f, mMinShooterZ);
Rectangle proj2 = Space3D.Project(mPlayerWormhole.X, mPlayerWormhole.Y, mPlayerWormhole.Z, 1f, 1f, mMinShooterZ);
// Prevent divide by 0 errors by nudging explodable numbers by 1 pixel
if (proj1.X == proj2.X)
proj2.X += 1;
if (proj1.Y == proj2.Y)
proj2.Y += 1;
// Variables we will need to make the connection beam appear to travel to infinity
float centerX = AstroBaseApplication.GameManager.ScreenWidth / 2;
float centerY = AstroBaseApplication.GameManager.ScreenHeight / 2;
// Make a line of particles between the 2 points
float spacing = 5f;
float slope = (float)(proj2.Y - proj1.Y) / (float)(proj2.X - proj1.X);
float scaling;
if (slope < -1 || slope > 1)
{
// Vertical Line
float invSlope = 1 / slope;
// Flip the points if the line needs to go to the left
if (proj1.Y > proj2.Y)
{
Rectangle temp = proj1;
proj1 = proj2;
proj2 = temp;
}
float distanceToTravel = proj2.Y - proj1.Y;
float distanceCovered = 0;
// Draw the line
for (float yCounter = proj1.Y; yCounter < proj2.Y; yCounter += spacing)
{
float x = invSlope * (yCounter - proj1.Y) + proj1.X;
if (proj1.Y < centerY)
scaling = 1f - (distanceCovered / distanceToTravel);
else
scaling = distanceCovered / distanceToTravel;
distanceCovered += spacing;
//emit.RegularEmissionType.Modifiers.Add(new Modifier("Scaler", 0f, 0f, Color.White, Color.White, 1f, .5f, 1f, 1f, 0f, (float)mPlayerShip.Rotation));
Emitter emitClone = (Emitter)emit.Clone();
emitClone.Position2D = new Vector2(x, yCounter);
Sparx.AddEmitter(emitClone);
}
}
else
{
// Horizontal line
// Flip the points if the line needs to go to the left
if (proj1.X > proj2.X)
{
Rectangle temp = proj1;
proj1 = proj2;
proj2 = temp;
}
float distanceToTravel = proj2.X - proj1.X;
float distanceCovered = 0;
// Draw the line
for (float xCounter = proj1.X; xCounter < proj2.X; xCounter += spacing)
{
float y = slope * (xCounter - proj1.X) + proj1.Y;
if (proj1.X < centerX)
scaling = 1f - (distanceCovered / distanceToTravel);
else
scaling = distanceCovered / distanceToTravel;
distanceCovered += spacing;
//emit.RegularEmissionType.Modifiers.Add(new Modifier("Scaler", 0f, 0f, Color.White, Color.White, 1f, .5f, 1f, 1f, 0f, (float)mPlayerShip.Rotation));
Emitter emitClone = (Emitter)emit.Clone();
emitClone.Position2D = new Vector2(xCounter, y);
Sparx.AddEmitter(emitClone);
}
}
}
///
/// Moves the Shooter phase's health bar to stay next to the ship
///
private void UpdateHealthBarPosition()
{
Rectangle playerScreenPosition = Space3D.Project(
mPlayerShip.Position.X,
mPlayerShip.Position.Y,
mPlayerShip.Position.Z,
mPlayerShip.Width,
mPlayerShip.Height,
mMinShooterZ);
mPlayerHealthBar.X = playerScreenPosition.X;
mPlayerHealthBar.Y = playerScreenPosition.Y;
if (playerScreenPosition.Y < GenericBaseApplication.GameManager.ScreenHeight / 3f)
{
mPlayerHealthBar.Y += (int)playerScreenPosition.Height / 2;
mPlayerHealthAboveLastTime = false;
}
else if (playerScreenPosition.Y > GenericBaseApplication.GameManager.ScreenHeight * 2f / 3f)
{
mPlayerHealthBar.Y -= (int)playerScreenPosition.Height / 2;
mPlayerHealthAboveLastTime = true;
}
else if (mPlayerHealthAboveLastTime)
{
mPlayerHealthBar.Y -= (int)playerScreenPosition.Height / 2;
}
else
{
mPlayerHealthBar.Y += (int)playerScreenPosition.Height / 2;
}
}
public override string DebugInfo()
{
return
"AllIsLost: " + mAllIsLost + "\n" +
"Boss: " + mBoss + "\n" +
"CurrentLevel: " + mCurrentLevel + "\n" +
"CurrentPhase: " + mCurrentPhase + "\n" +
"GameState: " + mGameState + "\n" +
"Initialized: " + mInitialized + "\n" +
"PhaseQueue: " + mPhaseQueue.ToString() + "\n" +
"PlayerLives: " + mPlayerLives + "\n" +
"PlayerShip: " + mPlayerShip + "\n" +
"PlayerWormhole: " + mPlayerWormhole + "\n" +
"Unit: " + mUnit + "\n" +
"UnitWormhole: " + mUnitWormhole;
}
}
}