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