#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 GeoscapeScreen.cs * @date Created: 2007/01/21 * @author File creator: David Teviotdale * @author Credits: none */ #endregion #region Using Statements using System; using System.Collections.Generic; using System.Text; using System.Diagnostics; using System.Drawing; using System.Globalization; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using ProjectXenocide.Utils; using ProjectXenocide.UI.Scenes.Geoscape; using ProjectXenocide.UI.Dialogs; using ProjectXenocide.Model; using ProjectXenocide.Model.Geoscape; using ProjectXenocide.Model.Geoscape.Vehicles; using ProjectXenocide.Model.Geoscape.Outposts; using ProjectXenocide.Model.Geoscape.Geography; using ProjectXenocide.Model.Geoscape.AI; using System.Threading; using Xenocide.Resources; #endregion namespace ProjectXenocide.UI.Screens { /// /// The Geoscape screen /// public partial class GeoscapeScreen : PolarScreen { /// /// constructor (obviously) /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "FxCop False Positive")] public GeoscapeScreen() : base("GeoscapeScreen") { Scene = new GeoscapeScene(oldCameraPosition); State = new ViewGeoscapeScreenState(this); Xenocide.AudioSystem.PlayRandomMusic("PlanetView"); } /// /// Implement Dispose /// protected override void Dispose(bool disposing) { try { if (disposing) { if (geoscapeScene != null) { geoscapeScene.Dispose(); Scene = null; } } } finally { base.Dispose(disposing); } } #region Create the CeGui widgets /// /// add the buttons to the screen /// protected override void CreateCeguiWidgets() { SetView(0.00f, 0.00f, 0.745f, 1f); // add text giving the time gameTimeTop = AddStaticText(0.7574f, 0.07f, 0.2275f, 0.05f); gameTimeHour = AddStaticText(0.79f, 0.08f, 0.4275f, 0.08f); gameTimeSec = AddStaticText(0.888f, 0.095f, 0.4275f, 0.08f); fundsText = AddStaticText(0.754f, 0.02f, 0.2275f, 0.04125f); fundsAmount = AddStaticText(0.84f, 0.015f, 0.2275f, 0.04125f); sceneToolTip = AddStaticText(0.7475f, 0.25f, 0.3f, 0.3f); timeText = AddStaticText(0.925f, 0.095f, 0.2275f, 0.08f); sceneToolTip.VerticalAlignment = CeGui.VerticalAlignment.Top; // Set Font and color for text SetTimeFont(gameTimeTop, "GeoTime"); SetTimeFont(gameTimeHour, "GeoTimeBig"); SetTimeFont(gameTimeSec, "GeoTime"); SetTimeFont(timeText, "GeoTime"); SetFont(fundsAmount, "XenoBig"); // change time buttons timeStopButton = AddButton("BUTTON_TIME_STOP", 0.750f, 0.16f, 0.050f, 0.04125f); timeNormalButton = AddButton("BUTTON_TIME_X60", 0.810f, 0.16f, 0.050f, 0.04125f, "PlanetView\\speedslow.ogg"); timeHourButton = AddButton("BUTTON_TIME_X3600", 0.870f, 0.16f, 0.050f, 0.04125f, "PlanetView\\speedfast.ogg"); timeDayButton = AddButton("BUTTON_TIME_X86400", 0.930f, 0.16f, 0.050f, 0.04125f, "PlanetView\\speedveryfast.ogg"); interceptButton = AddButton("BUTTON_INTERCEPT", 0.7475f, 0.51f, 0.2275f, 0.04125f); basesButton = AddButton("BUTTON_BASES", 0.7475f, 0.56f, 0.2275f, 0.04125f); researchButton = AddButton("BUTTON_RESEARCH", 0.7475f, 0.61f, 0.2275f, 0.04125f); fundingButton = AddButton("BUTTON_FUNDING", 0.7475f, 0.66f, 0.2275f, 0.04125f); statisticsButton = AddButton("BUTTON_STATISTICS", 0.7475f, 0.71f, 0.2275f, 0.04125f); xnetButton = AddButton("BUTTON_XNET", 0.7475f, 0.76f, 0.2275f, 0.04125f); optionsButton = AddButton("BUTTON_OPTIONS", 0.7475f, 0.81f, 0.2275f, 0.04125f); // move camera buttons cameraUpButton = AddStaticImageButton("BUTTON_UP", 0.7890f, 0.885f, 0.020f, 0.03125f, "PanUpNormal", "PanUpPushed", "Menu\\buttonclick1_ok.ogg"); cameraDownButton = AddStaticImageButton("BUTTON_DOWN", 0.7890f, 0.965f, 0.020f, 0.03125f, "PanDownNormal", "PanDownPushed", "Menu\\buttonclick1_ok.ogg"); cameraLeftButton = AddStaticImageButton("BUTTON_LEFT", 0.7575f, 0.925f, 0.030f, 0.03125f, "PanLeftNormal", "PanLeftPushed", "Menu\\buttonclick1_ok.ogg"); cameraRightButton = AddStaticImageButton("BUTTON_RIGHT", 0.8100f, 0.925f, 0.030f, 0.03125f, "PanRightNormal", "PanRightPushed", "Menu\\buttonclick1_ok.ogg"); cameraInButton = AddStaticImageButton("BUTTON_ZOOM_IN", 0.8950f, 0.915f, 0.020f, 0.03125f, "ZoomInNormal", "ZoomInPushed", "PlanetView\\zoomin.ogg"); cameraOutButton = AddStaticImageButton("BUTTON_ZOOM_OUT", 0.8625f, 0.955f, 0.020f, 0.03125f, "ZoomOutNormal", "ZoomOutPushed", "PlanetView\\zoomout.ogg"); // change rate of time timeStopButton.Clicked += new CeGui.GuiEventHandler(OnTimeRateButtonClicked); timeNormalButton.Clicked += new CeGui.GuiEventHandler(OnTimeRateButtonClicked); timeHourButton.Clicked += new CeGui.GuiEventHandler(OnTimeRateButtonClicked); timeDayButton.Clicked += new CeGui.GuiEventHandler(OnTimeRateButtonClicked); interceptButton.Clicked += new CeGui.GuiEventHandler(OnInterceptButtonClicked); basesButton.Clicked += new CeGui.GuiEventHandler(ShowBasesScreen); researchButton.Clicked += new CeGui.GuiEventHandler(ShowResearchDialog); fundingButton.Clicked += new CeGui.GuiEventHandler(OnFundingButtonClicked); statisticsButton.Clicked += new CeGui.GuiEventHandler(ShowStatisticsScreen); xnetButton.Clicked += new CeGui.GuiEventHandler(ShowXNetScreen); optionsButton.Clicked += new CeGui.GuiEventHandler(ShowOptionsDialog); cameraUpButton.MouseClicked += new CeGui.MouseEventHandler(OnMoveCameraButtonClicked); cameraDownButton.MouseClicked += new CeGui.MouseEventHandler(OnMoveCameraButtonClicked); cameraLeftButton.MouseClicked += new CeGui.MouseEventHandler(OnMoveCameraButtonClicked); cameraRightButton.MouseClicked += new CeGui.MouseEventHandler(OnMoveCameraButtonClicked); cameraInButton.MouseClicked += new CeGui.MouseEventHandler(OnMoveCameraButtonClicked); cameraOutButton.MouseClicked += new CeGui.MouseEventHandler(OnMoveCameraButtonClicked); //Change button size SetFont(timeStopButton, "XenoSmall"); SetFont(timeHourButton, "XenoSmall"); SetFont(timeDayButton, "XenoSmall"); SetFont(timeNormalButton, "XenoSmall"); SetFont(cameraUpButton, "XenoSmall"); SetFont(cameraDownButton, "XenoSmall"); SetFont(cameraLeftButton, "XenoSmall"); SetFont(cameraRightButton,"XenoSmall"); //Adds text to top of screen. fundsText.Text = Strings.SCREEN_GEOSCAPE_FUNDS; fundsAmount.Text = Xenocide.GameState.GeoData.XCorp.Bank.DisplayCurrentBalance; timeText.Text = Strings.SCREEN_GEOSCAPE_GMT; // special widgets based on state CeGui.Window extraButton = state.CreateCeguiWidgets(); // nasty bit of hackary, if a button was created, need to make the button // a child of the Scene Window, because it's drawn OVER the scene window if (null != extraButton) { RootWidget.RemoveChild(extraButton); SceneWindow.AddChild(extraButton); } } private CeGui.Widgets.PushButton timeStopButton; private CeGui.Widgets.PushButton timeNormalButton; private CeGui.Widgets.PushButton timeHourButton; private CeGui.Widgets.PushButton timeDayButton; private CeGui.Widgets.PushButton interceptButton; private CeGui.Widgets.PushButton basesButton; private CeGui.Widgets.PushButton researchButton; private CeGui.Widgets.PushButton fundingButton; private CeGui.Widgets.PushButton statisticsButton; private CeGui.Widgets.PushButton xnetButton; private CeGui.Widgets.PushButton optionsButton; private CeGui.Widgets.StaticImage cameraUpButton; private CeGui.Widgets.StaticImage cameraDownButton; private CeGui.Widgets.StaticImage cameraLeftButton; private CeGui.Widgets.StaticImage cameraRightButton; private CeGui.Widgets.StaticImage cameraInButton; private CeGui.Widgets.StaticImage cameraOutButton; private CeGui.Widgets.StaticText gameTimeTop; private CeGui.Widgets.StaticText gameTimeHour; private CeGui.Widgets.StaticText gameTimeSec; private CeGui.Widgets.StaticText fundsText; private CeGui.Widgets.StaticText fundsAmount; private CeGui.Widgets.StaticText sceneToolTip; private CeGui.Widgets.StaticText timeText; //Used to keep track of time and avoid updating if needed. private string gameTimeText; /// /// Adjust static text widget for displaying the time /// /// static text widget to adjust /// Name of font widget should be using private static void SetTimeFont(CeGui.Widgets.StaticText widget, string fontName) { //Create color for fonts widget.SetTextColor(new CeGui.Colour(0.4921875f, 0.703125f, 0.609375f)); widget.Font = CeGui.FontManager.Instance.GetFont(fontName); } /// /// Set a widget's font /// /// /// private static void SetFont(CeGui.Window widget, string fontName) { widget.Font = CeGui.FontManager.Instance.GetFont(fontName); } #endregion Create the CeGui widgets #region 3D scene management /// /// Update any model data /// /// Provides a snapshot of timing values. public override void Update(GameTime gameTime) { state.Update(gameTime); // update time shown on screen // Note, if display hasn't changed, don't write new value to text window // as this will make CeGui recompute all render quads. DateTime time = Xenocide.GameState.GeoData.GeoTime.Time; String newTime = time.ToString(); // render hover text even if game time is not ticking sceneToolTip.VerticalFormat = CeGui.VerticalTextFormat.Top; if (gameTimeText != newTime) { CultureInfo culture = Thread.CurrentThread.CurrentCulture; //First we create the day, add on to it, add the month then the year. StringBuilder gametime = new StringBuilder(time.ToString("%d", culture)); if (gametime.ToString() == "1") gametime.Append("st"); else if (gametime.ToString() == "2") gametime.Append("nd"); else if (gametime.ToString() == "3") gametime.Append("rd"); else gametime.Append("th"); //Change month to uppercase string upper = time.ToString("MMMM", culture); gametime.Append(" "); gametime.Append(upper.ToUpper(culture)); gametime.Append(" "); gametime.Append(time.ToString("yyyy", culture)); gameTimeTop.Text = gametime.ToString(); gameTimeHour.Text = time.ToString("HH:mm", culture); gameTimeSec.Text = time.ToString(":ss", culture); //Set time gameTimeText = newTime; } } /// /// Called to tell the Window prior to destruction to tell it to save any state information /// needed to reconstruct it's state. /// Basically, used to get the Geoscape to store the camera's position /// when we go to a different screen. /// public override void SaveState() { oldCameraPosition = Scene.CameraPosition; } #endregion #region event handlers /// Replace this screen on display with the Bases Screen /// Not used /// Not used private void ShowBasesScreen(object sender, CeGui.GuiEventArgs e) { state.OnBasesButton(); } /// /// React to "Research" button being pressed /// private void ShowResearchDialog(object sender, CeGui.GuiEventArgs e) { state.OnResearchButton(); } /// Put the options dialog on the display /// Not used /// Not used private void ShowOptionsDialog(object sender, CeGui.GuiEventArgs e) { state.OnOptionsButton(); } /// Respond to user clicking the "Funding" button /// Not used /// Not used private void OnFundingButtonClicked(object sender, CeGui.GuiEventArgs e) { state.OnFundingButton(); } /// Replace this screen on display with the Statistics Screen /// Not used /// Not used private void ShowStatisticsScreen(object sender, CeGui.GuiEventArgs e) { state.OnStatisticsButton(); } /// Replace this screen on display with the X-Net Screen /// Not used /// Not used private void ShowXNetScreen(object sender, CeGui.GuiEventArgs e) { state.OnXNetButton(); } /// Launch interceptor from base to attack UFO /// Not used /// Not used private void OnInterceptButtonClicked(object sender, CeGui.GuiEventArgs e) { state.OnInterceptButton(); } /// /// Return true if Button is the specified button /// /// button to examine /// Name of resource string to look for /// True if Button's Name ends with the specified resource string private static bool IsButton(CeGui.Widgets.PushButton button, string resourceName) { string label = XenocideResourceManager.Get(resourceName); Debug.Assert(!String.IsNullOrEmpty(label)); return button.Name.EndsWith(label); } /// React to user clicking on one of the "move camera" buttons /// Button the user clicked /// Not used private void OnMoveCameraButtonClicked(object sender, CeGui.MouseEventArgs e) { // turn an 1/8th of a revolution const float rotation = (float)(Math.PI / 4); // Step size to move camera in const float zoomStep = 0.5f; if (sender == cameraLeftButton) { Scene.RotateCamera(-rotation, 0.0f); } else if (sender == cameraRightButton) { Scene.RotateCamera(rotation, 0.0f); } else if (sender == cameraUpButton) { Scene.RotateCamera(0.0f, rotation); } else if (sender == cameraDownButton) { Scene.RotateCamera(0.0f, -rotation); } else if (sender == cameraInButton) { Scene.ZoomCamera(-zoomStep); } else if (sender == cameraOutButton) { Scene.ZoomCamera(zoomStep); } } /// React to user clicking on one of the "time rate" buttons /// Button the user clicked /// Not used private void OnTimeRateButtonClicked(object sender, CeGui.GuiEventArgs e) { state.OnTimeRateButtonClicked((CeGui.Widgets.PushButton)sender); } /// React to user clicking left mouse button in the 3D geoscape scene /// Mouse information protected override void OnLeftMouseDownInScene(CeGui.MouseEventArgs e) { Debug.Assert(e.Button == System.Windows.Forms.MouseButtons.Left); GeoPosition pos = WindowPixelToGeoPosition(e.Position.X, e.Position.Y); if (pos != null) { state.OnLeftMouseDownInScene(pos); } } /// React to user clicking the "Cancel setting new base location" button /// Button the user clicked /// Not used [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "FxCop False Positive")] private void OnCancelNewBase(object sender, CeGui.GuiEventArgs e) { ScreenManager.ScheduleScreen(new GeoscapeScreen()); } #endregion event handlers /// /// Confirm user wants to build base here /// /// location of new base /// is this player's first base? private static void ConfirmBasePositionDialog(GeoPosition pos, bool isFirstBase) { if (Xenocide.GameState.GeoData.Planet.IsPositionOverWater(pos)) { Util.ShowMessageBox(Strings.MSGBOX_CANNOT_BUILD_ON_WATER); } else { PlanetRegion region = Xenocide.GameState.GeoData.Planet.GetRegionAtLocation(pos); int cost = isFirstBase ? 0 : region.OutpostBuildCost; string area = region.ToString(); if (Xenocide.GameState.GeoData.XCorp.Bank.CanAfford(cost)) { YesNoDialog dlg = YesNoDialog.OkCancelDialog( Util.StringFormat(Strings.YESNOMSG_BUILD_BASE_HERE, Util.StringFormat("{0:N0}", cost), area) ); // if yes is pressed, do the "base name" dialog dlg.YesAction += delegate() { if (!isFirstBase) { Xenocide.GameState.GeoData.XCorp.Bank.Debit(cost); } else { // Overmind starts sending missions near location of X-Corp base GeoPosition target = pos.RandomLocationDistantBykm(500); target = Xenocide.GameState.GeoData.Planet.GetClosestLand(target); Xenocide.GameState.GeoData.Overmind.BeginFirstMissions(target); } Xenocide.ScreenManager.QueueDialog(new NameNewBaseDialog(pos, isFirstBase)); }; Xenocide.ScreenManager.ShowDialog(dlg); } } } /// /// Return the Craft (UFO or Aircraft) that is closest to position on the Geoscape. /// /// Craft more than 500km away from position will be ignored. /// /// center of search area /// closest craft, or null if none found private static Craft FindClosestCraft(GeoPosition pos) { double distance = GeoPosition.KilometersToRadians(501); Craft nearest = pos.FindClosest(Xenocide.GameState.GeoData.Overmind.Ufos, distance); if (null != nearest) { distance = pos.Distance(nearest.Position); } foreach (Outpost outpost in Xenocide.GameState.GeoData.Outposts) { foreach (Craft craft in outpost.Fleet) { if (!craft.InBase && (craft.Position.Distance(pos) < distance)) { nearest = craft; distance = craft.Position.Distance(pos); } } } return nearest; } /// /// Convert position of pixel on screen to geoposition /// /// pixel's column /// pixel's row /// corresponding Geoposition, or null if not on Geoscape private GeoPosition WindowPixelToGeoPosition(float X, float Y) { PointF coords2 = SceneWindow.AbsoluteToRelative(new PointF( X - SceneWindow.AbsoluteX, Y - SceneWindow.AbsoluteY)); return geoscapeScene.WindowToGeoPosition(coords2); } /// /// Move camera due to mouse move /// /// details of the mouse move protected override void RotateSceneUsingMouse(CeGui.MouseEventArgs e) { // try to track cursor on globe GeoPosition final = WindowPixelToGeoPosition(e.Position.X, e.Position.Y); GeoPosition start = WindowPixelToGeoPosition(e.Position.X - e.MoveDelta.X, e.Position.Y - e.MoveDelta.Y); if ((null != start) && (null != final)) { float latitude = final.Latitude - start.Latitude; float longitude = final.Longitude - start.Longitude; Scene.RotateCamera(longitude, latitude); } else { // can't track cursor on globe, fallback to default handling base.RotateSceneUsingMouse(e); } } #region fields /// /// What mode are we in? /// public ScreenState State { get { return state; } set { state = value; } } /// /// Return the scene field as it's real type (a GeoscapeScene) /// private GeoscapeScene geoscapeScene { get { return (GeoscapeScene)Scene; } } /// /// What mode are we in? /// private ScreenState state; /// /// Remember where camera has been, to prevent Geoscape jumping around /// private static Vector3 oldCameraPosition = new Vector3(0.0f, 0.0f, 3.5f); #endregion fields } }