/* * MainForm.cs * Authors: Mike DeMauro, Adam Nabinger * Copyright (c) 2007-2008 Cornell University This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Reflection; using System.Windows.Forms; using StarJackWorldEditor.Engine; using StarJackWorldEditor.Forms; using System.IO; using System.Drawing.Imaging; namespace StarJackWorldEditor { internal struct TileTag { internal int x; internal int y; internal GameObject go; } internal partial class MainForm : Form { private Label[,] mTiles; private Level mLevel; private readonly Dictionary textures; private readonly string executingDirectory; private enum Modes { Select, Floor, Wall, Start, Terminal, Console, Door, Guard, Camera, End, Link, Delete, AI } private Modes mode = Modes.Select; private Label selectedLabel; private string currentFilePath; Process starJack; private delegate void ProcessExitCallback(); private bool processRunning; /// /// Creates a new instance of MainForm. /// internal MainForm() { InitializeComponent(); propertyGrid1.PropertyValueChanged += propertyGrid1_PropertyValueChanged; // Create tile controls CreateTiles(); Assembly a = Assembly.GetExecutingAssembly(); executingDirectory = a.Location.TrimEnd(a.ManifestModule.Name.ToCharArray()); // Load default tile and game object images textures = new Dictionary(); LoadTextures(); newWorld(); initStarJackProcess(); } protected override void OnClosed(EventArgs e) { if(processRunning) starJack.Kill(); base.OnClosed(e); } private void newWorld() { resetUI(); currentFilePath = string.Empty; mLevel = new Level(); LoadLevel(); } private void resetUI() { roomComboBox.Items.Clear(); roomComboBox.Text = string.Empty; propertyGrid1.SelectedObject = null; resetTiles(); } private void resetTiles() { foreach (Label l in mTiles) { l.Image = null; l.BackgroundImage = null; TileTag tag = new TileTag(); tag.x = ((TileTag)l.Tag).x; tag.y = ((TileTag)l.Tag).y; l.Tag = tag; } } /// /// Occurs when a property of the selected GameObject is changed. Repopulates every game object. /// NOTE: should optimize to simply update the selected GameObject. /// /// /// void propertyGrid1_PropertyValueChanged(object s, PropertyValueChangedEventArgs e) { Room room = roomComboBox.SelectedItem as Room; object selectedObj = propertyGrid1.SelectedObject; if (room != null) { if (selectedObj is Room) { // Refresh combobox data roomComboBox.Items.Clear(); roomComboBox.Items.AddRange(mLevel.Rooms.ToArray()); roomComboBox.SelectedItem = room; } else if (selectedObj is GameObject) { // Enforce unique GameObject names GameObject gameObj = selectedObj as GameObject; if (e.ChangedItem.PropertyDescriptor.DisplayName == "ObjectName") { string newValue = e.ChangedItem.Value as string; if (room.GetNumByName(newValue) > 1) { gameObj.ObjectName = e.OldValue as string; MessageBox.Show("'" + newValue + "' already exists."); } } } PopulateGameObjects(room); } } private bool IsValidImageFormat(string extension) { string extLower = extension.ToLower(); if (extLower == ImageFormat.Bmp.ToString().ToLower()) return true; if (extLower == ImageFormat.Emf.ToString().ToLower()) return true; if (extLower == ImageFormat.Exif.ToString().ToLower()) return true; if (extLower == ImageFormat.Gif.ToString().ToLower()) return true; if (extLower == ImageFormat.Icon.ToString().ToLower()) return true; if (extLower == ImageFormat.Jpeg.ToString().ToLower()) return true; if (extLower == ImageFormat.MemoryBmp.ToString().ToLower()) return true; if (extLower == ImageFormat.Png.ToString().ToLower()) return true; if (extLower == ImageFormat.Tiff.ToString().ToLower()) return true; if (extLower == ImageFormat.Wmf.ToString().ToLower()) return true; return false; } private void LoadTextures() { // Load each Bitmap image string texDir = executingDirectory + "..\\..\\..\\..\\StarJack\\Content\\Texture\\"; if (!Directory.Exists(texDir)) throw new DirectoryNotFoundException("Directory does not exist: " + texDir); string[] files = Directory.GetFiles(texDir, "*", SearchOption.AllDirectories); foreach (string filePath in files) { int extIndex = filePath.LastIndexOf('.') + 1; string fileExt = filePath.Substring(extIndex); if (IsValidImageFormat(fileExt)) { string fileName = filePath.Remove(extIndex - 1).Remove(0, texDir.Length); if(textures.ContainsKey(fileName)) Console.WriteLine("Duplicate texture name found: " + fileName); textures[fileName] = new Bitmap(filePath); } } // Resize each Bitmap image Size size = new Size(xnaPanel.Width / Level.TILES_PER_DIMENSION, xnaPanel.Height / Level.TILES_PER_DIMENSION); String[] keys = new string[textures.Keys.Count]; textures.Keys.CopyTo(keys, 0); foreach(string s in keys) { textures[s] = new Bitmap(textures[s], size); } } /// /// Creates tile controls /// private void CreateTiles() { mTiles = new Label[Level.TILES_PER_DIMENSION, Level.TILES_PER_DIMENSION]; int tileWidth = xnaPanel.Width / Level.TILES_PER_DIMENSION; int tileHeight = xnaPanel.Height / Level.TILES_PER_DIMENSION; for (int i = 0; i < Level.TILES_PER_DIMENSION; ++i) { for (int j = 0; j < Level.TILES_PER_DIMENSION; ++j) { Label label = new Label(); label.Location = new Point(i * tileWidth, j * tileHeight); label.Size = new Size(tileWidth, tileHeight); label.MinimumSize = label.Size; label.AutoSize = true; label.ForeColor = Color.Red; mTiles[i, j] = label; label.MouseDown += new MouseEventHandler(tile_MouseDown); label.MouseEnter += new EventHandler(tile_MouseEnter); TileTag tag = new TileTag(); tag.x = i; tag.y = j; label.Tag = tag; xnaPanel.Controls.Add(label); } } } private void LoadLevel() { PopulateRooms(); LoadFirstRoom(); difficultyStepper.Value = mLevel.Difficulty; this.Text = "Starjack Level Editor - " + mLevel.Name; } /// /// Populates Level rooms /// internal void PopulateRooms() { roomComboBox.Items.Clear(); roomComboBox.SelectedItem = null; roomComboBox.Items.AddRange(mLevel.Rooms.ToArray()); } private void LoadFirstRoom() { if (roomComboBox.Items.Count > 0) { roomComboBox.SelectedIndex = 0; LoadSelectedRoom(); } } private void roomComboBox_SelectedIndexChanged(object sender, EventArgs e) { LoadSelectedRoom(); } private void LoadSelectedRoom() { Room room = roomComboBox.SelectedItem as Room; if (room != null) { selectButton.PerformClick(); propertyGrid1.SelectedObject = null; PopulateTiles(room); PopulateGameObjects(room); } } /// /// Sets the tile label background images using the room's Tile list /// /// private void PopulateTiles(Room room) { for (int i = 0; i < Level.TILES_PER_DIMENSION; ++i) { for (int j = 0; j < Level.TILES_PER_DIMENSION; ++j) { Tile t = room.Tiles[(i * Level.TILES_PER_DIMENSION) + j]; if (t != null) { if (t.Collision) { if (t.Collision) { mTiles[i, j].BackgroundImage = textures["Wall\\Ciel"]; } else { mTiles[i, j].BackgroundImage = textures["Wall\\Wall"]; } } else { mTiles[i, j].BackgroundImage = textures["Wall\\Floor"]; } } } } } /// /// Sets the tile label images using the room's GameObject list /// /// private void PopulateGameObjects(Room room) { if (room != null) { // clear tiles first foreach (Label l in mTiles) { l.Image = null; TileTag tag = new TileTag(); tag.x = ((TileTag)l.Tag).x; tag.y = ((TileTag)l.Tag).y; l.Tag = tag; } // set objects foreach (GameObject o in room.GameObjects) { mTiles[o.X, o.Y].Image = o.GetTexture(textures); TileTag t = (TileTag)mTiles[o.X, o.Y].Tag; t.go = o; mTiles[o.X, o.Y].Tag = t; } } } private void handleTileChange(Label label) { if (label != null) { Room room = roomComboBox.SelectedItem as Room; if (room != null) { TileTag tag = (TileTag)label.Tag; switch(mode) { case Modes.Select: propertyGrid1.SelectedObject = tag.go; if (selectedLabel != null) { selectedLabel.BorderStyle = BorderStyle.None; } selectedLabel = label; selectedLabel.BorderStyle = BorderStyle.Fixed3D; // Allow linking if the object is a LinkedObject linkButton.Enabled = tag.go is LinkedObject; aiButton.Enabled = tag.go is Enemy; label.Capture = false; break; case Modes.Floor: room.Tiles[(tag.x * Level.TILES_PER_DIMENSION) + tag.y].Collision = false; PopulateTiles(room); label.Capture = false; break; case Modes.Wall: room.Tiles[(tag.x * Level.TILES_PER_DIMENSION) + tag.y].Collision = true; PopulateTiles(room); label.Capture = false; break; case Modes.Start: GameObject startObj = new GameObject("Start", true); startObj.Type = StarJack.ObjectTypes.StartPosition; startObj.X = tag.x; startObj.Y = tag.y; startObj.ActiveTextureName = "Start"; startObj.ObjectName = "Start"; room.Add(startObj); PopulateGameObjects(room); break; case Modes.Terminal: HackableObject terminal = new HackableObject(); terminal.ObjectName = "Terminal"; terminal.Type = StarJack.ObjectTypes.HackableObject; terminal.TextureGroup = "Terminal"; terminal.X = tag.x; terminal.Y = tag.y; terminal.Collision = true; room.Add(terminal); PopulateGameObjects(room); break; case Modes.Console: HackableObject console = new SallyAnneObject(); console.ObjectName = "Console"; console.Type = StarJack.ObjectTypes.SallyAnne; console.TextureGroup = "Console"; console.X = tag.x; console.Y = tag.y; console.Collision = true; room.Add(console); PopulateGameObjects(room); break; case Modes.Door: LinkedDoor door = new LinkedDoor(); door.ObjectName = "Door"; door.Type = StarJack.ObjectTypes.LinkedDoor; door.TextureGroup = "Door"; door.X = tag.x; door.Y = tag.y; door.Collision = true; room.Add(door); PopulateGameObjects(room); break; case Modes.Guard: Enemy enemy = new Enemy(); enemy.ObjectName = "Guard"; enemy.Type = StarJack.ObjectTypes.Enemy; enemy.ActiveTextureName = "Guard"; enemy.TextureGroup = "Guard"; enemy.X = tag.x; enemy.Y = tag.y; enemy.Collision = true; room.Add(enemy); PopulateGameObjects(room); break; case Modes.Camera: LinkedCamera camera = new LinkedCamera(); camera.ObjectName = "Camera"; camera.Type = StarJack.ObjectTypes.LinkedCamera; camera.TextureGroup = "Camera"; camera.X = tag.x; camera.Y = tag.y; camera.Collision = true; room.Add(camera); PopulateGameObjects(room); break; case Modes.End: LinkedCamera end = new LinkedCamera(); end.ObjectName = "End"; end.Type = StarJack.ObjectTypes.Teleporter; end.TextureGroup = "LevelEnd"; end.X = tag.x; end.Y = tag.y; room.Add(end); PopulateGameObjects(room); break; case Modes.Link: LinkedObject linkedObject = propertyGrid1.SelectedObject as LinkedObject; if (linkedObject != null && tag.go != null) { // only allow a LinkedObject that is not a HackableObject (Terminal) // to link to another HackableObject if (!(tag.go is HackableObject) && linkedObject is HackableObject) { LinkedObject linkedObjectTag = tag.go as LinkedObject; linkedObjectTag.Linked = linkedObject; } else if (!(linkedObject is HackableObject) && tag.go is HackableObject) { linkedObject.Linked = tag.go; } } break; case Modes.Delete: if (tag.go != null) { room.Remove(tag.go); PopulateGameObjects(room); } break; case Modes.AI: enemy = propertyGrid1.SelectedObject as Enemy; if (enemy != null) { AINode node = new AINode(); FacingDialog fd = new FacingDialog(); if (fd.ShowDialog() == DialogResult.OK) { node.NodeFacing = fd.getFacing(); } node.X = tag.x; node.Y = tag.y; enemy.AINodes.Add(node); UpdateOverlayTiles(); } break; } propertyGrid1.Refresh(); } } } private void UpdateOverlayTiles() { foreach (Label l in mTiles) { l.BorderStyle = BorderStyle.None; l.Text = string.Empty; } if (mode == Modes.AI) { Enemy enemy = propertyGrid1.SelectedObject as Enemy; if (enemy != null) { int i = 0; foreach (AINode node in enemy.AINodes) { Label l = this.mTiles[node.X, node.Y]; if (string.IsNullOrEmpty(l.Text)) { l.Text = node.X + "," + node.Y + "\n" + i; } else { l.Text += "," + i; } ++i; } } } if (selectedLabel != null) { selectedLabel.BorderStyle = BorderStyle.Fixed3D; } } #region Event Handlers private void difficulty_ValueChanged(object sender, EventArgs e) { mLevel.Difficulty = (int)difficultyStepper.Value; } private void addRoomBtn_Click(object sender, EventArgs e) { InputDialog id = new InputDialog(); if (id.ShowDialog() == DialogResult.OK) { bool exists = false; foreach (Room room in mLevel.Rooms) { if (room.Name == id.InputValue) { exists = true; break; } } if (!exists) { Room room = new Room(id.InputValue); mLevel.Rooms.Add(room); PopulateRooms(); roomComboBox.SelectedItem = room; LoadSelectedRoom(); } } } private void editRoomButton_Click(object sender, EventArgs e) { Room room = roomComboBox.SelectedItem as Room; if (room != null) { propertyGrid1.SelectedObject = room; } } private void removeRoomBtn_Click(object sender, EventArgs e) { Room room = roomComboBox.SelectedItem as Room; if (room != null) { ConfirmDialog cd = new ConfirmDialog("Remove Room", "Are you sure you want to remove room '" + room.Name + "'?"); if (cd.ShowDialog() == DialogResult.Yes) { mLevel.Rooms.Remove(room); PopulateRooms(); LoadFirstRoom(); } } } private void tile_MouseEnter(object sender, EventArgs e) { if (MouseButtons == MouseButtons.Left) { if (mode == Modes.Floor || mode == Modes.Wall || mode == Modes.Select) { Label label = sender as Label; if (label != null) handleTileChange(label); } } } private void tile_MouseDown(object sender, MouseEventArgs e) { Label label = sender as Label; if (label != null) handleTileChange(label); } #endregion #region Menu Handlers private void newToolStripMenuItem_Click(object sender, EventArgs e) { newWorld(); } private void openToolStripMenuItem_Click(object sender, EventArgs e) { using (OpenFileDialog ofd = new OpenFileDialog()) { ofd.Filter = "XML Files|*.xml"; ofd.Title = "Select an XML File"; ofd.Multiselect = false; ofd.CheckFileExists = true; if (ofd.ShowDialog() == DialogResult.OK) { currentFilePath = ofd.FileName; mLevel = Level.Load(ofd.FileName); LoadLevel(); } } } private void saveToolStripMenuItem_Click(object sender, EventArgs e) { saveFile(); } private void saveFile() { if (System.IO.File.Exists(currentFilePath)) { mLevel.Save(currentFilePath); } else { saveDialog(); } } private void saveAsToolStripMenuItem_Click(object sender, EventArgs e) { saveDialog(); } private void saveDialog() { SaveFileDialog sfd = new SaveFileDialog(); sfd.DefaultExt = ".xml"; DialogResult result = sfd.ShowDialog(); if (result == DialogResult.OK) { mLevel.Save(sfd.FileName); currentFilePath = sfd.FileName; this.Text = "Starjack Level Editor - " + mLevel.Name; } } private void exitToolStripMenuItem_Click(object sender, EventArgs e) { Close(); } private void playToolStripMenuItem_Click(object sender, EventArgs e) { if(!processRunning) { saveFile(); starJack.StartInfo.Arguments = "\"" + currentFilePath + "\""; try { starJack.Start(); starJack.BeginErrorReadLine(); starJack.BeginOutputReadLine(); playToolStripMenuItem.Text = "Stop"; processRunning = true; } catch (Exception ex) { MessageBox.Show(ex.ToString(), string.Empty, MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly, false); } } else { //starJack.Close(); starJack.Kill(); } } #endregion #region RadioButton Handlers private void SetMode(Modes newMode) { mode = newMode; UpdateOverlayTiles(); } private void wallButton_CheckedChanged(object sender, EventArgs e) { SetMode(Modes.Wall); } private void floorButton_CheckedChanged(object sender, EventArgs e) { SetMode(Modes.Floor); } private void startObjectButton_CheckedChanged(object sender, EventArgs e) { SetMode(Modes.Start); } private void selectButton_CheckedChanged(object sender, EventArgs e) { SetMode(Modes.Select); } private void terminalButton_CheckedChanged(object sender, EventArgs e) { SetMode(Modes.Terminal); } private void consoleButton_CheckedChanged(object sender, EventArgs e) { SetMode(Modes.Console); } private void doorButton_CheckedChanged(object sender, EventArgs e) { SetMode(Modes.Door); } private void guardButton_CheckedChanged(object sender, EventArgs e) { SetMode(Modes.Guard); } private void cameraButton_CheckedChanged(object sender, EventArgs e) { SetMode(Modes.Camera); } private void endButton_CheckedChanged(object sender, EventArgs e) { SetMode(Modes.End); } private void linkButton_CheckedChanged(object sender, EventArgs e) { SetMode(Modes.Link); } private void deleteButton_CheckedChanged(object sender, EventArgs e) { SetMode(Modes.Delete); } private void aiButton_CheckedChanged(object sender, EventArgs e) { SetMode(Modes.AI); } #endregion #region Process Management private void processOutputHandler(object src, DataReceivedEventArgs args) { Console.WriteLine(args.Data); } private void initStarJackProcess() { starJack = new Process(); starJack.StartInfo.FileName = executingDirectory + "StarJack.exe"; starJack.StartInfo.UseShellExecute = false; DataReceivedEventHandler dreh = new DataReceivedEventHandler(processOutputHandler); starJack.StartInfo.RedirectStandardError = true; starJack.ErrorDataReceived += dreh; starJack.StartInfo.RedirectStandardOutput = true; starJack.OutputDataReceived += dreh; starJack.EnableRaisingEvents = true; starJack.Exited += delegate { this.Invoke(new ProcessExitCallback(processExited)); }; } private void processExited() { starJack.CancelErrorRead(); starJack.CancelOutputRead(); playToolStripMenuItem.Text = "Play"; processRunning = false; } #endregion } }