/* * ColonyGame.cs * Authors: August Zinsser * * Copyright Matthew Belmonte 2007 */ #region Using Statements using System; using System.Diagnostics; using System.Collections.Generic; 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 tAC_Engine; using Pina3D; #endregion namespace Astropolis.ColonySimulator { /// /// This is the main class that handles the actual colony simulator. /// The program calls ColonyGame.Save() before going to a minigame. /// That minigame's scorecard gives resources to this colony simulator and then reloads this simulator. /// public class ColonyGame : GenericGame { // Fixed Parameters (either hardcoded or loaded in from a user's preferences file) protected const float CAMERA_NEAR_PLANE = 1f; protected const float CAMERA_FAR_PLANE = 1000f; protected const float CAMERA_MAX_HEIGHT = 1000f; // Relative to the origin protected const float CAMERA_MIN_HEIGHT = 5f; // Relative to the ground directly below the camera protected const float CAMERA_TARGET_DISTANCE = 10f; // 2D-Projected (xy) distance between the camera and its target point while zooming protected const float CAMERA_ORBIT_DISTANCE = 100f; // 2D-Projected (xy) distance between the camera and its pivot piont while orbiting protected const float TIME_SPEED_NORMAL = 24f / 10f; // 24 "visual hours" elapse every 300 real seconds protected const float TIME_SPEED_FAST2 = 2 * TIME_SPEED_NORMAL; protected const float TIME_SPEED_FAST4 = 4 * TIME_SPEED_NORMAL; protected const float GAME_MONTHS_PER_VISUAL_DAY = 1f; // One "game month" per "visual day" protected const float YEARS_PER_MONTH = 1f / 12f; protected const float WEEKS_PER_MONTH = 4f; protected const float DAYS_PER_MONTH = 28f; protected const float NOON_GLOBAL_AMBIENT = .1f; // Lighting params protected const float MIDNIGHT_GLOBAL_AMBIENT = .1f; // '' protected const float NOON_GLOBAL_POWER = .9f; // '' protected const float MIDNIGHT_GLOBAL_POWER = .2f; // '' protected Color NOON_GLOBAL_COLOR = Color.White; // '' protected Color MIDNIGHT_GLOBAL_COLOR = new Color(75, 100, 255); // '' protected float mKeyboardScrollSpeed = 20f; protected float mKeyboardZoomSpeed = 10; protected float mKeyboardOrbitSpeed = 2f; protected float mMouseScrollSpeed = 20f; protected float mMouseZoomSpeed = .0075f; protected float mMouseOrbitSpeed = .5f; protected float mZoomSteepness = 25f; // Higher values mean the camera looks more in the -z direction as it zooms out protected float mSpeedToHeightFactor = 50f; // Higher values mean the camera pans faster as it zooms out protected float mZoomToHeightFactor = 100f; // Higher values mean the camera zooms faster as it zooms out // Instanced Parameters (calculated during initialize, so may change between instances of this game) protected int mMouseScrollLeftX; protected int mMouseScrollRightX; protected int mMouseScrollUpY; protected int mMouseScrollDownY; // Game Data (changes during the course of the game) protected ColonyLevel mLevel; protected float mCameraCurHeight = 50f; protected Vector3 mCameraTargetPoint; protected Vector2 mMousePreviousLocation; protected float mDGameTime; // Game time (in days) that has elapsed since the last game update protected float mVisualHours; // 24 hours per day protected float mTimeSpeed = TIME_SPEED_NORMAL; protected Vector3 mGlobalLightDirection; // Sun/Moonlight protected GenericScoreCard mProductionYTD; // Remembers the outputs of all buildings in the colony this year protected GenericScoreCard mConsumedYTD; // Remembers the used inputs of all buildings in the colony this year protected GenericScoreCard mMissionEarningsYTD; // Remembers the rewards from minigames this year protected Structure mTempStructure1; protected Structure mTempStructure2; protected Structure mTempWarehouse; public ColonyLevel Level { get { return mLevel; } } public int Money { get { return mStockpile.QueryResource(AstroResources.Credits); } } /// /// Affects how fast the camera pans /// public float KeyboardScrollSpeed { set { mKeyboardScrollSpeed = value; } get { return mKeyboardScrollSpeed; } } /// /// Affects how fast the camera zooms /// public float KeyboardZoomSpeed { set { mKeyboardZoomSpeed = value; } get { return mKeyboardZoomSpeed; } } /// /// Affects how fast the camera orbits /// public float KeyboardOrbitSpeed { set { mKeyboardOrbitSpeed = value; } get { return mKeyboardOrbitSpeed; } } /// /// Affects how fast the camera pans /// public float MouseScrollSpeed { set { mMouseScrollSpeed = value; } get { return mMouseScrollSpeed; } } /// /// Affects how fast the camera zooms /// public float MouseZoomSpeed { set { mMouseZoomSpeed = value; } get { return mMouseZoomSpeed; } } /// /// Affects how fast the camera orbits /// public float MouseOrbitSpeed { set { mMouseOrbitSpeed = value; } get { return mMouseOrbitSpeed; } } /// /// Assign the stockpile to the colony resource type /// public ColonyGame() { mLevel = new ColonyLevel(); } /// /// (Re)initializes the game /// public void Initialize() { // Setup the camera and lights Pina.NearPlane = CAMERA_NEAR_PLANE; Pina.FarPlane = CAMERA_FAR_PLANE; Pina.Camera.FinishLerping(); Pina.Camera.Position = new Vector3(0, 50f, mCameraCurHeight); mCameraTargetPoint = new Vector3(CAMERA_TARGET_DISTANCE, 50f, CAMERA_TARGET_DISTANCE); Pina.Camera.TargetPoint = mCameraTargetPoint; // // Load level data // // TODO: read this information from a save file. For now just hardcode a new level. mLevel.Initialize(200, 200); mVisualHours = 12f; mStockpile = new GenericScoreCard(); foreach (string commodity in Commodities.List) mStockpile.Gather(commodity, 0); mProductionYTD = new GenericScoreCard(); foreach (string commodity in Commodities.List) mProductionYTD.Gather(commodity, 0); mConsumedYTD = new GenericScoreCard(); foreach (string commodity in Commodities.List) mConsumedYTD.Gather(commodity, 0); mMissionEarningsYTD = new GenericScoreCard(); foreach (string commodity in Commodities.List) mMissionEarningsYTD.Gather(commodity, 0); // // (Re)Calculate parameters // // Scrolling parameters int pixelBorder = 2; mMouseScrollLeftX = pixelBorder; mMouseScrollRightX = GenericBaseApplication.GameManager.ScreenWidth - pixelBorder; mMouseScrollUpY = pixelBorder; mMouseScrollDownY = GenericBaseApplication.GameManager.ScreenHeight - pixelBorder; // Game State mDGameTime = 0f; //////////////////////////////////////////////////////////////////////////////////////////// // TEMP: Hardcode some buildings Matrix transform = Matrix.CreateTranslation(10f, 10f, 25f); Dictionary produces = new Dictionary(); produces.Add(Commodities.Temp1, 1); FactoryType horsery = new FactoryType( new PinaModel(PinaState.Default, new ColladaFile(@"Content\Models\10mBox")), 1, 1, new Dictionary(), produces, 1f); mTempStructure1 = new Factory(horsery, new StructureState(transform, new Rectangle(0, 0, 1, 1))); transform = Matrix.CreateTranslation(12f, 10f, 25f); produces = new Dictionary(); produces.Add(Commodities.Temp2, 1); Dictionary needs = new Dictionary(); needs.Add(Commodities.Temp1, 10); FactoryType glueFactory = new FactoryType( new PinaModel(PinaState.Default, new ColladaFile(@"Content\Models\10mBox")), 1, 1, needs, produces, 1f); mTempStructure2 = new Factory(glueFactory, new StructureState(transform, new Rectangle(0, 0, 1, 1))); transform = Matrix.CreateTranslation(14f, 20f, 25f); List stores = new List(); stores.Add(Commodities.Temp1); stores.Add(Commodities.Temp2); WarehouseType myWarehouse = new WarehouseType( new PinaModel(PinaState.Default, new ColladaFile(@"Content\Models\10mBox")), 1, 1, stores, 50); mTempWarehouse = new Warehouse(myWarehouse, new StructureState(transform, new Rectangle(0, 0, 1, 1))); //////////////////////////////////////////////////////////////////////////////////////////// // Ensure that data propagates through the game before trying to draw Update(); } /// /// Increases a specific overall resource /// /// Type of resource to add /// Amount of that resource to add public override void IncreaseResource(string resourceType, int amount) { // Make sure that a minigame doesn't try to add an unknown resource if (!Commodities.List.Contains(resourceType)) { System.Diagnostics.Debug.Assert(false, "Colony Simulator does not recognize resource " + resourceType); return; } // Remember money was earned from a mission // TODO: initialize the omnipresent part of the colony sime before minigames are run //mMissionEarningsYTD.Gather(resourceType, amount); // TODO: try to put it in a warehouse. If failed, store it in the stockpile (wasted) mStockpile.Gather(resourceType, amount); } /// /// Ask about the overall quantity of a specific resource /// /// Type of resource to inquire /// Overall amount of that resource public override int QueryResource(string resourceType) { // TODO: Sum up the total resources stored in warehouse. For now, just return what's in the stockpile return mStockpile.QueryResource(resourceType); } /// /// Will always fail for the ColonyGame since resources cannot be taken from it /// /// Type of resource to decrement /// Amount to decrement /// True if successful, False otherwise (for lack of funds) public override bool DecreaseResource(string resourceType, int amount) { return false; } /// /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input and playing audio. /// public override void Update() { base.Update(); // Update timers float dT = GenericBaseApplication.GameManager.ElapsedSeconds; mDGameTime = dT * mTimeSpeed * GAME_MONTHS_PER_VISUAL_DAY * DAYS_PER_MONTH; // Change in days mVisualHours += dT * mTimeSpeed; // Update rendering parameters ProcessCameraMovement(); float dayPercentage = ((mVisualHours - 12) % 24f) / 24f; // Assign 0 to noon for the light calculations float arcPercentage = dayPercentage * (float)Math.PI * 2f; Pina.GlobalLightDirection = new Vector3(0f, 1f * (float)Math.Cos(arcPercentage), 1f * (float)Math.Abs(Math.Sin(arcPercentage))); Pina.GlobalLightAmbient = (1 - dayPercentage) * NOON_GLOBAL_AMBIENT + dayPercentage * MIDNIGHT_GLOBAL_AMBIENT; Pina.GlobalLightColor = new Color((Vector4.Multiply(NOON_GLOBAL_COLOR.ToVector4(), 1 - dayPercentage) + Vector4.Multiply(MIDNIGHT_GLOBAL_COLOR.ToVector4(), dayPercentage))); Pina.GlobalLightPower = (1 - dayPercentage) * NOON_GLOBAL_POWER + dayPercentage * MIDNIGHT_GLOBAL_POWER; Pina.GlobalLightDirection = new Vector3(1f, 0f, 1f); Pina.GlobalLightAmbient = .01f; Pina.GlobalLightColor = Color.White; Pina.GlobalLightPower = 1f; mLevel.Update(dT); //////////////////////////////////////////////////////////////////////////////////////////// // TEMP: test goods shipments mTempStructure1.Update(dT); mTempStructure2.Update(dT); mTempWarehouse.Update(dT); // Ship from 1 to the factory bool shipped = ((Building)mTempStructure1).Ship(Commodities.Temp1, 1); if (shipped) { mProductionYTD.Gather(Commodities.Temp1, 1); if (!((Warehouse)mTempWarehouse).Deliver(Commodities.Temp1, 1)) mStockpile.Gather(Commodities.Temp1, 1); } //shipped = ((Building)mTempStructure2).Ship(Commodities.Temp2, 1); //if (shipped) //{ // mProductionYTD.Gather(Commodities.Temp2, 1); // mStockpile.Gather(Commodities.Temp2, 1); //} float o1 = ((Factory)mTempStructure1).PercentageOutputInventory(Commodities.Temp1); float i2 = ((Factory)mTempStructure2).PercentageInputInventory(Commodities.Temp1); float o2 = ((Factory)mTempStructure2).PercentageOutputInventory(Commodities.Temp2); //////////////////////////////////////////////////////////////////////////////////////////// } /// /// Draws the level and HUD elements to the scree n /// public void Draw() { //////////////////////////////////////////////////////////////////////////////////////////// // TEMP: print the resources to the screen // Surplus (the stockpile) int x = (int)(GenericBaseApplication.GameManager.ScreenWidth * .40f); int y = 10; Ticker.Display( "SURPLUS YTD", null, Vector2.Zero, new Vector2(x, -GenericBaseApplication.GameManager.ScreenHeight / 2 + y), Vector2.Zero, Ticker.Font.Standard, Color.Blue, 1f, .02f, false); foreach (string commodity in Commodities.List) { y += 15; Ticker.Display( mStockpile.QueryResource(commodity) + Commodities.GetUnitDescription(commodity) + commodity, null, Vector2.Zero, new Vector2(x, -GenericBaseApplication.GameManager.ScreenHeight / 2 + y), Vector2.Zero, Ticker.Font.Standard, Color.Turquoise, 1f, .02f, false); } // In Warehouses x = (int)(GenericBaseApplication.GameManager.ScreenWidth * .25f); y = 10; Ticker.Display( "IN WAREHOUSES", null, Vector2.Zero, new Vector2(x, -GenericBaseApplication.GameManager.ScreenHeight / 2 + y), Vector2.Zero, Ticker.Font.Standard, Color.Blue, 1f, .02f, false); foreach (string commodity in Commodities.List) { y += 15; Ticker.Display( ((Warehouse)mTempWarehouse).InputInventory.ContainsKey(commodity) ? ((Warehouse)mTempWarehouse).InputInventory[commodity] + Commodities.GetUnitDescription(commodity) + commodity : 0 + Commodities.GetUnitDescription(commodity) + commodity, null, Vector2.Zero, new Vector2(x, -GenericBaseApplication.GameManager.ScreenHeight / 2 + y), Vector2.Zero, Ticker.Font.Standard, Color.Turquoise, 1f, .02f, false); } // Consumed to YTD x = (int)(GenericBaseApplication.GameManager.ScreenWidth * .10f); y = 10; Ticker.Display( "CONSUMED YTD", null, Vector2.Zero, new Vector2(x, -GenericBaseApplication.GameManager.ScreenHeight / 2 + y), Vector2.Zero, Ticker.Font.Standard, Color.Blue, 1f, .02f, false); foreach (string commodity in Commodities.List) { y += 15; Ticker.Display( mConsumedYTD.QueryResource(commodity) + Commodities.GetUnitDescription(commodity) + commodity, null, Vector2.Zero, new Vector2(x, -GenericBaseApplication.GameManager.ScreenHeight / 2 + y), Vector2.Zero, Ticker.Font.Standard, Color.Turquoise, 1f, .02f, false); } // Produced to YTD x = (int)(GenericBaseApplication.GameManager.ScreenWidth * -.05f); y = 10; Ticker.Display( "PRODUCED YTD", null, Vector2.Zero, new Vector2(x, -GenericBaseApplication.GameManager.ScreenHeight / 2 + y), Vector2.Zero, Ticker.Font.Standard, Color.Blue, 1f, .02f, false); foreach (string commodity in Commodities.List) { y += 15; Ticker.Display( mProductionYTD.QueryResource(commodity) + Commodities.GetUnitDescription(commodity) + commodity, null, Vector2.Zero, new Vector2(x, -GenericBaseApplication.GameManager.ScreenHeight / 2 + y), Vector2.Zero, Ticker.Font.Standard, Color.Turquoise, 1f, .02f, false); } //sb.End(); // END TEMP //////////////////////////////////////////////////////////////////////////////////////////// } /// /// Queries user input and moves the camera accordingly /// private void ProcessCameraMovement() { float dT = GenericBaseApplication.GameManager.ElapsedSeconds; // Find the height of the ground the camera is over float localMin = mLevel.GetGroundHeight(new Vector2(Pina.Camera.X, Pina.Camera.Y)); localMin += CAMERA_MIN_HEIGHT; // Panning Vector3 moveDir = Vector3.Zero; bool usedKeyboard = false; bool usedMouse = false; if (InputState.IsKeyUp(Keys.LeftControl) && InputState.IsKeyUp(Keys.RightControl)) { if (InputState.IsKeyDown(Keys.Left)) { moveDir -= Pina.Camera.Right; usedKeyboard = true; } if (InputState.IsKeyDown(Keys.Right)) { moveDir += Pina.Camera.Right; usedKeyboard = true; } if (InputState.IsKeyDown(Keys.Up)) { moveDir += Pina.Camera.Forward; usedKeyboard = true; } if (InputState.IsKeyDown(Keys.Down)) { moveDir -= Pina.Camera.Forward; usedKeyboard = true; } } if (InputState.IsMouseMiddleUp) { if (InputState.MouseLocation.X < mMouseScrollLeftX) { moveDir -= Pina.Camera.Right; usedMouse = true; } if (InputState.MouseLocation.X >= mMouseScrollRightX) { moveDir += Pina.Camera.Right; usedMouse = true; } if (InputState.MouseLocation.Y < mMouseScrollUpY) { moveDir += Pina.Camera.Forward; usedMouse = true; } if (InputState.MouseLocation.Y >= mMouseScrollDownY) { moveDir -= Pina.Camera.Forward; usedMouse = true; } } moveDir.Z = 0; if (moveDir.LengthSquared() > 0) { moveDir = Vector3.Normalize(moveDir); float speedIncrease = 1 + (mSpeedToHeightFactor * (Pina.Camera.Z - CAMERA_MIN_HEIGHT) / (CAMERA_MAX_HEIGHT - CAMERA_MIN_HEIGHT)); if (usedKeyboard) moveDir = Vector3.Multiply(moveDir, mKeyboardScrollSpeed * speedIncrease * dT); else if (usedMouse) moveDir = Vector3.Multiply(moveDir, mMouseScrollSpeed * speedIncrease * dT); } mCameraTargetPoint += moveDir; Pina.Camera.Position += moveDir; // Zooming (also zoom out if the camera is underground) if (Pina.Camera.Z < localMin || InputState.DeltaScrollWheel != 0f || ((InputState.IsKeyDown(Keys.LeftControl) || InputState.IsKeyDown(Keys.RightControl)) && (InputState.IsKeyDown(Keys.Up) || InputState.IsKeyDown(Keys.Down)))) { float speedIncrease = 1 + (mZoomToHeightFactor * (Pina.Camera.Z - CAMERA_MIN_HEIGHT) / (CAMERA_MAX_HEIGHT - CAMERA_MIN_HEIGHT)); // Scroll wheel mCameraCurHeight -= InputState.DeltaScrollWheel * mMouseZoomSpeed * speedIncrease; // ctrl+Up/Down if ((InputState.IsKeyDown(Keys.LeftControl) || InputState.IsKeyDown(Keys.RightControl)) && InputState.IsKeyDown(Keys.Up)) mCameraCurHeight -= mKeyboardZoomSpeed * speedIncrease * dT; if ((InputState.IsKeyDown(Keys.LeftControl) || InputState.IsKeyDown(Keys.RightControl)) && InputState.IsKeyDown(Keys.Down)) mCameraCurHeight += mKeyboardZoomSpeed * speedIncrease * dT; // Make sure the height doesn't go out of bounds if (mCameraCurHeight < localMin) mCameraCurHeight = localMin; if (mCameraCurHeight > CAMERA_MAX_HEIGHT) mCameraCurHeight = CAMERA_MAX_HEIGHT; // Smooth the zooming //Pina.Camera.LerpZ(Pina.Camera.Z, mCameraCurHeight, .2f); Pina.Camera.Z = mCameraCurHeight; } // Orbit if (InputState.IsMouseMiddleDown || ((InputState.IsKeyDown(Keys.LeftControl) || InputState.IsKeyDown(Keys.RightControl)) && (InputState.IsKeyDown(Keys.Left) || InputState.IsKeyDown(Keys.Right)))) { // Calculate the radians to rotate float dRad = 0f; // Middle Mouse + move if (InputState.WasMouseMiddleUp) { mMousePreviousLocation = InputState.MouseLocation; } if (InputState.IsMouseMiddleDown) { dRad = (mMousePreviousLocation.X - InputState.MouseLocation.X) * mMouseOrbitSpeed * dT; } // Update the mouse tracking mMousePreviousLocation = InputState.MouseLocation; // ctrl+Left/Right if ((InputState.IsKeyDown(Keys.LeftControl) || InputState.IsKeyDown(Keys.RightControl)) && InputState.IsKeyDown(Keys.Left)) dRad -= mKeyboardOrbitSpeed * dT; if ((InputState.IsKeyDown(Keys.LeftControl) || InputState.IsKeyDown(Keys.RightControl)) && InputState.IsKeyDown(Keys.Right)) dRad += mKeyboardOrbitSpeed * dT; // Get the orbit's pivot point Vector3 pivotOffset = Pina.Camera.Forward; pivotOffset.Z = 0; pivotOffset.Normalize(); pivotOffset = Vector3.Multiply(pivotOffset, CAMERA_ORBIT_DISTANCE); Vector3 invPivotOffset = -pivotOffset; Vector3 pivot = Pina.Camera.Position + pivotOffset; // Rotate the camera about the pivot Matrix rotate = Matrix.CreateFromAxisAngle(Vector3.UnitZ, dRad); invPivotOffset = Vector3.Transform(invPivotOffset, rotate); Pina.Camera.Position = pivot + invPivotOffset; // Reorient the camera pivotOffset = -invPivotOffset; pivotOffset.Normalize(); pivotOffset = Vector3.Multiply(pivotOffset, CAMERA_TARGET_DISTANCE); mCameraTargetPoint = Pina.Camera.Position + pivotOffset; } // Adjust the camera's facing mCameraTargetPoint.Z = Pina.Camera.Position.Z - (((Pina.Camera.Position.Z - CAMERA_MIN_HEIGHT) / CAMERA_MAX_HEIGHT) * mZoomSteepness); Pina.Camera.TargetPoint = mCameraTargetPoint; } } }