hostile-takeover/m/LevelDoc.cs
2014-07-06 17:47:28 -07:00

2050 lines
58 KiB
C#

using System;
using System.Collections;
using System.Collections.Specialized;
using System.IO;
using System.Windows.Forms;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization.Formatters.Soap;
using System.ComponentModel;
using SpiffLib;
using System.Drawing;
using System.Drawing.Imaging;
using System.Text;
using System.Text.RegularExpressions;
namespace m {
public enum LayerType { Start = 0, TileMap = 0, Galaxite, SurfaceDecal, Scenery, DepthSorted, SmokeFire, Area, Selection, End };
[Flags]
public enum LayerFlags { Templates = 1, Gobs = 2, Areas = 4, Default = (Templates | Gobs | Areas) };
[Serializable]
public class LevelDoc : Document, ISerializable, IDeserializationCallback {
string m_strOutputFilename = null;
bool m_fSwitchTemplatesEnabled = true;
IMapItem[] m_amiDeserializationTemp;
SideInfo[] m_asidiDeserializationTemp;
TemplateDoc m_tmpd = null;
string m_strTitle = null;
Rectangle m_trcBounds = new Rectangle(1, 1, 62, 62);
int m_nPlayersMin = 1;
int m_nPlayersMax = 1;
ArrayList m_alsmi = new ArrayList();
ArrayList m_alsidi = new ArrayList();
bool m_fUpdateDirty = false;
static bool s_fLoading = false;
int m_ctx = 64, m_cty = 64;
TriggerManager m_tgrm = new TriggerManager();
UnitGroupManager m_ugm = new UnitGroupManager();
SwitchManager m_swm = new SwitchManager();
CounterManager m_ctrm = new CounterManager();
string m_strComment;
int[] m_mpDirToDx = new int[] { 0, 1, 1, 1, 0, -1, -1, -1 };
int[] m_mpDirToDy = new int[] { -1, -1, 0, 1, 1, 1, 0, -1 };
ArrayList m_alsmiSelected = new ArrayList();
public delegate void ImageChangedHandler();
public event ImageChangedHandler ImageChanged;
public delegate void ItemsRemovedHandler(IMapItem[] ami);
public event ItemsRemovedHandler ItemsRemoved;
public delegate void NameChangedHandler(LevelDoc lvld);
public event NameChangedHandler NameChanged;
public LevelDoc(DocTemplate doct, string strFile, Object aobj) : base(doct, strFile) {
m_strTitle = m_doct.GetString(DocTemplate.Strings.NewFileName);
SetTemplateDoc((TemplateDoc)DocManager.GetActiveDocument(typeof(TemplateDoc)));
InitCommon();
}
// ISerializable implemented to prevent events from being serialized
public LevelDoc(SerializationInfo info, StreamingContext ctx) : base((DocTemplate)(((Hashtable)ctx.Context)["DocTemplate"]), (string)(((Hashtable)ctx.Context)["Filename"])) {
s_fLoading = true;
CaTypeSwitch.InitFixupList();
Title = info.GetString("Title");
m_trcBounds.X = info.GetInt32("BoundsX");
m_trcBounds.Y = info.GetInt32("BoundsY");
m_trcBounds.Width = info.GetInt32("BoundsWidth");
m_trcBounds.Height = info.GetInt32("BoundsHeight");
try {
// These were introduced at the same time so either they're both present or they both aren't
m_ctx = info.GetInt32("Width");
m_cty = info.GetInt32("Height");
} catch (SerializationException) {
}
// Default values in case they aren't present
m_nPlayersMin = 1;
m_nPlayersMax = 1;
try {
// These were introduced at the same time so either they're both present or they both aren't
m_nPlayersMin = info.GetInt32("MinPlayers");
m_nPlayersMax = info.GetInt32("MaxPlayers");
} catch (SerializationException) {
}
// Side info
try {
m_asidiDeserializationTemp = (SideInfo[])info.GetValue("SideInfos", typeof(SideInfo[]));
} catch (SerializationException) {
}
// TriggerManager
try {
m_tgrm = (TriggerManager)info.GetValue("TriggerManager", typeof(TriggerManager));
} catch (SerializationException) {
m_tgrm = new TriggerManager();
}
// UnitGroupManager
try {
m_ugm = (UnitGroupManager)info.GetValue("UnitGroupManager", typeof(UnitGroupManager));
} catch (SerializationException) {
m_ugm = new UnitGroupManager();
}
// SwitchManager
try {
m_swm = (SwitchManager)info.GetValue("SwitchManager", typeof(SwitchManager));
} catch (SerializationException) {
m_swm = new SwitchManager();
}
// CounterManager
try {
m_ctrm = (CounterManager)info.GetValue("CounterManager", typeof(CounterManager));
} catch (SerializationException) {
m_ctrm = new CounterManager();
}
// Comment string
try {
m_strComment = info.GetString("Comment");
} catch (SerializationException) {
}
// Support old .ld's that have separate .map files
string strTemplatesFileName = null;
try {
string strFile = info.GetString("MapFileName");
if (strFile != null) {
Stream stm = null;
try {
IFormatter fmtr = new BinaryFormatter();
stm = new FileStream(m_strDir + Path.DirectorySeparatorChar + strFile, FileMode.Open, FileAccess.Read, FileShare.Read);
strTemplatesFileName = (string)fmtr.Deserialize(stm);
SetTemplateDoc((TemplateDoc)DocManager.OpenDocument(m_strDir + Path.DirectorySeparatorChar + strTemplatesFileName));
m_alsmi.AddRange((IMapItem[])fmtr.Deserialize(stm));
stm.Close();
} catch {
if (stm != null)
stm.Close();
}
}
} catch(SerializationException) {
}
// Load the desired template set
if (m_tmpd == null) {
try {
strTemplatesFileName = info.GetString("TemplatesFileName");
SetTemplateDoc((TemplateDoc)DocManager.OpenDocument(m_strDir + Path.DirectorySeparatorChar + strTemplatesFileName));
} catch (SerializationException) {
// If couldn't load it, use the active one
SetTemplateDoc(null);
string strT = "Couldn't load " + strTemplatesFileName;
if (m_tmpd != null)
strT += " - Using " + m_tmpd.GetName();
MessageBox.Show(strT);
}
}
// Load the map items
m_amiDeserializationTemp = (IMapItem[])info.GetValue("MapItems", typeof(IMapItem[]));
}
public void OnDeserialization(object obSender) {
// NEW FOR .NET 1.1
// Couldn't do this during the deserialization constructor any more because the
// result is null references in the ArrayList.
m_alsidi.AddRange(m_asidiDeserializationTemp);
m_asidiDeserializationTemp = null;
// NEW FOR .NET 1.1
// Couldn't do this during the deserialization constructor any more because the
// result is null references in the ArrayList.
m_alsmi.AddRange(m_amiDeserializationTemp);
// Set events
AddEventHandlers(m_amiDeserializationTemp);
m_amiDeserializationTemp = null;
// Incorporate old-style switches into the SwitchManager
CaTypeSwitch.Fixup(this);
// Common initialization
InitCommon();
}
public void GetObjectData(SerializationInfo info, StreamingContext context) {
info.AddValue("Title", Title);
info.AddValue("MapItems", (IMapItem[])m_alsmi.ToArray(typeof(IMapItem)));
info.AddValue("BoundsX", Bounds.X);
info.AddValue("BoundsY", Bounds.Y);
info.AddValue("BoundsWidth", Bounds.Width);
info.AddValue("BoundsHeight", Bounds.Height);
info.AddValue("MinPlayers", MinPlayers);
info.AddValue("MaxPlayers", MaxPlayers);
info.AddValue("SideInfos", GetSideInfos());
info.AddValue("Width", Width);
info.AddValue("Height", Height);
info.AddValue("TriggerManager", m_tgrm);
info.AddValue("UnitGroupManager", m_ugm);
info.AddValue("SwitchManager", m_swm);
info.AddValue("CounterManager", m_ctrm);
info.AddValue("Comment", m_strComment);
if (m_tmpd == null) {
info.AddValue("TemplatesFileName", "");
} else {
info.AddValue("TemplatesFileName", Path.GetFileName(m_tmpd.GetPath()));
}
}
void InitCommon() {
// We want to know when the application goes idle for batching MapItem notifications
// into single doc notifications
Application.Idle += new EventHandler(Application_IdleHandler);
// We want to know when the active template doc changes
TemplateDocTemplate doct = (TemplateDocTemplate)DocManager.FindDocTemplate(typeof(TemplateDoc));
doct.DocActive += new DocTemplate.DocActiveHandler(TemplateDocTemplate_DocActiveHandler);
doct.TemplatesRemoved += new TemplateDocTemplate.TemplatesRemovedHandler(TemplateDocTemplate_TemplatesRemovedHandler);
doct.TemplatesAdded += new TemplateDocTemplate.TemplatesAddedHandler(TemplateDocTemplate_TemplatesAddedHandler);
SetModified(false);
}
public void Dispose() {
m_alsmi.Clear();
}
void Application_IdleHandler(object sender, EventArgs e) {
s_fLoading = false;
if (!m_fUpdateDirty)
return;
m_fUpdateDirty = false;
if (ImageChanged != null)
ImageChanged();
}
public void Refresh() {
m_fUpdateDirty = true;
}
void TemplateDocTemplate_DocActiveHandler(Document doc) {
// TemplateDocs get loaded and activated during LevelDoc
// deserialization. We don't want to change the template doc
// of the currently active level doc in this case
if (!s_fLoading && m_fSwitchTemplatesEnabled) {
if (DocManager.GetActiveDocument(typeof(LevelDoc)) == this)
SetTemplateDoc((TemplateDoc)doc);
}
}
public bool SwitchTemplatesEnabled {
get {
return m_fSwitchTemplatesEnabled;
}
set {
m_fSwitchTemplatesEnabled = value;
}
}
public string OutputFilename {
get {
return m_strOutputFilename;
}
set {
m_strOutputFilename = value;
}
}
void SetTemplateDoc(TemplateDoc tmpd) {
if (m_tmpd == tmpd)
return;
// Active template doc change. Remove event handlers from old one, add event handlers to new
if (m_tmpd != null)
m_tmpd.BackgroundChanged -= new TemplateDoc.BackgroundChangedHandler(TemplateDoc_BackgroundChangedHandler);
// Assign new. If null, use current active
m_tmpd = tmpd;
if (m_tmpd == null)
m_tmpd = (TemplateDoc)DocManager.GetActiveDocument(typeof(TemplateDoc));
// Put in hooks
if (m_tmpd != null)
m_tmpd.BackgroundChanged += new TemplateDoc.BackgroundChangedHandler(TemplateDoc_BackgroundChangedHandler);
// Raise changed event
m_fUpdateDirty = true;
// Doc modified
SetModified(true);
}
public override bool Save() {
// Save the template doc when we save the level doc. For convenience
if (base.Save()) {
if (m_tmpd != null && m_tmpd.IsModified())
m_tmpd.Save();
return true;
}
return false;
}
public override bool SaveAs(string strFile) {
// Save the template doc when we save the level doc. For convenience
if (base.SaveAs(strFile)) {
if (m_tmpd != null)
m_tmpd.Save();
return true;
}
return false;
}
public TemplateDoc GetTemplateDoc() {
return m_tmpd;
}
public override string GetName() {
return m_strTitle;
}
private SideInfo[] GetSideInfos() {
return (SideInfo[])m_alsidi.ToArray(typeof(SideInfo));
}
public SideInfo GetSideInfo(Side side) {
foreach (SideInfo sidi in m_alsidi) {
if (sidi.Side == side)
return sidi;
}
SideInfo sidiNew = new SideInfo(side);
m_alsidi.Add(sidiNew);
SetModified(true);
return sidiNew;
}
public void Draw(Bitmap bm, IMapItem miExclude, Size sizTile, TemplateDoc tmpd, LayerFlags lyrf) {
ArrayList alsmiSelected = m_alsmiSelected;
// Draw tile map
DrawTileMap(bm, alsmiSelected, sizTile, tmpd, lyrf);
// Draw other layers
using (Graphics g = Graphics.FromImage(bm)) {
for (LayerType layer = LayerType.Galaxite; layer < LayerType.End; layer++) {
if (layer == LayerType.Area) {
if ((lyrf & LayerFlags.Areas) == 0)
continue;
} else {
if ((lyrf & LayerFlags.Gobs) == 0)
continue;
}
foreach (IMapItem mi in m_alsmi) {
if (mi != miExclude) {
int x = (int)(mi.tx * sizTile.Width);
int y = (int)(mi.ty * sizTile.Height);
mi.Draw(g, x, y, sizTile, tmpd, layer, alsmiSelected != null ? alsmiSelected.Contains(mi) : false);
}
}
}
// Draw bounds
Pen pen = new Pen(new SolidBrush(Color.FromArgb(0, 255, 0)));
pen.Width = 2;
g.DrawRectangle(pen, Bounds.X * sizTile.Width - pen.Width + 1, Bounds.Y * sizTile.Height - pen.Width + 1, Bounds.Width * sizTile.Width + pen.Width, Bounds.Height * sizTile.Height + pen.Width);
}
}
public void DrawTileMap(Bitmap bm, ArrayList alsmiSelected, Size sizTile, TemplateDoc tmpd, LayerFlags lyrf) {
// Draw background
using (Graphics g = Graphics.FromImage(bm)) {
Template tmplBackground = null;
if (tmpd != null)
tmplBackground = tmpd.GetBackgroundTemplate();
if (tmplBackground == null) {
g.FillRectangle(new SolidBrush(Color.Black), 0, 0, bm.Width, bm.Height);
for (int x = sizTile.Width; x < bm.Width; x += sizTile.Width) {
for (int y = sizTile.Height; y < bm.Height; y += sizTile.Height) {
bm.SetPixel(x, y, Color.BlanchedAlmond);
}
}
} else {
for (int x = 0; x < bm.Width; x += tmplBackground.Bitmap.Width) {
for (int y = 0; y < bm.Height; y += tmplBackground.Bitmap.Height)
g.DrawImage(tmplBackground.Bitmap, x, y);
}
}
// Draw templates
if ((lyrf & LayerFlags.Templates) != 0) {
foreach (IMapItem mi in m_alsmi) {
int x = (int)mi.tx * sizTile.Width;
int y = (int)mi.ty * sizTile.Height;
mi.Draw(g, x, y, sizTile, tmpd, LayerType.TileMap, alsmiSelected != null ? alsmiSelected.Contains(mi) : false);
}
}
}
}
public void AddMapItems(IMapItem[] ami) {
m_alsmi.AddRange(ami);
AddEventHandlers(ami);
m_fUpdateDirty = true;
SetModified(true);
}
public void RemoveMapItems(IMapItem[] ami) {
for (int n = 0; n < ami.Length; n++)
m_alsmi.Remove(ami[n]);
RemoveEventHandlers(ami);
if (ItemsRemoved != null)
ItemsRemoved(ami);
m_fUpdateDirty = true;
SetModified(true);
}
void AddEventHandlers(IMapItem[] ami) {
foreach (IMapItem mi in ami)
mi.PropertyChanged += new PropertyChangedHandler(IMapItem_PropertyChanged);
}
void RemoveEventHandlers(IMapItem[] ami) {
foreach (IMapItem mi in ami)
mi.PropertyChanged -= new PropertyChangedHandler(IMapItem_PropertyChanged);
}
void IMapItem_PropertyChanged(IMapItem mi, string strProperty) {
SetModified(true);
m_fUpdateDirty = true;
}
void TemplateDoc_BackgroundChangedHandler(TemplateDoc tmpd) {
m_fUpdateDirty = true;
}
private void TemplateDocTemplate_TemplatesAddedHandler(TemplateDoc tmpd, string[] astrNames) {
TemplatesAddedRemoved(astrNames);
}
private void TemplateDocTemplate_TemplatesRemovedHandler(TemplateDoc tmpd, string[] astrNames) {
TemplatesAddedRemoved(astrNames);
}
void TemplatesAddedRemoved(string[] astrNames) {
foreach (string strName in astrNames) {
for (int n = 0; n < m_alsmi.Count; n++) {
Tile tile = m_alsmi[n] as Tile;
if (tile != null && tile.Name == strName) {
m_fUpdateDirty = true;
SetModified(true);
return;
}
}
}
}
public IMapItem HitTest(int x, int y, Size sizTile, TemplateDoc tmpd, LayerFlags lyrf) {
// MapItems on top are first
for (int i = m_alsmi.Count - 1; i >= 0; i--) {
IMapItem mi = (IMapItem)m_alsmi[i];
if (mi.HitTest(x, y, sizTile, tmpd)) {
if (mi is Area) {
if ((lyrf & LayerFlags.Areas) != 0)
return mi;
} else if (mi is Tile) {
if ((lyrf & LayerFlags.Templates) != 0)
return mi;
} else {
if ((lyrf & LayerFlags.Gobs) != 0)
return mi;
}
}
}
return null;
}
public ArrayList HitTest(Rectangle rc, Size sizTile, TemplateDoc tmpd, LayerFlags lyrf) {
ArrayList alsmi = new ArrayList();
foreach (IMapItem mi in m_alsmi) {
Point ptCenter = mi.GetCenterPoint(sizTile);
if (rc.Contains(ptCenter)) {
if (mi is Area) {
if ((lyrf & LayerFlags.Areas) != 0)
alsmi.Add(mi);
} else if (mi is Tile) {
if ((lyrf & LayerFlags.Templates) != 0)
alsmi.Add(mi);
} else {
if ((lyrf & LayerFlags.Gobs) != 0)
alsmi.Add(mi);
}
}
}
return alsmi;
}
public Bitmap GetMapBitmap(Size sizTile, TemplateDoc tmpd, bool fTilesOnly) {
Bitmap bm = new Bitmap(Width * sizTile.Width, Height * sizTile.Height, PixelFormat.Format24bppRgb);
DrawTileMap(bm, null, sizTile, tmpd, LayerFlags.Default);
if (!fTilesOnly) {
using (Graphics g = Graphics.FromImage(bm)) {
for (LayerType layer = LayerType.Galaxite; layer < LayerType.End; layer++) {
foreach (IMapItem mi in m_alsmi) {
int x = (int)(mi.tx * sizTile.Width);
int y = (int)(mi.ty * sizTile.Height);
mi.Draw(g, x, y, sizTile, tmpd, layer, false);
}
}
}
}
Bitmap bmT = new Bitmap(Bounds.Width * sizTile.Width, Bounds.Height * sizTile.Height, PixelFormat.Format24bppRgb);
using (Graphics g = Graphics.FromImage(bmT)) {
Rectangle rcSrc = new Rectangle(Bounds.X * sizTile.Width, Bounds.Y * sizTile.Height, bmT.Width, bmT.Height);
g.DrawImage(bm, 0, 0, rcSrc, GraphicsUnit.Pixel);
}
bm.Dispose();
return bmT;
}
public TerrainTypes[,] GetTerrainMap(Size sizTile, TemplateDoc tmpd, bool fStructures) {
// Get raw terrain map
TerrainTypes[,] aterMap = GetRawTerrainMap(sizTile, tmpd);
// Flood fill to mark areas. The "biggest" area is accessible.
// All the smaller areas are not; mark them as such.
int[,] anFill = new int[aterMap.GetLength(0), aterMap.GetLength(1)];
// Mark all the areas in the fill map that are known to be not accessible.
for (int ty = 0; ty < aterMap.GetLength(0); ty++) {
for (int tx = 0; tx < aterMap.GetLength(1); tx++) {
if (aterMap[ty, tx] == TerrainTypes.Blocked) {
anFill[ty, tx] = -1;
}
}
}
// Mark structures if asked, before flood filling
// This is used for terrain diffing to see if structures make terrain inaccessible
if (fStructures) {
foreach (IMapItem mi in m_alsmi) {
if (mi is Structure) {
for (int ty = (int)mi.ty; ty < mi.ty + mi.cty; ty++) {
for (int tx = (int)mi.tx; tx < mi.tx + mi.ctx; tx++) {
if (Bounds.Contains(tx, ty)) {
int txT = tx - Bounds.Left;
int tyT = ty - Bounds.Top;
anFill[tyT, txT] = -1;
}
}
}
}
}
}
// Start flood filling areas
int nFillValue = 1;
ArrayList alsFillCounts = new ArrayList();
alsFillCounts.Add(0);
for (int ty = 0; ty < anFill.GetLength(0); ty++) {
for (int tx = 0; tx < anFill.GetLength(1); tx++) {
if (anFill[ty, tx] == 0) {
int cFill = FloodFill(anFill, tx, ty, nFillValue);
nFillValue++;
alsFillCounts.Add(cFill);
}
}
}
// Find the largest count; that is the accessible area
int nFillValueLargest = -1;
int cLargest = 0;
for (int n = 0; n < alsFillCounts.Count; n++) {
if ((int)alsFillCounts[n] > cLargest) {
cLargest = (int)alsFillCounts[n];
nFillValueLargest = n;
}
}
// Now mark the areas that aren't this fill value as inaccessible
for (int ty = 0; ty < anFill.GetLength(0); ty++) {
for (int tx = 0; tx < anFill.GetLength(1); tx++) {
if (anFill[ty, tx] != nFillValueLargest) {
aterMap[ty, tx] = TerrainTypes.Blocked;
}
}
}
// Mark where areas are in the terrain
foreach (IMapItem mi in m_alsmi) {
if (mi is Area) {
for (int ty = (int)mi.ty; ty < mi.ty + mi.cty; ty++) {
for (int tx = (int)mi.tx; tx < mi.tx + mi.ctx; tx++) {
if (Bounds.Contains(tx, ty)) {
int txT = tx - Bounds.Left;
int tyT = ty - Bounds.Top;
if (aterMap[tyT, txT] == TerrainTypes.Open)
aterMap[tyT, txT] = TerrainTypes.Area;
}
}
}
}
}
// Mark where walls are in the terrain
foreach (IMapItem mi in m_alsmi) {
if (mi is Wall) {
int tx = (int)mi.tx;
int ty = (int)mi.ty;
if (Bounds.Contains(tx, ty)) {
int txT = tx - Bounds.Left;
int tyT = ty - Bounds.Top;
aterMap[tyT, txT] = TerrainTypes.Wall;
}
}
}
// Done
return aterMap;
}
int FloodFill(int[,] anFill, int tx, int ty, int nFillValue) {
int nFillMatch = anFill[ty, tx];
if (nFillMatch == nFillValue)
return 0;
ArrayList alsPoints = new ArrayList();
anFill[ty, tx] = nFillValue;
alsPoints.Add(new Point(tx, ty));
int ipt = 0;
int cFill = 1;
while (true) {
bool fFound = false;
Point ptT = (Point)alsPoints[ipt];
for (int dir = 0; dir < 8; dir++) {
int txT = ptT.X + m_mpDirToDx[dir];
int tyT = ptT.Y + m_mpDirToDy[dir];
if (txT < 0 || txT >= anFill.GetLength(1))
continue;
if (tyT < 0 || tyT >= anFill.GetLength(0))
continue;
if (anFill[tyT, txT] == nFillMatch) {
cFill++;
anFill[tyT, txT] = nFillValue;
alsPoints.Add(new Point(txT, tyT));
ipt++;
fFound = true;
break;
}
}
if (!fFound) {
alsPoints.RemoveAt(ipt);
ipt--;
if (ipt < 0)
break;
}
}
return cFill;
}
TerrainTypes[,] GetRawTerrainMap(Size sizTile, TemplateDoc tmpd) {
TerrainTypes[,] aterMap = new TerrainTypes[Bounds.Height, Bounds.Width];
foreach (IMapItem mi in m_alsmi) {
if (mi is Tile) {
Tile tile = mi as Tile;
int x = (int)mi.tx * sizTile.Width;
int y = (int)mi.ty * sizTile.Height;
Rectangle rc = tile.GetBoundingRectAt(x, y, sizTile, m_tmpd);
rc.Width /= sizTile.Width;
rc.Height /= sizTile.Height;
rc.X /= sizTile.Width;
rc.Y /= sizTile.Height;
rc.Intersect(Bounds);
for (int ty = rc.Y - (int)tile.ty; ty < rc.Bottom - (int)tile.ty; ty++) {
for (int tx = rc.X - (int)tile.tx; tx < rc.Right - (int)tile.tx; tx++) {
if (tile.Visibility == null || tile.IsVisible(tx, ty)) {
Template tmpl = tile.GetTemplate(m_tmpd);
// Maybe this template doesn't exist in document's current tile collection
if (tmpl == null)
continue;
// Make sure this part of the template is occupied first
if (tmpl.OccupancyMap[ty, tx])
aterMap[ty + (int)tile.ty - Bounds.Y, tx + (int)tile.tx - Bounds.X] = tmpl.TerrainMap[ty, tx];
}
}
}
}
if (mi is Wall) {
int tx = (int)mi.tx - Bounds.X;
int ty = (int)mi.ty - Bounds.Y;
if (tx < 0 || tx >= Bounds.Width)
continue;
if (ty < 0 || ty >= Bounds.Height)
continue;
aterMap[ty, tx] = TerrainTypes.Blocked;
}
}
return aterMap;
}
public TerrainColors[,] GetTerrainColorsMap(Size sizTile, TemplateDoc tmpd) {
TerrainColors[,] atclrMap = new TerrainColors[Bounds.Height * 2, Bounds.Width * 2];
foreach (IMapItem mi in m_alsmi) {
Tile tile = mi as Tile;
if (tile == null)
continue;
int x = (int)mi.tx * sizTile.Width;
int y = (int)mi.ty * sizTile.Height;
Rectangle rc = tile.GetBoundingRectAt(x, y, sizTile, m_tmpd);
rc.Width /= sizTile.Width;
rc.Height /= sizTile.Height;
rc.X /= sizTile.Width;
rc.Y /= sizTile.Height;
rc.Intersect(Bounds);
for (int ty = rc.Y - (int)tile.ty; ty < rc.Bottom - (int)tile.ty; ty++) {
for (int tx = rc.X - (int)tile.tx; tx < rc.Right - (int)tile.tx; tx++) {
if (tile.Visibility == null || tile.Visibility[ty, tx]) {
Template tmpl = tile.GetTemplate(m_tmpd);
// Maybe this template doesn't exist in document's current tile collection
if (tmpl == null)
continue;
// TerrainColors are a 2x2 subgrid inside each tile
for (int tyT = 0; tyT < 2; tyT++) {
for (int txT = 0; txT < 2; txT++) {
TerrainColors tclr = TerrainColors.Grass;
if (tmpl.TerrainColors != null)
tclr = tmpl.TerrainColors[ty * 2 + tyT, tx * 2 + txT];
atclrMap[(ty + (int)tile.ty - Bounds.Y) * 2 + tyT, (tx + (int)tile.tx - Bounds.X) * 2 + txT] = tclr;
}
}
}
}
}
}
return atclrMap;
}
//
// Level validation stuff
//
public enum ValidateError {
Info,
Warning,
Error
}
public delegate void ValidateErrorDelegate(LevelDoc lvld, ValidateError ve, int tx, int ty, object ob, string str);
#if false
public int Validate(ValidateErrorDelegate dgt) {
return 0;
}
#else
enum ItemMask { None = 0, Galaxite = 1, Wall = 2, Unreachable = 4, Scenery = 8, Structure = 16, MobileUnit = 32 };
public int Validate(ValidateErrorDelegate dgt) {
int cError = 0;
// Validate switch count
int cSwitchesMax = 16;
if (m_swm.Items.Count > cSwitchesMax) {
dgt(this, ValidateError.Error, 0, 0, null, String.Format("Max switches is {0}, this mission has {1}", cSwitchesMax, m_swm.Items.Count));
cError++;
}
// Validate area count
int cAreas = 0;
foreach (IMapItem mi in m_alsmi) {
if (mi is Area)
cAreas++;
}
int cAreasMax = 32;
if (cAreas > cAreasMax) {
dgt(this, ValidateError.Error, 0, 0, null, String.Format("Max areas is {0}, this mission has {1}", cAreasMax, cAreas));
cError++;
}
// Validate SideInfo
int cHuman = 0;
foreach (SideInfo sidi in m_alsidi) {
if (sidi.Intelligence == Intelligence.Human)
cHuman++;
#if false
if (sidi.InitialCredits == 0)
dgt(this, ValidateError.Warning, 0, 0, sidi, String.Format("Side {0} starts with no credits", sidi.Side));
#endif
}
#if false
// Don't allow multi-player yet
if (MaxPlayers != 1) {
dgt(this, ValidateError.Error, 0, 0, null, "Multi-player not supported yet: MaxPlayers should be 1");
cError++;
}
// Must have 1 human side
if (cHuman != 1) {
dgt(this, ValidateError.Error, 0, 0, null, "Must have 1 human side!");
cError++;
}
#endif
if (cHuman > MaxPlayers) {
dgt(this, ValidateError.Error, 0, 0, null, String.Format("MaxPlayers is {0}, # of human sides is {1}", MaxPlayers, cHuman));
cError++;
}
// Validate gob inside/outside of boundaries
foreach (IMapItem mi in m_alsmi) {
if (mi is Galaxite)
continue;
if (mi is Wall)
continue;
if (mi is Tile)
continue;
// Gobs can be inside or outside of boundaries. If gobs are intersected by the boundaries
// that is not ok and will send the game into fits
Rectangle rcT = new Rectangle(Bounds.Location, Bounds.Size);
if (!Bounds.Contains(new Rectangle((int)mi.tx, (int)mi.ty, mi.ctx, mi.cty))) {
if (mi is Area) {
Area area = (Area)mi;
dgt(this, ValidateError.Error, (int)mi.tx, (int)mi.ty, area, String.Format("Area '{0}' out of bounds", area.Name));
cError++;
} else {
// If we have a gob that is "partially" outside, and gets compiled into the game,
// errors will occur at runtime, so make this an error
dgt(this, ValidateError.Error, (int)mi.tx, (int)mi.ty, mi, mi.ToString() + " out of bounds");
cError++;
}
}
}
// Collect placement information.
// enum ItemMask { None = 0, Galaxite = 1, Wall = 2, Unreachable = 4, Scenery = 8, Structure = 16, MobileUnit = 32 };
// Initialize with all "unreachable areas" appropriately
TerrainTypes[,] aterMap = GetTerrainMap(m_tmpd.TileSize, m_tmpd, false);
ItemMask[,] aimMap = new ItemMask[Bounds.Height, Bounds.Width];
for (int ty = 0; ty < Bounds.Height; ty++) {
for (int tx = 0; tx < Bounds.Width; tx++) {
switch (aterMap[ty, tx]) {
case TerrainTypes.Blocked:
case TerrainTypes.Wall:
aimMap[ty, tx] |= ItemMask.Unreachable;
break;
}
}
}
// Validate placement
foreach (IMapItem mi in m_alsmi) {
ItemMask im = ItemMask.None;
ItemMask imInvalid = ItemMask.None;
if (mi is Galaxite) {
im = ItemMask.Galaxite;
imInvalid = ItemMask.Wall | ItemMask.Unreachable | ItemMask.Structure;
} else if (mi is Wall) {
im = ItemMask.Wall;
imInvalid = ItemMask.Galaxite | ItemMask.Wall | ItemMask.Structure | ItemMask.MobileUnit;
} else if (mi is Scenery) {
im = ItemMask.Scenery;
imInvalid = ItemMask.None;
} else if (mi is Structure) {
im = ItemMask.Structure;
imInvalid = ItemMask.Galaxite | ItemMask.Wall | ItemMask.Unreachable | ItemMask.Structure | ItemMask.MobileUnit;
} else if (mi is MobileUnit) {
im = ItemMask.MobileUnit;
imInvalid = ItemMask.Wall | ItemMask.Unreachable | ItemMask.Structure | ItemMask.MobileUnit;
}
if (im == ItemMask.None)
continue;
// Check each tile occupied by this mi
ItemMask imError = ItemMask.None;
for (int ty = (int)mi.ty; ty < (int)mi.ty + mi.cty; ty++) {
for (int tx = (int)mi.tx; tx < (int)mi.tx + mi.ctx; tx++) {
int txT = tx - Bounds.Left;
int tyT = ty - Bounds.Top;
if (txT < 0 || tyT < 0)
continue;
if (txT >= Bounds.Width || tyT >= Bounds.Height)
continue;
ItemMask imInvalidOverlap = aimMap[tyT, txT] & imInvalid;
aimMap[tyT, txT] |= im;
ItemMask imErrorNew = (ItemMask)(imInvalidOverlap & ~imError);
if (imErrorNew != ItemMask.None) {
imError |= imErrorNew;
// Build up the error string
ItemMask[] aimValues = (ItemMask[])Enum.GetValues(typeof(ItemMask));
string strT = "";
for (int n = 0; n < aimValues.Length; n++) {
if ((aimValues[n] & imErrorNew) != 0)
strT += aimValues[n].ToString() + ",";
}
if (strT != "")
strT = strT.Substring(0, strT.Length - 1);
dgt(this, ValidateError.Error, (int)mi.tx, (int)mi.ty, mi, mi.ToString() + " is on top of: " + strT);
cError++;
}
}
}
}
// Validate that structures don't make terrain inaccessible
// Everywhere there is a blocked section in aterMapStructs that isn't
// blocked in aterMap and isn't a structure is now inaccessible because
// of a structure block
TerrainTypes[,] aterMapStructs = GetTerrainMap(m_tmpd.TileSize, m_tmpd, true);
for (int ty = 0; ty < aterMap.GetLength(0); ty++) {
for (int tx = 0; tx < aterMap.GetLength(1); tx++) {
// Check
bool fSrcOpen = (aimMap[ty, tx] & (ItemMask.Unreachable | ItemMask.Structure)) == 0;
if (fSrcOpen && aterMapStructs[ty, tx] == TerrainTypes.Blocked) {
dgt(this, ValidateError.Error, tx + Bounds.Left, ty + Bounds.Top, null, String.Format("Terrain at {0},{1} is inaccessible due to structure blockage", tx + Bounds.Left, ty + Bounds.Top));
cError++;
}
}
}
// Validate areas in triggers exist
StringCollection strc = CaTypeArea.GetAreaNames();
foreach (Side side in Enum.GetValues(typeof(Side))) {
Trigger[] atgr = m_tgrm.GetTriggerList(side);
foreach (Trigger tgr in atgr) {
foreach (CaBase cab in tgr.Conditions) {
foreach (CaType cat in cab.GetTypes()) {
if (cat is CaTypeArea) {
CaTypeArea catArea = (CaTypeArea)cat;
if (strc.IndexOf(catArea.Area) < 0) {
dgt(this, ValidateError.Error, 0, 0, null, "Area " + catArea.Area + " in trigger " + tgr.ToString() + " doesn't exist!");
cError++;
}
}
}
}
foreach (CaBase cab in tgr.Actions) {
foreach (CaType cat in cab.GetTypes()) {
if (cat is CaTypeArea) {
CaTypeArea catArea = (CaTypeArea)cat;
if (strc.IndexOf(catArea.Area) < 0) {
dgt(this, ValidateError.Error, 0, 0, null, "Area " + catArea.Area + " in trigger " + tgr.ToString() + " doesn't exist!");
cError++;
}
}
}
}
}
}
// Validate unit groups in triggers
foreach (Side side in Enum.GetValues(typeof(Side))) {
Trigger[] atgr = m_tgrm.GetTriggerList(side);
foreach (Trigger tgr in atgr) {
foreach (CaBase cab in tgr.Conditions) {
foreach (CaType cat in cab.GetTypes()) {
if (cat is CaTypeUnitGroup) {
CaTypeUnitGroup catUg = (CaTypeUnitGroup)cat;
if (Array.IndexOf(atgr, tgr) < 0) {
dgt(this, ValidateError.Error, 0, 0, null, "Orphaned UnitGroup in trigger " + tgr.ToString());
cError++;
}
}
}
}
foreach (CaBase cab in tgr.Actions) {
foreach (CaType cat in cab.GetTypes()) {
if (cat is CaTypeArea) {
CaTypeArea catArea = (CaTypeArea)cat;
if (strc.IndexOf(catArea.Area) < 0) {
dgt(this, ValidateError.Error, 0, 0, null, "Orphaned UnitGroup in trigger " + tgr.ToString());
cError++;
}
}
}
}
}
}
// Validate triggers per side limit
int cTriggersPerSideMax = 128;
foreach (Side side in Enum.GetValues(typeof(Side))) {
Trigger[] atgr = m_tgrm.GetTriggerList(side);
if (atgr == null)
continue;
if (atgr.Length > cTriggersPerSideMax) {
string strT = String.Format("Triggers per side {0}. Side {1} has {2} triggers", cTriggersPerSideMax, side.ToString(), atgr.Length);
dgt(this, ValidateError.Error, 0, 0, null, strT);
cError++;
}
}
// Validate gob limits
int[] acStructures = new int[Enum.GetNames(typeof(Side)).Length];
int[] acMunts = new int[Enum.GetNames(typeof(Side)).Length];
int cScenery = 0;
foreach (IMapItem mi in m_alsmi) {
if (mi is Unit) {
Unit unit = (Unit)mi;
if (unit is Structure) {
acStructures[(int)unit.Side]++;
} else {
acMunts[(int)unit.Side]++;
}
continue;
}
if (mi is Scenery) {
cScenery++;
continue;
}
}
// Scenery Limit
int cSceneryMax = 100;
if (cScenery > cSceneryMax) {
string strT = String.Format("{0} scenery; {1} allowed", cScenery, cSceneryMax);
dgt(this, ValidateError.Error, 0, 0, null, strT);
cError++;
}
// Unit counts
if (MaxPlayers == 1) {
// Single player - asymmetric: one human count, shared computer counts
// #define kcStructGobsHumanMin 39
// #define kcStructGobsComputerMin 52
// #define kcMuntGobsHumanMin 60
// #define kcMuntGobsComputerMin 80
int cStructsHumanMax = 39;
int cMuntsHumanMax = 60;
int cStructsComputerMax = 52;
int cMuntsComputerMax = 80;
int cStructsHuman = 0;
int cMuntsHuman = 0;
int cStructsComputer = 0;
int cMuntsComputer = 0;
foreach (SideInfo sidi in m_alsidi) {
if (sidi.Intelligence == Intelligence.Human) {
cStructsHuman += acStructures[(int)sidi.Side];
cMuntsHuman += acMunts[(int)sidi.Side];
} else {
cStructsComputer += acStructures[(int)sidi.Side];
cMuntsComputer += acMunts[(int)sidi.Side];
}
}
// HACK ALERT: Sometimes levels have more human structures than the human limit. In this
// case, take some from the computer side if possible. Hack: Reserve 5 structs for computer building
int cStructuresAvailable = cStructsComputerMax - cStructsComputer - 5;
if (cStructuresAvailable < 0)
cStructuresAvailable = 0;
cStructsHumanMax += cStructuresAvailable;
cStructsComputerMax -= cStructuresAvailable;
// Check
if (cStructsHuman > cStructsHumanMax) {
string strT = String.Format("Human Side has {0} structures; {1} allowed", cStructsHuman, cStructsHumanMax);
dgt(this, ValidateError.Error, 0, 0, null, strT);
cError++;
}
if (cMuntsHuman > cMuntsHumanMax) {
string strT = String.Format("Human Side has {0} mobile units; {1} allowed", cMuntsHuman, cMuntsHumanMax);
dgt(this, ValidateError.Error, 0, 0, null, strT);
cError++;
}
if (cStructsComputer > cStructsComputerMax) {
string strT = String.Format("Computer Side has {0} structures; {1} allowed", cStructsComputer, cStructsComputerMax);
dgt(this, ValidateError.Error, 0, 0, null, strT);
cError++;
}
if (cMuntsComputer > cMuntsComputerMax) {
string strT = String.Format("Computer Side has {0} mobile units; {1} allowed", cMuntsComputer, cMuntsComputerMax);
dgt(this, ValidateError.Error, 0, 0, null, strT);
cError++;
}
} else {
// Multi-player - symmetric: same counts for each side
// #define kcStructGobsMax 55
// #define kcMuntGobsMax 88
int cStructsMax = 55;
int cMuntsMax = 88;
foreach (Side side in Enum.GetValues(typeof(Side))) {
if (acStructures[(int)side] > cStructsMax) {
string strT = String.Format("Side {0} has {1} structures; {2} allowed", side.ToString(), acStructures[(int)side], cStructsMax);
dgt(this, ValidateError.Error, 0, 0, null, strT);
cError++;
}
if (acMunts[(int)side] > cMuntsMax) {
string strT = String.Format("Side {0} has {1} mobile units; {2} allowed", side.ToString(), acMunts[(int)side], cMuntsMax);
dgt(this, ValidateError.Error, 0, 0, null, strT);
cError++;
}
}
}
// UNDONE: Validate legal multiplayer triggers
// UNDONE: Validate computer sides have enough power
// UNDONE: Validate computer sides have a surveillance center if they have towers
// UNDONE: info -- total credits value of on-map Galaxite
// UNDONE: info -- power supply/demand for each side
return cError;
}
#endif
public void SaveIni(string strFile, int nVersion, string strFileTmap, string strFileTrmap, string strFilePalette) {
FileStream stm = new FileStream(strFile, FileMode.Create);
SaveIni(stm, nVersion, strFileTmap, strFileTrmap, strFilePalette, false);
stm.Close();
}
public void SaveIni(Stream stm, int nVersion, string strFileTmap, string strFileTrmap, string strFilePalette, bool fDemoCheckTrigger) {
Ini ini = new Ini();
Ini.Section sec;
// [Intro]
sec = new Ini.Section("Intro");
sec.Add(new Ini.Property("String", "This is a test level!"));
ini.Add(sec);
// [Side1-n]
int txOrigin = Bounds.X;
int tyOrigin = Bounds.Y;
// Hack - there should be a real "neutral" side
ArrayList alsidiT = (ArrayList)m_alsidi.Clone();
SideInfo sidiNeutral = new SideInfo(Side.sideNeutral);
sidiNeutral.Intelligence = Intelligence.ComputerNeutral;
sidiNeutral.InitialCredits = 0;
sidiNeutral.InitialView = new Point(0, 0);
alsidiT.Add(sidiNeutral);
foreach (SideInfo sidi in alsidiT) {
sec = new Ini.Section(sidi.Side.ToString());
sec.Add(new Ini.Property("InitialView", String.Format("{0},{1}",
sidi.InitialView.X - txOrigin, sidi.InitialView.Y - tyOrigin)));
sec.Add(new Ini.Property("InitialCredits", sidi.InitialCredits.ToString()));
sec.Add(new Ini.Property("Intelligence", "knIntelligence" + sidi.Intelligence.ToString()));
// How many units for this side?
int cStructures = 0;
int cMobileUnits = 0;
foreach (IMapItem mi in m_alsmi) {
if (mi is Unit) {
Unit unt = (Unit)mi;
if (unt.Side == sidi.Side) {
if (mi is MobileUnit) {
cMobileUnits++;
}
if (mi is Structure) {
cStructures++;
}
}
}
}
sec.Add(new Ini.Property("InitialMobileUnitCount", cMobileUnits.ToString()));
sec.Add(new Ini.Property("InitialStructureCount", cStructures.ToString()));
ini.Add(sec);
}
// [GameObjects]
sec = new Ini.Section("GameObjects");
foreach (IMapItem mi in m_alsmi) {
if (mi is Galaxite)
continue;
if (mi is Area)
continue;
if (mi is Wall)
continue;
if (mi is Tile)
continue;
Ini.Property prop = mi.GetIniProperty(txOrigin, tyOrigin);
if (prop == null)
continue;
// Skip Gobs that are out of bounds
// UNDONE: can't do the right thing to make sure Gob's right/bottom
// edges aren't out of bounds because M doesn't know the true
// width and height of Gobs.
if (!Bounds.Contains(new Rectangle((int)mi.tx, (int)mi.ty, mi.ctx, mi.cty))) {
Console.WriteLine("{0} out of bounds", mi);
continue;
}
sec.Add(prop);
}
ini.Add(sec);
// [Galaxite]
sec = new Ini.Section("Galaxite");
foreach (IMapItem mi in m_alsmi) {
if (!(mi is Galaxite))
continue;
Ini.Property prop = mi.GetIniProperty(txOrigin, tyOrigin);
if (prop == null)
continue;
// Skip Galaxite that is out of bounds
if (!Bounds.Contains((int)mi.tx, (int)mi.ty)) {
Console.WriteLine("{0} out of bounds", mi);
continue;
}
sec.Add(prop);
}
ini.Add(sec);
#if false
// In terrain now
// [Walls]
sec = new Ini.Section("Walls");
foreach (IMapItem mi in m_alsmi) {
if (!(mi is Wall))
continue;
Ini.Property prop = mi.GetIniProperty(txOrigin, tyOrigin);
if (prop == null)
continue;
// Skip Walls that are out of bounds
if (!Bounds.Contains((int)mi.tx, (int)mi.ty)) {
Console.WriteLine("{0} out of bounds", mi);
continue;
}
sec.Add(prop);
}
ini.Add(sec);
#endif
// [Areas]
ArrayList alT = new ArrayList();
foreach (IMapItem mi in m_alsmi) {
if (!(mi is Area))
continue;
alT.Add(mi);
}
alT.Sort();
sec = new Ini.Section("Areas");
foreach (IMapItem mi in alT) {
Ini.Property prop = mi.GetIniProperty(txOrigin, tyOrigin);
if (prop == null)
continue;
Area area = (Area)mi;
if (!Bounds.Contains(new Rectangle((int)mi.tx, (int)mi.ty, mi.ctx, mi.cty))) {
MessageBox.Show(String.Format("The area \"{0}\" lies outside of the map's bounds", area.Name), "Error Compiling Level");
}
sec.Add(prop);
}
ini.Add(sec);
// [Triggers]
// NOTE: Triggers must be written before UnitGroups because some trigger actions
// e.g., CreateUnitAtArea will dynamically create UnitGroups and add them to the UnitGroup list
ini.Add(m_tgrm.GetIniSection(fDemoCheckTrigger));
// [UnitGroup 0-n]
m_ugm.SaveIni(ini);
// [Switches]
sec = new Ini.Section("Switches");
foreach (Switch sw in SwitchManager.Items)
sec.Add(new Ini.Property(sw.Name, ""));
ini.Add(sec);
// [General]
// This section is written last in case any of the values are modified by
// the process of writing out the prior sections (e.g., CreateUnitAtArea actions add UnitGroups)
sec = new Ini.Section("General", null);
sec.Add(new Ini.Property("Title", Title));
sec.Add(new Ini.Property("TileMap", strFileTmap));
sec.Add(new Ini.Property("TerrainMap", strFileTrmap));
sec.Add(new Ini.Property("Palette", strFilePalette));
sec.Add(new Ini.Property("MinPlayers", m_nPlayersMin.ToString()));
sec.Add(new Ini.Property("MaxPlayers", m_nPlayersMax.ToString()));
sec.Add(new Ini.Property("UnitGroupCount", m_ugm.Items.Count.ToString()));
// < 0 means use the current version, otherwise use the passed version
// This is the "level file format" version
if (nVersion < 0)
nVersion = 1;
sec.Add(new Ini.Property("Version", nVersion.ToString()));
// Add a random number for the revision #. This # is used to determine if saved games are
// based on older versions of a mission.
if (nVersion > 0) {
Random rand = new Random();
uint dwRevision = (uint)rand.Next();
sec.Add(new Ini.Property("Revision", dwRevision.ToString()));
}
ini.Add(sec);
// Done
ini.Save(stm);
// Mostly Done.
// Clean out the "__cuaa" unit groups created by CreateUnitAtAreaTriggerAction
ArrayList alsRemove = new ArrayList();
UnitGroup[] aug = (UnitGroup[])m_ugm.Items.ToArray(typeof(UnitGroup));
for (int i = 0; i < m_ugm.Items.Count; i++) {
if (((UnitGroup)m_ugm.Items[i]).Name.StartsWith("__cuaa")) {
alsRemove.Add(m_ugm.Items[i]);
}
}
foreach (UnitGroup ug in alsRemove) {
m_ugm.RemoveUnitGroup(ug);
}
}
IMapItem CreateGameObject(string strSecName, string strName, string strValue) {
if (strSecName == "Galaxite") {
return new Galaxite(strName, strValue, Bounds.Left, Bounds.Top);
} else if (strSecName == "Areas") {
return new Area(strName, strValue, Bounds.Left, Bounds.Top);
} else if (strSecName == "GameObjects") {
int gt = int.Parse(strValue.Split(',')[0]);
switch (gt) {
case 1: // kgtShortRangeInfantry
return new ShortRangeInfantry(strName, strValue, Bounds.Left, Bounds.Top);
case 2: // kgtLongRangeInfantry
return new LongRangeInfantry(strName, strValue, Bounds.Left, Bounds.Top);
case 3: // kgtHumanResourceCenter
return new HumanResourceCenter(strName, strValue, Bounds.Left, Bounds.Top);
case 5: // kgtScenery
return new Scenery(strName, strValue, Bounds.Left, Bounds.Top);
case 7: // kgtReactor
return new Reactor(strName, strValue, Bounds.Left, Bounds.Top);
case 8: // kgtProcessor
return new Processor(strName, strValue, Bounds.Left, Bounds.Top);
case 11: // kgtGalaxMiner
return new GalaxMiner(strName, strValue, Bounds.Left, Bounds.Top);
case 12: // kgtHeadquarters
return new Headquarters(strName, strValue, Bounds.Left, Bounds.Top);
case 13: // kgtResearchCenter
return new ResearchCenter(strName, strValue, Bounds.Left, Bounds.Top);
case 14: // kgtVehicleTransportStation
return new VehicleTransportStation(strName, strValue, Bounds.Left, Bounds.Top);
case 15: // kgtRadar
return new Radar(strName, strValue, Bounds.Left, Bounds.Top);
case 16: // kgtLightTank
return new LightTank(strName, strValue, Bounds.Left, Bounds.Top);
case 17: // kgtMediumTank
return new MediumTank(strName, strValue, Bounds.Left, Bounds.Top);
case 18: // kgtMachineGunVehicle
return new MachineGunVehicle(strName, strValue, Bounds.Left, Bounds.Top);
case 19: // kgtRocketVehicle
return new RocketVehicle(strName, strValue, Bounds.Left, Bounds.Top);
case 20: // kgtTakeoverSpecialist
return new TakeoverSpecialist(strName, strValue, Bounds.Left, Bounds.Top);
case 21: // kgtWarehouse
return new Warehouse(strName, strValue, Bounds.Left, Bounds.Top);
case 22: // kgtMobileHeadquarters
return new MobileHeadquarters(strName, strValue, Bounds.Left, Bounds.Top);
case 26: // kgtMachineGunTower
return new MachineGunTower(strName, strValue, Bounds.Left, Bounds.Top);
case 27: // kgtRocketTower
return new RocketTower(strName, strValue, Bounds.Left, Bounds.Top);
case 32: // kgtArtillery
return new Artillery(strName, strValue, Bounds.Left, Bounds.Top);
case 34: // kgtAndy
return new Andy(strName, strValue, Bounds.Left, Bounds.Top);
case 35: // kgtReplicator
return new Replicator(strName, strValue, Bounds.Left, Bounds.Top);
case 36: // kgtActivator
return new Activator(strName, strValue, Bounds.Left, Bounds.Top);
case 37: // kgtFox
return new Fox(strName, strValue, Bounds.Left, Bounds.Top);
//case 4: // kgtSurfaceDecal
//case 6: // kgtAnimation:
//case 9: // kgtStructure
//case 10: // kgtUnit
//case 23: // kgtOvermind
//case 24: // kgtTankShot
//case 25: // kgtRocket
//case 28: // kgtScorch
//case 29: // kgtSmoke
//case 30: // kgtPuff
//case 31: // kgtBullet
//case 33: // kgtArtilleryShot
//case 38: // kgtAndyShot
default:
return null;
}
}
return null;
}
public void LoadIni(Ini ini) {
// General
Ini.Section secGen = ini["General"];
m_strTitle = secGen["Title"].Value;
m_nPlayersMin = int.Parse(secGen["MinPlayers"].Value);
m_nPlayersMax = int.Parse(secGen["MaxPlayers"].Value);
// SideInfo
for (Side side = Side.side1; side <= Side.side4; side++) {
Ini.Section sec = ini[side.ToString()];
if (sec == null) {
continue;
}
SideInfo sidi = new SideInfo(side);
string s = sec["InitialView"].Value;
Regex re = new Regex(@"^(?<tx>(-)?\d+),(?<ty>(-)?\d+)$");
Match m = re.Match(s);
int tx = int.Parse(m.Groups["tx"].Value);
int ty = int.Parse(m.Groups["ty"].Value);
Point ptInitialView = new Point(tx + Bounds.Left, ty + Bounds.Top);
if (ptInitialView.X < Bounds.Left) {
ptInitialView.X = Bounds.Left;
}
if (ptInitialView.Y < Bounds.Top) {
ptInitialView.Y = Bounds.Top;
}
sidi.InitialView = ptInitialView;
sidi.InitialCredits = int.Parse(sec["InitialCredits"].Value);
sidi.Intelligence = (Intelligence)int.Parse(sec["Intelligence"].Value);
m_alsidi.Add(sidi);
}
// Misc MapItems. Areas must come before GameObjects because GameObjects
// can refer to Areas by index in UnitActions.
string[] secNames = { "Galaxite", "Areas", "GameObjects" };
foreach (string secName in secNames) {
Ini.Section sec = ini[secName];
ArrayList alsMapItems = new ArrayList();
foreach (Ini.Property prop in sec) {
IMapItem mi = CreateGameObject(secName, prop.Name, prop.Value);
if (mi != null) {
alsMapItems.Add(mi);
}
}
// Sometimes Areas are given the same name. Then on export the sort order
// doesn't match the original, which is a problem since in the game, area
// creation order is important.
if (secName == "Areas") {
for (int i = 0; i < alsMapItems.Count; i++) {
Area ar = (Area)alsMapItems[i];
ar.BonusSortKey = i;
}
}
AddMapItems((IMapItem[])alsMapItems.ToArray(typeof(IMapItem)));
}
// Switches (must be before triggers).
foreach (Ini.Property prop in ini["Switches"]) {
m_swm.AddSwitch(new Switch(prop.Name));
}
// UnitGroups (must be before triggers).
m_ugm.LoadIni(ini);
// Triggers
m_tgrm.LoadIni(ini);
// Clear out __cuaa UnitGroups now that triggers have been loaded
// (CreateUnitAtAreaTrigger loads state from __cuaa triggers).
ArrayList alsRemove = new ArrayList();
for (int i = 0; i < m_ugm.Items.Count; i++) {
UnitGroup ug = (UnitGroup)m_ugm.Items[i];
if (ug.Name.StartsWith("__cuaa")) {
alsRemove.Add(ug);
}
}
foreach (UnitGroup ug in alsRemove) {
m_ugm.RemoveUnitGroup(ug);
}
// TODO: remove the demo check trigger
}
public void EditTriggers() {
TriggersForm frm = new TriggersForm(m_tgrm);
m_tgrm.ClearModified();
frm.ShowDialog();
if (m_tgrm.IsModified())
SetModified(true);
}
public void EditUnitGroups() {
UnitGroupsForm frm = new UnitGroupsForm(this, m_ugm);
#if false
frm.Show();
#else
frm.ShowDialog();
#endif
}
public void EditComments() {
EditCommentsForm frm = new EditCommentsForm(m_strComment);
if (frm.ShowDialog() == DialogResult.OK) {
m_strComment = frm.textBox1.Text;
SetModified(true);
}
}
public void EditLevelText() {
new EditLevelTextForm(this).ShowDialog();
}
public string GetLevelText() {
StringBuilder strb = new StringBuilder();
ArrayList alsTriggers = m_tgrm.Triggers;
foreach (Trigger tgr in alsTriggers) {
ArrayList alsActions = tgr.Actions;
foreach (CaBase cab in alsActions) {
CaType[] acat = cab.GetTypes();
foreach (CaType cat in acat) {
if (cat is CaTypeText || cat is CaTypeRichText) {
int cch = "TriggerAction".Length;
string strAction = cab.GetType().Name;
strAction = strAction.Remove(strAction.Length - cch, cch);
// string strCaType = cat.GetType().Name;
// strCaType = strCaType.Substring("CaType".Length);
if (strAction == "Ecom") {
string str = " ($1 w/ $2) from $3 to $4";
for (int j = 0; j < acat.Length - 1; j++)
str = str.Replace("$" + (j + 1), acat[j].ToString());
strAction += str;
}
if (strb.Length != 0)
strb.Append('\n');
strb.AppendFormat("[{0}]\n", strAction);
strb.Append(cat.ToString());
}
}
}
}
return strb.ToString();
}
public bool SetLevelText(string strLevelText, out int ichErrorPos) {
ichErrorPos = 0;
// Validation pass
StringReader strr;
strr = new StringReader(strLevelText);
ArrayList alsTriggers = m_tgrm.Triggers;
foreach (Trigger tgr in alsTriggers) {
ArrayList alsActions = tgr.Actions;
foreach (CaBase cab in alsActions) {
CaType[] acat = cab.GetTypes();
foreach (CaType cat in acat) {
if (cat is CaTypeText || cat is CaTypeRichText) {
int cch = "TriggerAction".Length;
string strAction = cab.GetType().Name;
strAction = strAction.Remove(strAction.Length - cch, cch);
// string strCaType = cat.GetType().Name;
// strCaType = strCaType.Substring("CaType".Length);
if (strAction == "Ecom") {
string str = " ($1 w/ $2) from $3 to $4";
for (int j = 0; j < acat.Length - 1; j++)
str = str.Replace("$" + (j + 1), acat[j].ToString());
strAction += str;
}
string strType = String.Format("[{0}]", strAction);
string strT = strr.ReadLine();
if (strType != strT) {
MessageBox.Show(String.Format("Expected \"{0}\" but found \"{1}\"", strType, strT), "Error");
return false;
}
ichErrorPos += strT.Length + 1; // for \n
while (strr.Peek() != '[' && strr.Peek() != -1) {
strT = strr.ReadLine();
ichErrorPos += strT.Length + 1; // for \n
}
}
}
}
}
strr = new StringReader(strLevelText);
alsTriggers = m_tgrm.Triggers;
foreach (Trigger tgr in alsTriggers) {
ArrayList alsActions = tgr.Actions;
foreach (CaBase cab in alsActions) {
CaType[] acat = cab.GetTypes();
foreach (CaType cat in acat) {
if (cat is CaTypeText || cat is CaTypeRichText) {
int cch = "TriggerAction".Length;
string strAction = cab.GetType().Name;
strAction = strAction.Remove(strAction.Length - cch, cch);
// string strCaType = cat.GetType().Name;
// strCaType = strCaType.Substring("CaType".Length);
string strType = String.Format("[{0}]", strAction);
string strT = strr.ReadLine();
StringBuilder strb = new StringBuilder();
while (strr.Peek() != '[' && strr.Peek() != -1) {
strT = strr.ReadLine();
strb.Append(strT);
if (strr.Peek() != '[' && strr.Peek() != -1)
strb.Append('\n');
}
string strOld = cat.ToString();
string strNew = strb.ToString();
if (strOld != strNew) {
if (cat is CaTypeText) {
((CaTypeText)cat).Text = strNew;
} else if (cat is CaTypeRichText) {
((CaTypeRichText)cat).Text = strNew;
}
}
}
}
}
}
SetModified(true);
return true;
}
[BrowsableAttribute(false)]
public UnitGroupManager UnitGroupManager {
get {
return m_ugm;
}
}
[BrowsableAttribute(false)]
public SwitchManager SwitchManager {
get {
return m_swm;
}
}
[BrowsableAttribute(false)]
public CounterManager CounterManager {
get {
return m_ctrm;
}
}
[BrowsableAttribute(false)]
public IMapItem[] MapItems {
get {
return (IMapItem[])m_alsmi.ToArray(typeof(IMapItem));
}
}
public string Title {
get {
return m_strTitle;
}
set {
if (m_strTitle == value)
return;
m_strTitle = value;
SetModified(true);
if (NameChanged != null)
NameChanged(this);
m_strOutputFilename = null;
}
}
[BrowsableAttribute(false)]
public int Width {
get {
return m_ctx;
}
set {
m_ctx = value;
if (ImageChanged != null)
ImageChanged();
}
}
[BrowsableAttribute(false)]
public int Height {
get {
return m_cty;
}
set {
m_cty = value;
if (ImageChanged != null)
ImageChanged();
}
}
public Rectangle Bounds {
get {
return m_trcBounds;
}
set {
if (value.Left < 0 || value.Left >= m_ctx || value.Right < 0 || value.Right >= m_ctx ||
value.Top < 0 || value.Top >= m_cty || value.Bottom < 0 || value.Bottom >= m_cty) {
return;
}
m_trcBounds = value;
m_fUpdateDirty = true;
SetModified(true);
}
}
public int MinPlayers {
get {
return m_nPlayersMin;
}
set {
if (m_nPlayersMin == value)
return;
m_nPlayersMin = value;
SetModified(true);
}
}
public int MaxPlayers {
get {
return m_nPlayersMax;
}
set {
if (m_nPlayersMax == value)
return;
m_nPlayersMax = value;
SetModified(true);
}
}
public ArrayList Selection {
get {
return (ArrayList)m_alsmiSelected.Clone();
}
set {
m_alsmiSelected = value;
m_fUpdateDirty = true;
}
}
}
public enum Intelligence {
Human,
Computer,
ComputerOvermind,
ComputerNeutral,
}
[Serializable]
public class SideInfo : ISerializable {
private Side m_side;
private int m_nInitialCredits;
private Point m_ptInitialView;
private Intelligence m_nIntelligence = Intelligence.Computer;
protected SideInfo(SerializationInfo info, StreamingContext ctx) {
m_side = (Side)info.GetValue("m_side", typeof(Side));
m_nInitialCredits = info.GetInt32("m_nInitialCredits");
m_ptInitialView = (Point)info.GetValue("m_ptInitialView", typeof(Point));
try {
m_nIntelligence = (Intelligence)info.GetValue("m_nIntelligence", typeof(Intelligence));
} catch (SerializationException) {
m_nIntelligence = m_side == Side.side1 ? Intelligence.Human : Intelligence.ComputerOvermind;
}
}
public void GetObjectData(SerializationInfo info, StreamingContext context) {
info.AddValue("m_side", m_side);
info.AddValue("m_nInitialCredits", m_nInitialCredits);
info.AddValue("m_ptInitialView", m_ptInitialView);
info.AddValue("m_nIntelligence", m_nIntelligence);
}
public SideInfo(Side side) {
m_side = side;
}
public Side Side {
get {
return m_side;
}
}
public int InitialCredits {
get {
return m_nInitialCredits;
}
set {
m_nInitialCredits = value;
}
}
public Point InitialView {
get {
return m_ptInitialView;
}
set {
m_ptInitialView = value;
}
}
public Intelligence Intelligence {
get {
return m_nIntelligence;
}
set {
m_nIntelligence = value;
}
}
}
public class LevelDocTemplate : DocTemplate {
static string[] astr = { "Level Document", "UntitledLevel", "Level Docs", "ld" };
public LevelDocTemplate(Type typeFrame, Type typeView) : base(astr, typeof(LevelDoc), typeFrame, typeView, new LevelDocBinder()) {
}
}
// Compatibility goo
public class LevelDocBinder : SerializationBinder {
public override Type BindToType(string strAssembly, string strType) {
if (strType == "m.LevelDescription")
return typeof(LevelDoc);
#if false
// This is how we migrate a serialized class from one Assembly to another.
if (strType.StartsWith("m.SideWinder")) {
System.Reflection.Assembly ass = Globals.Plugins[0].GetType().Assembly;
Type typeT = ass.GetType(strType);
return typeT;
}
#endif
// Backwards compatibility with old levels
switch (strType) {
case "m.CenterViewAction":
strType = "m.CenterViewTriggerAction";
break;
case "m.SetNextMissionAction":
strType = "m.SetNextMissionTriggerAction";
break;
case "m.EndMissionAction":
strType = "m.EndMissionTriggerAction";
break;
case "m.SetAllowedUnitsAction":
strType = "m.SetAllowedUnitsTriggerAction";
break;
case "m.EcomAction":
strType = "m.EcomTriggerAction";
break;
case "m.SetObjectiveAction":
strType = "m.SetObjectiveTriggerAction";
break;
case "m.WaitAction":
strType = "m.WaitTriggerAction";
break;
case "m.SetSwitchAction":
strType = "m.SetSwitchTriggerAction";
break;
case "m.PerserveTriggerAction":
strType = "m.PreserveTriggerTriggerAction";
break;
case "m.DefogAreaAction":
strType = "m.DefogAreaTriggerAction";
break;
case "m.TakeoverSpecialistInfantry":
strType = "m.TakeoverSpecialist";
break;
case "m.BuildUnitGroupTriggerAction":
strType = "m.CreateUnitGroupTriggerAction";
break;
#if false
case "m.MoveUnitAction":
strType = "m.MoveUnitGroupAction";
break;
case "m.SetSwitchUnitAction":
strType = "m.SetSwitchUnitGroupAction";
break;
case "m.WaitUnitAction":
strType = "m.WaitUnitGroupAction";
break;
#endif
}
return Type.GetType(strType + ", " + strAssembly);
}
}
}