/* * 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; } } }