mirror of
https://github.com/spiffcode/hostile-takeover.git
synced 2025-12-16 12:08:36 +00:00
2050 lines
58 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|