#region Copyright
/*
--------------------------------------------------------------------------------
This source file is part of Xenocide
by Project Xenocide Team
For the latest info on Xenocide, see http://www.projectxenocide.com/
This work is licensed under the Creative Commons
Attribution-NonCommercial-ShareAlike 2.5 License.
To view a copy of this license, visit
http://creativecommons.org/licenses/by-nc-sa/2.5/
or send a letter to Creative Commons, 543 Howard Street, 5th Floor,
San Francisco, California, 94105, USA.
--------------------------------------------------------------------------------
*/
/*
* @file BaseInfoScreen.cs
* @date Created: 2009/10/20
* @author File creator: John Perrin
* @author Credits: none
*/
#endregion
#region Using Statements
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Input;
using ProjectXenocide.Utils;
using ProjectXenocide.Model;
using ProjectXenocide.Model.Geoscape;
using ProjectXenocide.Model.Geoscape.Outposts;
using ProjectXenocide.Model.Geoscape.Vehicles;
using ProjectXenocide.Model.Geoscape.AI;
using ProjectXenocide.UI.Scenes.Common;
#endregion
namespace ProjectXenocide.UI.Scenes.Geoscape
{
///
/// Used to draw an icon representation of a point of interest on the geoscape
///
class GeoHud
{
#region privates
SpriteBatch hudSprites;
MouseState mouseState;
SpriteFont hudFont;
String selectedItem = "";
string TempSelectedItem;
Texture2D iconTex;
SpriteMap iconSprites;
private const float OCCLUDEDISTANCE = 1.2f;
#endregion
///
/// This holds a list of all icons so far detected in the current ron on the geoscape
///
Dictionary iconStates = new Dictionary();
///
/// A list of the different icon typs that can be drawn
///
public enum HudIconTypes
{
XCorpBase = 0,
AlienBase = 1,
TerrorSite = 2,
XCorpCraft = 3,
UfoFly = 4,
UfoLand = 5,
UfoCrash = 6,
OccludedIcon = 19
}
///
/// Load the graphic content of the scene
///
/// content manager that fetches the content
/// the display
public void LoadContent(ContentManager content, GraphicsDevice device)
{
hudSprites = new SpriteBatch(device);
iconTex = content.Load(@"Content\Textures\Geoscape\IconSpriteMap");
hudFont = content.Load(@"Content\SpriteFont1");
iconSprites = new SpriteMap(iconTex,4,5);
}
///
/// Starts the icon rendering process
///
public void Begin()
{
mouseState = Mouse.GetState();
TempSelectedItem = string.Empty;
hudSprites.Begin();
}
///
/// finishes the icon rendering process
///
///
public void End()
{
selectedItem = TempSelectedItem;
hudSprites.End();
}
#region Icon Methods
///
/// Checks to see if icon state for a particular geo object is already being held. If not
/// create a new iconstate for it.
///
///
///
private IconState GetIconState(string iconName)
{
IconState returnVal;
if (!iconStates.TryGetValue(iconName, out returnVal))
{
returnVal = new IconState();
iconStates.Add(iconName, returnVal);
}
return returnVal;
}
///
/// This is the method called by geoscape scene to draw an icon
///
///
///
///
///
///
///
///
public void DrawIcon(GraphicsDevice device,
IGeoPosition iconGeoPosition,
BasicEffect basicEffect,
GameTime gameTime,
GeoPosition cameraGeoPosition,
string objectName,
HudIconTypes iconType)
{
Vector3 position = iconGeoPosition.Position.Polar;
Vector2 screenPos = Get2DProjection(position, device, basicEffect);
IconState myIcon = GetIconState(objectName);
bool blnHovering = IsHovering(screenPos);
// if icon is sufficiently far from camera, assume on other side of world
if (iconGeoPosition.Position.Distance(cameraGeoPosition) <= OCCLUDEDISTANCE)
{
if (blnHovering)
{
PlayMouseOver(objectName);
TempSelectedItem = objectName;
}
myIcon.Next(blnHovering, gameTime);
DrawHudIcon(iconType, screenPos, myIcon, objectName,device);
}
else
{
CreateBlip(iconType,screenPos);
}
}
#endregion
#region private methods
///
/// position on globe (in polar radians)
/// World matrix used by Draw
private static Vector2 Get2DProjection(Vector3 geoObject, GraphicsDevice device, BasicEffect effect)
{
Matrix World;
geoObject.Z = 1.0f;
World = Matrix.CreateRotationX(-geoObject.Y)
* Matrix.CreateRotationY(geoObject.X)
* Matrix.CreateTranslation(GeoPosition.PolarToCartesian(geoObject));
Vector3 projectedPosition = device.Viewport.Project(Vector3.Zero, effect.Projection, effect.View, World);
return new Vector2(projectedPosition.X, projectedPosition.Y);
}
///
/// Internal call to draw an icon.
///
///
///
///
///
private void DrawHudIcon(HudIconTypes iconType, Vector2 position, IconState iconState, string text, GraphicsDevice device)
{
if (0 < iconState.Scale)
{
SpriteItem outer = iconSprites[7];
outer.Draw(hudSprites, position, new Color(1.0f, 1.0f, 1.0f, 1.0f), iconState.Rotation, new Vector2((outer.Width / 2), (outer.Height / 2)), iconState.Scale, SpriteEffects.None, 0.0F);
}
SpriteItem inner = iconSprites[(int)iconType];
inner.Draw(hudSprites, position, new Color(1.0f, 1.0f, 1.0f, 1.0f), 0.0f, new Vector2((inner.Width / 2), (inner.Height / 2)), 1.0F, SpriteEffects.None, 0.0F);
// Get text to display on the icon
string iconText = iconState.GetTextToDisplay(text);
if (iconText != string.Empty)
{
// Get the size of the text
Vector2 textSize = hudFont.MeasureString(iconText);
// Text position set to right hand side of icon, cast to int to prevent 'fuzzy' text
Vector2 absTextPosition = position;
absTextPosition.X = (int)(absTextPosition.X += 30);
absTextPosition.Y = (int)(absTextPosition.Y - (textSize.Y / 2));
// Dont let the text exceed right hand side of screen.
if ((hudFont.MeasureString(text).X + position.X + 50) >= device.Viewport.Width)
{
absTextPosition.X = device.Viewport.Width - hudFont.MeasureString(text).X - 30;
absTextPosition.Y += 30;
}
// Set the recectangles that border,and leftborder the text.
Rectangle background = new Rectangle(Convert.ToInt32(absTextPosition.X) - 5, Convert.ToInt32(absTextPosition.Y), Convert.ToInt32(textSize.X) + 10, Convert.ToInt32(textSize.Y));
// Draw these rectangles
iconSprites[(int)iconState.FlashFrameToDraw].Draw(hudSprites, background, new Color(Color.White, 0.8f));
hudSprites.DrawString(hudFont, iconText, absTextPosition, Color.White);
}
}
///
/// Create a blip used to reprensent the object when it moves out of view on the geoscape
///
///
///
private void CreateBlip(HudIconTypes iconType,Vector2 position)
{
Color oCol = Color.White;
if (iconType <= HudIconTypes.UfoCrash)
{
oCol = obscuredIconColors[(int)iconType];
}
SpriteItem inner = iconSprites[(int)HudIconTypes.OccludedIcon];
inner.Draw(hudSprites, position, oCol, 0.0f, new Vector2((inner.Width / 2), (inner.Height / 2)), 1.0F, SpriteEffects.None, 0.0F);
}
///
/// Determines if the mouse point is psoitioned over a given position
///
///
///
private bool IsHovering(Vector2 position)
{
float deltaX = mouseState.X - position.X;
float deltaY = mouseState.Y - position.Y;
return (deltaX * deltaX + deltaY * deltaY < 256.0) &&
TempSelectedItem == String.Empty;
}
///
/// If selected object wasnt selected in the last pass, play the mouse over soundeffect.
///
///
private void PlayMouseOver(string objectName)
{
if (selectedItem != objectName)
{
Xenocide.AudioSystem.PlaySound("Menu\\buttonover.ogg");
}
}
///
/// Sound to play if the rendered icon text has changed. Typewrite effect?
///
///
private void PlayTextChanged()
{
Xenocide.AudioSystem.PlaySound("Menu\\buttonover.ogg");
}
///
/// Colours to use for icons that are on far side of Earth
///
private Color[] obscuredIconColors = new Color[]
{
new Color(Color.Blue,0.7f),
new Color(Color.Pink, 0.7f),
new Color(Color.Orange, 0.7f),
new Color(Color.Yellow, 0.7f),
new Color(Color.Red, 0.7f),
new Color(Color.Green , 0.7f),
new Color(Color.White, 0.7f)
};
#endregion
#region IconStateClass
///
/// IconState class, hold persistent information for each geoobject that relates to the current
/// state of it's on screen icon.
///
public class IconState
{
private const float fluxCapacitor = 15.0f;
///
/// Time in seconds between hover over and full focus icon size
///
private const float scaleUpRate = 200.0f;
// maximum size of icon
private const float maxIconScale = 1.0f;
//outer ring rotation speed
private const float outerRotationSpeed = 0.025f;
// max rotation value
private const float fullCircle = 360.0f;
// text area flash animation control values
private const int flashAnimStartChance = 30;
private const int flashStartFrame = 8;
private const int flashEndFrame = 18;
private const float flashSpeed = 300.0f;
private const float flashCheckInterval = 200.0f;
///
/// returns time since icon was hovered over
///
///
///
private double msSinceMouseOver(GameTime gameTime)
{
return gameTime.TotalRealTime.TotalMilliseconds - timeCreated.TotalMilliseconds;
}
public string GetTextToDisplay(string name)
{
string textToDraw = string.Empty;
// only do this if we have some text to draw.
if( ticks > 0)
{
// Get the text we will be drawing
textToDraw = name.Substring(0, Math.Min(name.Length, ticks)).ToUpper();
}
oldText = textToDraw;
return textToDraw;
}
private void UpdateHoverStatus(GameTime time, bool blnHovered)
{
if (blnHovered && !previouslyHovered)
{ // newly entered
timeCreated = time.TotalRealTime;
previouslyHovered = true;
}
else if (!blnHovered && previouslyHovered)
{ // newly left
// no longer previously hovered
previouslyHovered = false ;
// no ticks since hover
ticks = 0;
}
}
// called to update the state of the current icon based on screen input
public void Next(bool blnMouseOver, GameTime time)
{
UpdateHoverStatus(time, blnMouseOver);
UpdateOuterRing(time, blnMouseOver);
UpdateFlash(time, blnMouseOver);
if (blnMouseOver)
UpdateTextDisplay(time);
}
///
/// How many characters to draw of object name
///
///
private void UpdateTextDisplay(GameTime time)
{
// set number of 'tick' (arbitarty time interval) since hovered. This will become
// how many characters of the name string to display
ticks = (int)(msSinceMouseOver(time) / fluxCapacitor);
}
///
/// updates the size and roation of the icons outer ring
///
///
///
private void UpdateOuterRing(GameTime time, bool blnHovered)
{
if (blnHovered)
{
outerRingSize += (float)time.ElapsedRealTime.TotalMilliseconds;
}
else
{
outerRingSize -= (float)time.ElapsedRealTime.TotalMilliseconds;
}
outerRingSize = MathHelper.Clamp(outerRingSize, 0, scaleUpRate);
scale = (outerRingSize / scaleUpRate) * maxIconScale;
rotation -= outerRotationSpeed;
rotation = MathHelper.Clamp(rotation, -fullCircle, 0.0f);
}
///
/// Sets the status of the text animation wipe.
///
private void UpdateFlash(GameTime time, bool blnHovered)
{
if (!blnHovered)
return;
flashCheckTime += (float)time.ElapsedRealTime.TotalMilliseconds;
if (flashCheckTime > flashCheckInterval)
{
flashCheckTime = 0;
if(Xenocide.Rng.RollDice(flashAnimStartChance))
{
inFlash = true;
flashAnimTime = 0;
}
}
if(inFlash)
{
flashAnimTime += (float)time.ElapsedRealTime.TotalMilliseconds;
flashFrameToDraw = (int)((flashAnimTime/flashSpeed)*10) + 8;
if (flashFrameToDraw >= flashEndFrame )
{
flashFrameToDraw = flashStartFrame;
inFlash = false;
}
}
}
#region Fields
///
/// Outer ring current rotation
///
float rotation = 0.0f;
public float Rotation
{
get { return rotation; }
}
float scale = 0.0f;
///
/// Icon current scale
///
public float Scale
{
get { return scale; }
}
///
/// internal value that holds time since hover over occured.
///
private TimeSpan timeCreated;
///
/// Size of the icon outer ring
///
private float outerRingSize;
///
/// Time since last flash animation check
///
private float flashCheckTime = 0;
///
/// Time since being of last animation check
///
private float flashAnimTime = 0;
private int ticks = 0;
///
/// Number of ticks since icon started
///
public int Ticks
{
get { return Convert.ToInt32(ticks); }
}
///
/// Use to remember the hover status
///
private bool previouslyHovered;
///
/// USed to remember previously rendered text
///
private string oldText;
private int flashFrameToDraw = 8;
///
/// Current frame index of the text area animation effect
///
public int FlashFrameToDraw
{
get { return flashFrameToDraw; }
}
///
/// Are we in the midst of a run of the text area animation effect
///
private bool inFlash = false;
#endregion
}
#endregion
}
}