C&C Remastered Map Editor

Initial commit of C&C Remastered Map Editor code
This commit is contained in:
PG-SteveT 2020-09-10 11:12:58 -07:00
parent 1f6350fe6e
commit e37e174be1
289 changed files with 80922 additions and 7 deletions

View file

@ -0,0 +1,185 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using MobiusEditor.Utility;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
namespace MobiusEditor.Model
{
public class BooleanTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return (context is MapContext) && (sourceType == typeof(string));
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return (context is MapContext) && (destinationType == typeof(string));
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (!(value is bool boolean) || !CanConvertTo(context, destinationType))
{
return null;
}
return boolean ? "1" : "0";
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (!(value is string str) || !CanConvertFrom(context, value?.GetType()))
{
return null;
}
var first = (str.Length > 0) ? str.ToUpper()[0] : 0;
return (first == 'T') || (first == 'Y') || (first == '1');
}
}
public class PercentageTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return (context is MapContext) && (sourceType == typeof(string));
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return (context is MapContext) && (destinationType == typeof(string));
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (!(value is int percent) || !CanConvertTo(context, destinationType))
{
return null;
}
var mapContext = context as MapContext;
return mapContext.FractionalPercentages ? (percent / 100M).ToString("D2") : percent.ToString();
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (!(value is string str) || !CanConvertFrom(context, value?.GetType()))
{
return null;
}
var mapContext = context as MapContext;
if (mapContext.FractionalPercentages && str.Contains("."))
{
if (!decimal.TryParse(str, out decimal percent))
{
return null;
}
return (int)(percent * 100);
}
else
{
if (!int.TryParse(str, out int percent))
{
return null;
}
return percent;
}
}
}
public class BasicSection : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string name;
[DefaultValue(null)]
public string Name { get => name; set => SetField(ref name, value); }
private int carryOverCap;
[TypeConverter(typeof(PercentageTypeConverter))]
[DefaultValue(-1)]
public int CarryOverCap { get => carryOverCap; set => SetField(ref carryOverCap, value); }
private int carryOverMoney;
[TypeConverter(typeof(PercentageTypeConverter))]
[DefaultValue(100)]
public int CarryOverMoney { get => carryOverMoney; set => SetField(ref carryOverMoney, value); }
private string intro;
[DefaultValue(null)]
public string Intro { get => intro; set => SetField(ref intro, value); }
private string theme;
[DefaultValue("No Theme")]
public string Theme { get => theme; set => SetField(ref theme, value); }
private int percent;
[TypeConverter(typeof(PercentageTypeConverter))]
[DefaultValue(100)]
public int Percent { get => percent; set => SetField(ref percent, value); }
public string player;
[DefaultValue(null)]
public string Player { get => player; set => SetField(ref player, value); }
private string action;
[DefaultValue("x")]
public string Action { get => action; set => SetField(ref action, value); }
private string lose;
[DefaultValue("x")]
public string Lose { get => lose; set => SetField(ref lose, value); }
private string win;
[DefaultValue("x")]
public string Win { get => win; set => SetField(ref win, value); }
private string brief;
[DefaultValue("x")]
public string Brief { get => brief; set => SetField(ref brief, value); }
private string author;
[DefaultValue(null)]
public string Author { get => author; set => SetField(ref author, value); }
private string basePlayer;
[NonSerializedINIKey]
[DefaultValue(null)]
public string BasePlayer { get => basePlayer; set => SetField(ref basePlayer, value); }
private bool soloMission;
[DefaultValue(false)]
public bool SoloMission { get => soloMission; set => SetField(ref soloMission, value); }
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
{
return false;
}
field = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

View file

@ -0,0 +1,24 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using System.ComponentModel;
namespace MobiusEditor.Model
{
public class BriefingSection
{
[DefaultValue(null)]
public string Briefing { get; set; }
}
}

View file

@ -0,0 +1,103 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using MobiusEditor.Interface;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.CompilerServices;
namespace MobiusEditor.Model
{
public class Building : ICellOverlapper, ICellOccupier, INotifyPropertyChanged, ICloneable
{
public event PropertyChangedEventHandler PropertyChanged;
private BuildingType type;
public BuildingType Type { get => type; set => SetField(ref type, value); }
public Rectangle OverlapBounds => Type.OverlapBounds;
public bool[,] OccupyMask => Type.OccupyMask;
private HouseType house;
public HouseType House { get => house; set => SetField(ref house, value); }
private int strength;
public int Strength { get => strength; set => SetField(ref strength, value); }
private DirectionType direction;
public DirectionType Direction { get => direction; set => SetField(ref direction, value); }
private string trigger = Model.Trigger.None;
public string Trigger { get => trigger; set => SetField(ref trigger, value); }
private int basePriority = -1;
public int BasePriority { get => basePriority; set => SetField(ref basePriority, value); }
private bool isPrebuilt = true;
public bool IsPrebuilt { get => isPrebuilt; set => SetField(ref isPrebuilt, value); }
private bool sellable;
public bool Sellable { get => sellable; set => SetField(ref sellable, value); }
private bool rebuild;
public bool Rebuild { get => rebuild; set => SetField(ref rebuild, value); }
public ISet<int> BibCells { get; private set; } = new HashSet<int>();
private Color tint = Color.White;
public Color Tint
{
get => IsPrebuilt ? tint : Color.FromArgb((int)(tint.A * 0.75f), tint.R, tint.G, tint.B);
set => tint = value;
}
public Building Clone()
{
return new Building()
{
Type = Type,
House = House,
Strength = Strength,
Direction = Direction,
Trigger = Trigger,
BasePriority = BasePriority,
IsPrebuilt = IsPrebuilt,
Sellable = Sellable,
Rebuild = Rebuild,
Tint = Tint
};
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
{
return false;
}
field = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
object ICloneable.Clone()
{
return Clone();
}
}
}

View file

@ -0,0 +1,166 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using MobiusEditor.Interface;
using MobiusEditor.Render;
using System;
using System.Drawing;
namespace MobiusEditor.Model
{
public class BuildingType : ICellOverlapper, ICellOccupier, ITechnoType, IBrowsableType
{
public sbyte ID { get; private set; }
public string Name { get; private set; }
public string DisplayName { get; private set; }
public string Tilename { get; private set; }
public Rectangle OverlapBounds => new Rectangle(Point.Empty, new Size(OccupyMask.GetLength(1), OccupyMask.GetLength(0)));
public bool[,] OccupyMask { get; private set; }
public bool[,] BaseOccupyMask { get; private set; }
public Size Size { get; private set; }
public bool HasBib { get; private set; }
public string OwnerHouse { get; private set; }
public TheaterType[] Theaters { get; private set; }
public bool IsFake { get; private set; }
public bool HasTurret { get; private set; }
public string FactoryOverlay { get; private set; }
public Image Thumbnail { get; set; }
public BuildingType(sbyte id, string name, string textId, bool[,] occupyMask, bool hasBib, string ownerHouse, TheaterType[] theaters, bool isFake, bool hasTurret, string factoryOverlay)
{
ID = id;
Name = isFake ? (name.Substring(0, name.Length - 1) + "f") : name;
DisplayName = Globals.TheGameTextManager[textId];
Tilename = name;
BaseOccupyMask = occupyMask;
Size = new Size(BaseOccupyMask.GetLength(1), BaseOccupyMask.GetLength(0));
HasBib = hasBib;
OwnerHouse = ownerHouse;
Theaters = theaters;
IsFake = isFake;
HasTurret = hasTurret;
FactoryOverlay = factoryOverlay;
if (HasBib)
{
OccupyMask = new bool[BaseOccupyMask.GetLength(0) + 1, BaseOccupyMask.GetLength(1)];
for (var i = 0; i < BaseOccupyMask.GetLength(0) - 1; ++i)
{
for (var j = 0; j < BaseOccupyMask.GetLength(1); ++j)
{
OccupyMask[i, j] = BaseOccupyMask[i, j];
}
}
for (var j = 0; j < OccupyMask.GetLength(1); ++j)
{
OccupyMask[OccupyMask.GetLength(0) - 2, j] = true;
OccupyMask[OccupyMask.GetLength(0) - 1, j] = true;
}
}
else
{
OccupyMask = BaseOccupyMask;
}
}
public BuildingType(sbyte id, string name, string textId, bool[,] occupyMask, bool hasBib, string ownerHouse, bool isFake, bool hasTurret, string factoryOverlay)
: this(id, name, textId, occupyMask, hasBib, ownerHouse, null, isFake, hasTurret, factoryOverlay)
{
}
public BuildingType(sbyte id, string name, string textId, bool[,] occupyMask, bool hasBib, string ownerHouse)
: this(id, name, textId, occupyMask, hasBib, ownerHouse, null, false, false, null)
{
}
public BuildingType(sbyte id, string name, string textId, bool[,] occupyMask, bool hasBib, string ownerHouse, TheaterType[] theaters)
: this(id, name, textId, occupyMask, hasBib, ownerHouse, theaters, false, false, null)
{
}
public BuildingType(sbyte id, string name, string textId, bool[,] occupyMask, bool hasBib, string ownerHouse, bool isFake)
: this(id, name, textId, occupyMask, hasBib, ownerHouse, null, isFake, false, null)
{
}
public BuildingType(sbyte id, string name, string textId, bool[,] occupyMask, bool hasBib, string ownerHouse, bool isFake, bool hasTurret)
: this(id, name, textId, occupyMask, hasBib, ownerHouse, null, isFake, hasTurret, null)
{
}
public override bool Equals(object obj)
{
if (obj is BuildingType)
{
return this == obj;
}
else if (obj is sbyte)
{
return ID == (sbyte)obj;
}
else if (obj is string)
{
return string.Equals(Name, obj as string, StringComparison.OrdinalIgnoreCase);
}
return base.Equals(obj);
}
public override int GetHashCode()
{
return ID.GetHashCode();
}
public override string ToString()
{
return Name;
}
public void Init(GameType gameType, TheaterType theater, HouseType house, DirectionType direction)
{
var mockBuilding = new Building()
{
Type = this,
House = house,
Strength = 256,
Direction = direction
};
var render = MapRenderer.Render(gameType, theater, Point.Empty, Globals.TileSize, Globals.TileScale, mockBuilding);
if (!render.Item1.IsEmpty)
{
var buildingPreview = new Bitmap(render.Item1.Width, render.Item1.Height);
using (var g = Graphics.FromImage(buildingPreview))
{
render.Item2(g);
}
Thumbnail = buildingPreview;
}
}
}
}

View file

@ -0,0 +1,193 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
namespace MobiusEditor.Model
{
public enum FacingType
{
None,
North,
NorthEast,
East,
SouthEast,
South,
SouthWest,
West,
NorthWest
}
public class CellChangedEventArgs<T> : EventArgs
{
public readonly int Cell;
public readonly Point Location;
public readonly T OldValue;
public readonly T Value;
public CellChangedEventArgs(CellMetrics metrics, int cell, T oldValue, T value)
{
Cell = cell;
metrics.GetLocation(cell, out Location);
OldValue = oldValue;
Value = value;
}
public CellChangedEventArgs(CellMetrics metrics, Point location, T oldValue, T value)
{
Location = location;
metrics.GetCell(location, out Cell);
OldValue = oldValue;
Value = value;
}
}
public class CellGrid<T> : IEnumerable<(int Cell, T Value)>, IEnumerable
{
private readonly CellMetrics metrics;
private readonly T[,] cells;
public T this[int x, int y]
{
get => cells[y, x];
set
{
if (!EqualityComparer<T>.Default.Equals(cells[y, x], value))
{
var lastValue = cells[y, x];
cells[y, x] = value;
OnCellChanged(new CellChangedEventArgs<T>(metrics, new Point(x, y), lastValue, cells[y, x]));
}
}
}
public T this[Point location] { get => this[location.X, location.Y]; set => this[location.X, location.Y] = value; }
public T this[int cell] { get => this[cell % metrics.Width, cell / metrics.Width]; set => this[cell % metrics.Width, cell / metrics.Width] = value; }
public Size Size => metrics.Size;
public int Length => metrics.Length;
public event EventHandler<CellChangedEventArgs<T>> CellChanged;
public event EventHandler<EventArgs> Cleared;
public CellGrid(CellMetrics metrics)
{
this.metrics = metrics;
cells = new T[metrics.Height, metrics.Width];
}
public void Clear()
{
Array.Clear(cells, 0, cells.Length);
OnCleared();
}
public T Adjacent(Point location, FacingType facing)
{
return metrics.Adjacent(location, facing, out Point adjacent) ? this[adjacent] : default;
}
public T Adjacent(int cell, FacingType facing)
{
if (!metrics.GetLocation(cell, out Point location))
{
return default;
}
return metrics.Adjacent(location, facing, out Point adjacent) ? this[adjacent] : default;
}
public bool CopyTo(CellGrid<T> other)
{
if (metrics.Length != other.metrics.Length)
{
return false;
}
for (var i = 0; i < metrics.Length; ++i)
{
other[i] = this[i];
}
return true;
}
protected virtual void OnCellChanged(CellChangedEventArgs<T> e)
{
CellChanged?.Invoke(this, e);
}
protected virtual void OnCleared()
{
Cleared?.Invoke(this, new EventArgs());
}
public IEnumerable<(int Cell, T Value)> IntersectsWith(ISet<int> cells)
{
foreach (var i in cells)
{
if (metrics.Contains(i))
{
var cell = this[i];
if (cell != null)
{
yield return (i, cell);
}
}
}
}
public IEnumerable<(int Cell, T Value)> IntersectsWith(ISet<Point> locations)
{
foreach (var location in locations)
{
if (metrics.Contains(location))
{
var cell = this[location];
if (cell != null)
{
metrics.GetCell(location, out int i);
yield return (i, cell);
}
}
}
}
public IEnumerator<(int Cell, T Value)> GetEnumerator()
{
for (var i = 0; i < metrics.Length; ++i)
{
var cell = this[i];
if (cell != null)
{
yield return (i, cell);
}
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View file

@ -0,0 +1,137 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MobiusEditor.Model
{
public class CellMetrics
{
public static readonly FacingType[] AdjacentFacings = new FacingType[] {
FacingType.North, FacingType.NorthEast, FacingType.East, FacingType.SouthEast, FacingType.South, FacingType.SouthWest, FacingType.West, FacingType.NorthWest
};
public int Width { get; private set; }
public int Height { get; private set; }
public Point TopLeft => Point.Empty;
public Size Size => new Size(Width, Height);
public Rectangle Bounds => new Rectangle(TopLeft, Size);
public int Length => Width * Height;
public bool Contains(Point location) => ((location.X >= 0) && (location.X < Width) && (location.Y >= 0) && (location.Y < Height));
public bool Contains(int cell) => ((cell >= 0) && (cell < Length));
public CellMetrics(int width, int height)
{
Width = width;
Height = height;
}
public CellMetrics(Size size)
: this(size.Width, size.Height)
{
}
public bool GetCell(Point location, out int cell)
{
cell = (location.Y * Width) + location.X;
return Contains(location);
}
public bool GetLocation(int cell, out Point location)
{
location = new Point(cell % Width, cell / Width);
return Contains(cell);
}
public bool Adjacent(Point location, FacingType facing, out Point adjacent)
{
adjacent = location;
switch (facing)
{
case FacingType.North:
adjacent.Y--;
break;
case FacingType.NorthEast:
adjacent.X++;
adjacent.Y--;
break;
case FacingType.East:
adjacent.X++;
break;
case FacingType.SouthEast:
adjacent.X++;
adjacent.Y++;
break;
case FacingType.South:
adjacent.Y++;
break;
case FacingType.SouthWest:
adjacent.X--;
adjacent.Y++;
break;
case FacingType.West:
adjacent.X--;
break;
case FacingType.NorthWest:
adjacent.X--;
adjacent.Y--;
break;
}
return Contains(adjacent);
}
public bool Adjacent(int cell, FacingType facing, out int adjacent)
{
if (!GetLocation(cell, out Point location) || !Adjacent(location, facing, out Point adjacentPoint))
{
adjacent = -1;
return false;
}
else
{
return GetCell(adjacentPoint, out adjacent);
}
}
public void Clip(ref Point location)
{
location.X = Math.Max(0, Math.Min(Width - 1, location.X));
location.Y = Math.Max(0, Math.Min(Height - 1, location.Y));
}
public void Clip(ref Point location, Size margin)
{
Clip(ref location, margin, margin);
}
public void Clip(ref Point location, Size topLeftMargin, Size bottomRightMargin)
{
location.X = Math.Max(topLeftMargin.Width, Math.Min(Width - bottomRightMargin.Width - 1, location.X));
location.Y = Math.Max(topLeftMargin.Height, Math.Min(Height - bottomRightMargin.Height - 1, location.Y));
}
}
}

View file

@ -0,0 +1,21 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
namespace MobiusEditor.Model
{
public class CellTrigger
{
public string Trigger { get; set; } = Model.Trigger.None;
}
}

View file

@ -0,0 +1,71 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using System;
namespace MobiusEditor.Model
{
public class DirectionType
{
public byte ID { get; private set; }
public string Name { get; private set; }
public FacingType Facing { get; private set; }
public DirectionType(byte id, string name, FacingType facing)
{
ID = id;
Name = name;
Facing = facing;
}
public DirectionType(byte id, string name)
: this(id, name, FacingType.None)
{
}
public override bool Equals(object obj)
{
if (obj is DirectionType)
{
return this == obj;
}
else if (obj is byte)
{
return ID == (byte)obj;
}
else if (obj is string)
{
return string.Equals(Name, obj as string, StringComparison.OrdinalIgnoreCase);
}
else if (obj is FacingType)
{
return Facing == (FacingType)obj;
}
return base.Equals(obj);
}
public override int GetHashCode()
{
return ID.GetHashCode();
}
public override string ToString()
{
return Name;
}
}
}

View file

@ -0,0 +1,172 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using MobiusEditor.Utility;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
namespace MobiusEditor.Model
{
public struct AlliesMask : IEnumerable<int>
{
public int Value { get; private set; }
public AlliesMask(int value)
{
Value = value;
}
public void Set(int id)
{
if ((id < 0) || (id > 31))
{
throw new ArgumentOutOfRangeException();
}
Value |= (1 << id);
}
public void Clear(int id)
{
if ((id < 0) || (id > 31))
{
throw new ArgumentOutOfRangeException();
}
Value &= ~(1 << id);
}
public override bool Equals(object obj)
{
return Value.Equals(obj);
}
public override int GetHashCode()
{
return Value.GetHashCode();
}
public override string ToString()
{
return Value.ToString();
}
public IEnumerator<int> GetEnumerator()
{
for (int i = 0, mask = 1; i < 32; ++i, mask <<= 1)
{
if ((Value & mask) != 0)
{
yield return i;
}
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public class AlliesMaskTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return (context is MapContext) && (sourceType == typeof(string));
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return (context is MapContext) && (destinationType == typeof(string));
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (!(value is AlliesMask) || !CanConvertTo(context, destinationType))
{
return null;
}
var map = (context as MapContext).Map;
var alliesMask = (AlliesMask)value;
var allies = new List<string>(map.Houses.Length);
foreach (var id in alliesMask)
{
if (map.Houses.Where(h => h.Type.Equals((sbyte)id)).FirstOrDefault() is House house)
{
allies.Add(house.Type.Name);
}
}
return string.Join(",", allies);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (!CanConvertFrom(context, value?.GetType()))
{
return null;
}
var map = (context as MapContext).Instance as Map;
var alliesMask = new AlliesMask(0);
var allies = (value as string).Split(',');
foreach (var ally in allies)
{
if (map.Houses.Where(h => h.Type.Equals(ally)).FirstOrDefault() is House house)
{
alliesMask.Set(house.Type.ID);
}
}
return alliesMask;
}
}
public class House
{
public readonly HouseType Type;
[NonSerializedINIKey]
[DefaultValue(true)]
public bool Enabled { get; set; }
[TypeConverter(typeof(AlliesMaskTypeConverter))]
public AlliesMask Allies { get; set; }
[DefaultValue(150)]
public int MaxBuilding { get; set; }
[DefaultValue(150)]
public int MaxUnit { get; set; }
[DefaultValue("North")]
public string Edge { get; set; }
[DefaultValue(0)]
public int Credits { get; set; } = 0;
public House(HouseType type)
{
Type = type;
Allies = new AlliesMask(1 << Type.ID);
}
}
}

View file

@ -0,0 +1,82 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MobiusEditor.Model
{
public class HouseType
{
public sbyte ID { get; private set; }
public string Name { get; private set; }
public string UnitTeamColor { get; private set; }
public string BuildingTeamColor { get; private set; }
public IDictionary<string, string> OverrideTeamColors { get; private set; }
public HouseType(sbyte id, string name, string unitTeamColor, string buildingTeamColor, params (string type, string teamColor)[] overrideTeamColors)
{
ID = id;
Name = name;
UnitTeamColor = unitTeamColor;
BuildingTeamColor = buildingTeamColor;
OverrideTeamColors = overrideTeamColors.ToDictionary(x => x.type, x => x.teamColor);
}
public HouseType(sbyte id, string name, string teamColor)
: this(id, name, teamColor, teamColor)
{
}
public HouseType(sbyte id, string name)
: this(id, name, null)
{
}
public override bool Equals(object obj)
{
if (obj is HouseType)
{
return this == obj;
}
else if (obj is sbyte)
{
return ID == (sbyte)obj;
}
else if (obj is string)
{
return string.Equals(Name, obj as string, StringComparison.OrdinalIgnoreCase);
}
return base.Equals(obj);
}
public override int GetHashCode()
{
return ID.GetHashCode();
}
public override string ToString()
{
return Name;
}
}
}

View file

@ -0,0 +1,127 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using MobiusEditor.Interface;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace MobiusEditor.Model
{
public enum InfantryStoppingType
{
Center = 0,
UpperLeft = 1,
UpperRight = 2,
LowerLeft = 3,
LowerRight = 4
}
public class Infantry : INotifyPropertyChanged, ICloneable
{
public event PropertyChangedEventHandler PropertyChanged;
public InfantryGroup InfantryGroup { get; set; }
private InfantryType type;
public InfantryType Type { get => type; set => SetField(ref type, value); }
private HouseType house;
public HouseType House { get => house; set => SetField(ref house, value); }
private int strength;
public int Strength { get => strength; set => SetField(ref strength, value); }
private DirectionType direction;
public DirectionType Direction { get => direction; set => SetField(ref direction, value); }
private string mission;
public string Mission { get => mission; set => SetField(ref mission, value); }
private string trigger = Model.Trigger.None;
public string Trigger { get => trigger; set => SetField(ref trigger, value); }
public Color Tint { get; set; } = Color.White;
public Infantry(InfantryGroup infantryGroup)
{
InfantryGroup = infantryGroup;
}
public Infantry Clone()
{
return new Infantry(InfantryGroup)
{
Type = Type,
House = House,
Strength = Strength,
Direction = Direction,
Trigger = Trigger,
Mission = Mission,
};
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
{
return false;
}
field = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
object ICloneable.Clone()
{
return Clone();
}
}
public class InfantryGroup : ICellOverlapper, ICellOccupier
{
private static readonly Point[] stoppingLocations = new Point[Globals.NumInfantryStops];
public Rectangle OverlapBounds => new Rectangle(-1, -1, 3, 3);
public bool[,] OccupyMask => new bool[1, 1] { { true } };
public readonly Infantry[] Infantry = new Infantry[Globals.NumInfantryStops];
static InfantryGroup()
{
stoppingLocations[(int)InfantryStoppingType.Center] = new Point(Globals.PixelWidth / 2, Globals.PixelHeight / 2);
stoppingLocations[(int)InfantryStoppingType.UpperLeft] = new Point(Globals.PixelWidth / 4, Globals.PixelHeight / 4);
stoppingLocations[(int)InfantryStoppingType.UpperRight] = new Point(3 * Globals.PixelWidth / 4, Globals.PixelHeight / 4);
stoppingLocations[(int)InfantryStoppingType.LowerLeft] = new Point(Globals.PixelWidth / 4, 3 * Globals.PixelHeight / 4);
stoppingLocations[(int)InfantryStoppingType.LowerRight] = new Point(3 * Globals.PixelWidth / 4, 3 * Globals.PixelHeight / 4);
}
public static IEnumerable<InfantryStoppingType> ClosestStoppingTypes(Point subPixel)
{
var stoppingDistances = new (InfantryStoppingType type, float dist)[stoppingLocations.Length];
for (int i = 0; i < stoppingDistances.Length; ++i)
{
stoppingDistances[i] = ((InfantryStoppingType)i, new Vector2(subPixel.X - stoppingLocations[i].X, subPixel.Y - stoppingLocations[i].Y).LengthSquared());
}
return stoppingDistances.OrderBy(sd => sd.dist).Select(sd => sd.type);
}
}
}

View file

@ -0,0 +1,100 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using MobiusEditor.Interface;
using MobiusEditor.Render;
using MobiusEditor.Utility;
using System;
using System.Drawing;
namespace MobiusEditor.Model
{
public class InfantryType : ITechnoType, IBrowsableType
{
public sbyte ID { get; private set; }
public string Name { get; private set; }
public string DisplayName { get; private set; }
public string OwnerHouse { get; private set; }
public Size RenderSize { get; set; }
public Image Thumbnail { get; set; }
public InfantryType(sbyte id, string name, string textId, string ownerHouse)
{
ID = id;
Name = name;
DisplayName = Globals.TheGameTextManager[textId];
OwnerHouse = ownerHouse;
}
public InfantryType(sbyte id, string name, string textId)
: this(id, name, textId, null)
{
}
public override bool Equals(object obj)
{
if (obj is InfantryType)
{
return this == obj;
}
else if (obj is sbyte)
{
return ID == (sbyte)obj;
}
else if (obj is string)
{
return string.Equals(Name, obj as string, StringComparison.OrdinalIgnoreCase);
}
return base.Equals(obj);
}
public override int GetHashCode()
{
return ID.GetHashCode();
}
public override string ToString()
{
return Name;
}
public void Init(GameType gameType, TheaterType theater, HouseType house, DirectionType direction)
{
if (Globals.TheTilesetManager.GetTileData(theater.Tilesets, Name, 4, out Tile tile))
{
RenderSize = new Size(tile.Image.Width / Globals.TileScale, tile.Image.Height / Globals.TileScale);
}
var mockInfantry = new Infantry(null)
{
Type = this,
House = house,
Strength = 256,
Direction = direction
};
var infantryThumbnail = new Bitmap(Globals.TileWidth, Globals.TileHeight);
using (var g = Graphics.FromImage(infantryThumbnail))
{
MapRenderer.Render(theater, Point.Empty, Globals.TileSize, mockInfantry, InfantryStoppingType.Center).Item2(g);
}
Thumbnail = infantryThumbnail;
}
}
}

View file

@ -0,0 +1,681 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using MobiusEditor.Interface;
using MobiusEditor.Render;
using MobiusEditor.Utility;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using TGASharpLib;
namespace MobiusEditor.Model
{
[Flags]
public enum MapLayerFlag
{
None = 0,
Basic = 1 << 0,
Map = 1 << 1,
Template = 1 << 2,
Terrain = 1 << 3,
Resources = 1 << 4,
Walls = 1 << 5,
Overlay = 1 << 6,
Smudge = 1 << 7,
Waypoints = 1 << 8,
CellTriggers = 1 << 9,
Houses = 1 << 10,
Infantry = 1 << 11,
Units = 1 << 12,
Buildings = 1 << 13,
Boundaries = 1 << 14,
TechnoTriggers = 1 << 15,
OverlayAll = Resources | Walls | Overlay,
Technos = Terrain | Walls | Infantry | Units | Buildings,
All = int.MaxValue
}
public class MapContext : ITypeDescriptorContext
{
public IContainer Container { get; private set; }
public object Instance { get; private set; }
public PropertyDescriptor PropertyDescriptor { get; private set; }
public Map Map => Instance as Map;
public readonly bool FractionalPercentages;
public MapContext(Map map, bool fractionalPercentages)
{
Instance = map;
FractionalPercentages = fractionalPercentages;
}
public object GetService(Type serviceType) => null;
public void OnComponentChanged() { }
public bool OnComponentChanging() => true;
}
public class Map : ICloneable
{
private int updateCount = 0;
private bool updating = false;
private IDictionary<MapLayerFlag, ISet<Point>> invalidateLayers = new Dictionary<MapLayerFlag, ISet<Point>>();
private bool invalidateOverlappers;
public readonly BasicSection BasicSection;
public readonly MapSection MapSection = new MapSection();
public readonly BriefingSection BriefingSection = new BriefingSection();
public readonly SteamSection SteamSection = new SteamSection();
public TheaterType Theater { get => MapSection.Theater; set => MapSection.Theater = value; }
public Point TopLeft
{
get => new Point(MapSection.X, MapSection.Y);
set { MapSection.X = value.X; MapSection.Y = value.Y; }
}
public Size Size
{
get => new Size(MapSection.Width, MapSection.Height);
set { MapSection.Width = value.Width; MapSection.Height = value.Height; }
}
public Rectangle Bounds
{
get => new Rectangle(TopLeft, Size);
set { MapSection.X = value.Left; MapSection.Y = value.Top; MapSection.Width = value.Width; MapSection.Height = value.Height; }
}
public readonly Type HouseType;
public readonly HouseType[] HouseTypes;
public readonly List<TheaterType> TheaterTypes;
public readonly List<TemplateType> TemplateTypes;
public readonly List<TerrainType> TerrainTypes;
public readonly List<OverlayType> OverlayTypes;
public readonly List<SmudgeType> SmudgeTypes;
public readonly string[] EventTypes;
public readonly string[] ActionTypes;
public readonly string[] MissionTypes;
public readonly List<DirectionType> DirectionTypes;
public readonly List<InfantryType> InfantryTypes;
public readonly List<UnitType> UnitTypes;
public readonly List<BuildingType> BuildingTypes;
public readonly string[] TeamMissionTypes;
public readonly CellMetrics Metrics;
public readonly CellGrid<Template> Templates;
public readonly CellGrid<Overlay> Overlay;
public readonly CellGrid<Smudge> Smudge;
public readonly OccupierSet<ICellOccupier> Technos;
public readonly OccupierSet<ICellOccupier> Buildings;
public readonly OverlapperSet<ICellOverlapper> Overlappers;
public readonly Waypoint[] Waypoints;
public readonly CellGrid<CellTrigger> CellTriggers;
public readonly ObservableCollection<Trigger> Triggers;
public readonly List<TeamType> TeamTypes;
public House[] Houses;
public readonly List<string> MovieTypes;
public int TiberiumOrGoldValue { get; set; }
public int GemValue { get; set; }
public int TotalResources
{
get
{
int totalResources = 0;
foreach (var (cell, value) in Overlay)
{
if (value.Type.IsResource)
{
totalResources += (value.Icon + 1) * (value.Type.IsGem ? GemValue : TiberiumOrGoldValue);
}
}
return totalResources;
}
}
public Map(BasicSection basicSection, TheaterType theater, Size cellSize, Type houseType,
IEnumerable<HouseType> houseTypes, IEnumerable<TheaterType> theaterTypes, IEnumerable<TemplateType> templateTypes,
IEnumerable<TerrainType> terrainTypes, IEnumerable<OverlayType> overlayTypes, IEnumerable<SmudgeType> smudgeTypes,
IEnumerable<string> eventTypes, IEnumerable<string> actionTypes, IEnumerable<string> missionTypes,
IEnumerable<DirectionType> directionTypes, IEnumerable<InfantryType> infantryTypes, IEnumerable<UnitType> unitTypes,
IEnumerable<BuildingType> buildingTypes, IEnumerable<string> teamMissionTypes, IEnumerable<Waypoint> waypoints,
IEnumerable<string> movieTypes)
{
BasicSection = basicSection;
HouseType = houseType;
HouseTypes = houseTypes.ToArray();
TheaterTypes = new List<TheaterType>(theaterTypes);
TemplateTypes = new List<TemplateType>(templateTypes);
TerrainTypes = new List<TerrainType>(terrainTypes);
OverlayTypes = new List<OverlayType>(overlayTypes);
SmudgeTypes = new List<SmudgeType>(smudgeTypes);
EventTypes = eventTypes.ToArray();
ActionTypes = actionTypes.ToArray();
MissionTypes = missionTypes.ToArray();
DirectionTypes = new List<DirectionType>(directionTypes);
InfantryTypes = new List<InfantryType>(infantryTypes);
UnitTypes = new List<UnitType>(unitTypes);
BuildingTypes = new List<BuildingType>(buildingTypes);
TeamMissionTypes = teamMissionTypes.ToArray();
MovieTypes = new List<string>(movieTypes);
Metrics = new CellMetrics(cellSize);
Templates = new CellGrid<Template>(Metrics);
Overlay = new CellGrid<Overlay>(Metrics);
Smudge = new CellGrid<Smudge>(Metrics);
Technos = new OccupierSet<ICellOccupier>(Metrics);
Buildings = new OccupierSet<ICellOccupier>(Metrics);
Overlappers = new OverlapperSet<ICellOverlapper>(Metrics);
Triggers = new ObservableCollection<Trigger>();
TeamTypes = new List<TeamType>();
Houses = HouseTypes.Select(t => { var h = (House)Activator.CreateInstance(HouseType, t); h.SetDefault(); return h; }).ToArray();
Waypoints = waypoints.ToArray();
CellTriggers = new CellGrid<CellTrigger>(Metrics);
MapSection.SetDefault();
BriefingSection.SetDefault();
SteamSection.SetDefault();
Templates.Clear();
Overlay.Clear();
Smudge.Clear();
Technos.Clear();
Overlappers.Clear();
CellTriggers.Clear();
TopLeft = new Point(1, 1);
Size = Metrics.Size - new Size(2, 2);
Theater = theater;
Overlay.CellChanged += Overlay_CellChanged;
Technos.OccupierAdded += Technos_OccupierAdded;
Technos.OccupierRemoved += Technos_OccupierRemoved;
Buildings.OccupierAdded += Buildings_OccupierAdded;
Buildings.OccupierRemoved += Buildings_OccupierRemoved;
Triggers.CollectionChanged += Triggers_CollectionChanged;
}
public void BeginUpdate()
{
updateCount++;
}
public void EndUpdate()
{
if (--updateCount == 0)
{
Update();
}
}
public void InitTheater(GameType gameType)
{
foreach (var templateType in TemplateTypes)
{
templateType.Init(Theater);
}
foreach (var smudgeType in SmudgeTypes)
{
smudgeType.Init(Theater);
}
foreach (var overlayType in OverlayTypes)
{
overlayType.Init(Theater);
}
foreach (var terrainType in TerrainTypes)
{
terrainType.Init(Theater);
}
foreach (var infantryType in InfantryTypes)
{
infantryType.Init(gameType, Theater, HouseTypes.Where(h => h.Equals(infantryType.OwnerHouse)).FirstOrDefault(), DirectionTypes.Where(d => d.Facing == FacingType.South).First());
}
foreach (var unitType in UnitTypes)
{
unitType.Init(gameType, Theater, HouseTypes.Where(h => h.Equals(unitType.OwnerHouse)).FirstOrDefault(), DirectionTypes.Where(d => d.Facing == FacingType.North).First());
}
foreach (var buildingType in BuildingTypes)
{
buildingType.Init(gameType, Theater, HouseTypes.Where(h => h.Equals(buildingType.OwnerHouse)).FirstOrDefault(), DirectionTypes.Where(d => d.Facing == FacingType.North).First());
}
}
private void Update()
{
updating = true;
if (invalidateLayers.TryGetValue(MapLayerFlag.Resources, out ISet<Point> locations))
{
UpdateResourceOverlays(locations);
}
if (invalidateLayers.TryGetValue(MapLayerFlag.Walls, out locations))
{
UpdateWallOverlays(locations);
}
if (invalidateOverlappers)
{
Overlappers.Clear();
foreach (var (location, techno) in Technos)
{
if (techno is ICellOverlapper)
{
Overlappers.Add(location, techno as ICellOverlapper);
}
}
}
invalidateLayers.Clear();
invalidateOverlappers = false;
updating = false;
}
private void UpdateResourceOverlays(ISet<Point> locations)
{
var tiberiumCounts = new int[] { 0, 1, 3, 4, 6, 7, 8, 10, 11 };
var gemCounts = new int[] { 0, 0, 0, 1, 1, 1, 2, 2, 2 };
foreach (var (cell, overlay) in Overlay.IntersectsWith(locations).Where(o => o.Value.Type.IsResource))
{
int count = 0;
foreach (var facing in CellMetrics.AdjacentFacings)
{
var adjacentTiberium = Overlay.Adjacent(cell, facing);
if (adjacentTiberium?.Type.IsResource ?? false)
{
count++;
}
}
overlay.Icon = overlay.Type.IsGem ? gemCounts[count] : tiberiumCounts[count];
}
}
private void UpdateWallOverlays(ISet<Point> locations)
{
foreach (var (cell, overlay) in Overlay.IntersectsWith(locations).Where(o => o.Value.Type.IsWall))
{
var northWall = Overlay.Adjacent(cell, FacingType.North);
var eastWall = Overlay.Adjacent(cell, FacingType.East);
var southWall = Overlay.Adjacent(cell, FacingType.South);
var westWall = Overlay.Adjacent(cell, FacingType.West);
int icon = 0;
if (northWall?.Type == overlay.Type)
{
icon |= 1;
}
if (eastWall?.Type == overlay.Type)
{
icon |= 2;
}
if (southWall?.Type == overlay.Type)
{
icon |= 4;
}
if (westWall?.Type == overlay.Type)
{
icon |= 8;
}
overlay.Icon = icon;
}
}
private void RemoveBibs(Building building)
{
var bibCells = Smudge.IntersectsWith(building.BibCells).Where(x => (x.Value.Type.Flag & SmudgeTypeFlag.Bib) != SmudgeTypeFlag.None).Select(x => x.Cell).ToArray();
foreach (var cell in bibCells)
{
Smudge[cell] = null;
}
building.BibCells.Clear();
}
private void AddBibs(Point location, Building building)
{
if (!building.Type.HasBib)
{
return;
}
var bib1Type = SmudgeTypes.Where(t => t.Flag == SmudgeTypeFlag.Bib1).FirstOrDefault();
var bib2Type = SmudgeTypes.Where(t => t.Flag == SmudgeTypeFlag.Bib2).FirstOrDefault();
var bib3Type = SmudgeTypes.Where(t => t.Flag == SmudgeTypeFlag.Bib3).FirstOrDefault();
SmudgeType bibType = null;
switch (building.Type.Size.Width)
{
case 2:
bibType = bib3Type;
break;
case 3:
bibType = bib2Type;
break;
case 4:
bibType = bib1Type;
break;
}
if (bibType != null)
{
int icon = 0;
for (var y = 0; y < bibType.Size.Height; ++y)
{
for (var x = 0; x < bibType.Size.Width; ++x, ++icon)
{
if (Metrics.GetCell(new Point(location.X + x, location.Y + building.Type.Size.Height + y - 1), out int subCell))
{
Smudge[subCell] = new Smudge
{
Type = bibType,
Icon = icon,
Data = 0,
Tint = building.Tint
};
building.BibCells.Add(subCell);
}
}
}
}
}
public Map Clone()
{
var map = new Map(BasicSection, Theater, Metrics.Size, HouseType,
HouseTypes, TheaterTypes, TemplateTypes, TerrainTypes, OverlayTypes, SmudgeTypes,
EventTypes, ActionTypes, MissionTypes, DirectionTypes, InfantryTypes, UnitTypes,
BuildingTypes, TeamMissionTypes, Waypoints, MovieTypes)
{
TopLeft = TopLeft,
Size = Size
};
map.BeginUpdate();
MapSection.CopyTo(map.MapSection);
BriefingSection.CopyTo(map.BriefingSection);
SteamSection.CopyTo(map.SteamSection);
Templates.CopyTo(map.Templates);
Overlay.CopyTo(map.Overlay);
Smudge.CopyTo(map.Smudge);
CellTriggers.CopyTo(map.CellTriggers);
Array.Copy(Houses, map.Houses, map.Houses.Length);
foreach (var trigger in Triggers)
{
map.Triggers.Add(trigger);
}
foreach (var (location, occupier) in Technos)
{
if (occupier is InfantryGroup infantryGroup)
{
var newInfantryGroup = new InfantryGroup();
Array.Copy(infantryGroup.Infantry, newInfantryGroup.Infantry, newInfantryGroup.Infantry.Length);
map.Technos.Add(location, newInfantryGroup);
}
else if (!(occupier is Building))
{
map.Technos.Add(location, occupier);
}
}
foreach (var (location, building) in Buildings)
{
map.Buildings.Add(location, building);
}
map.TeamTypes.AddRange(TeamTypes);
map.EndUpdate();
return map;
}
public TGA GeneratePreview(Size previewSize, bool sharpen)
{
var mapBounds = new Rectangle(
Bounds.Left * Globals.OriginalTileWidth,
Bounds.Top * Globals.OriginalTileHeight,
Bounds.Width * Globals.OriginalTileWidth,
Bounds.Height * Globals.OriginalTileHeight
);
var previewScale = Math.Min(previewSize.Width / (float)mapBounds.Width, previewSize.Height / (float)mapBounds.Height);
var scaledSize = new Size((int)(previewSize.Width / previewScale), (int)(previewSize.Height / previewScale));
using (var fullBitmap = new Bitmap(Metrics.Width * Globals.OriginalTileWidth, Metrics.Height * Globals.OriginalTileHeight))
using (var croppedBitmap = new Bitmap(previewSize.Width, previewSize.Height))
{
var locations = Bounds.Points().ToHashSet();
using (var g = Graphics.FromImage(fullBitmap))
{
MapRenderer.Render(GameType.None, this, g, locations, MapLayerFlag.Template | MapLayerFlag.Resources, 1);
}
using (var g = Graphics.FromImage(croppedBitmap))
{
Matrix transform = new Matrix();
transform.Scale(previewScale, previewScale);
transform.Translate((scaledSize.Width - mapBounds.Width) / 2, (scaledSize.Height - mapBounds.Height) / 2);
g.Transform = transform;
g.Clear(Color.Black);
g.DrawImage(fullBitmap, new Rectangle(0, 0, mapBounds.Width, mapBounds.Height), mapBounds, GraphicsUnit.Pixel);
}
fullBitmap.Dispose();
if (sharpen)
{
using (var sharpenedImage = croppedBitmap.Sharpen(1.0f))
{
croppedBitmap.Dispose();
return TGA.FromBitmap(sharpenedImage);
}
}
else
{
return TGA.FromBitmap(croppedBitmap);
}
}
}
public TGA GenerateMapPreview()
{
return GeneratePreview(Globals.MapPreviewSize, false);
}
public TGA GenerateWorkshopPreview()
{
return GeneratePreview(Globals.WorkshopPreviewSize, true);
}
object ICloneable.Clone()
{
return Clone();
}
private void Overlay_CellChanged(object sender, CellChangedEventArgs<Overlay> e)
{
if (e.OldValue?.Type.IsWall ?? false)
{
Buildings.Remove(e.OldValue);
}
if (e.Value?.Type.IsWall ?? false)
{
Buildings.Add(e.Location, e.Value);
}
if (updating)
{
return;
}
foreach (var overlay in new Overlay[] { e.OldValue, e.Value })
{
if (overlay == null)
{
continue;
}
MapLayerFlag layer = MapLayerFlag.None;
if (overlay.Type.IsResource)
{
layer = MapLayerFlag.Resources;
}
else if (overlay.Type.IsWall)
{
layer = MapLayerFlag.Walls;
}
else
{
continue;
}
if (!invalidateLayers.TryGetValue(layer, out ISet<Point> locations))
{
locations = new HashSet<Point>();
invalidateLayers[layer] = locations;
}
locations.UnionWith(Rectangle.Inflate(new Rectangle(e.Location, new Size(1, 1)), 1, 1).Points());
}
if (updateCount == 0)
{
Update();
}
}
private void Technos_OccupierAdded(object sender, OccupierAddedEventArgs<ICellOccupier> e)
{
if (e.Occupier is ICellOverlapper overlapper)
{
if (updateCount == 0)
{
Overlappers.Add(e.Location, overlapper);
}
else
{
invalidateOverlappers = true;
}
}
}
private void Technos_OccupierRemoved(object sender, OccupierRemovedEventArgs<ICellOccupier> e)
{
if (e.Occupier is ICellOverlapper overlapper)
{
if (updateCount == 0)
{
Overlappers.Remove(overlapper);
}
else
{
invalidateOverlappers = true;
}
}
}
private void Buildings_OccupierAdded(object sender, OccupierAddedEventArgs<ICellOccupier> e)
{
if (e.Occupier is Building building)
{
Technos.Add(e.Location, e.Occupier, building.Type.BaseOccupyMask);
AddBibs(e.Location, building);
}
else
{
Technos.Add(e.Location, e.Occupier);
}
}
private void Buildings_OccupierRemoved(object sender, OccupierRemovedEventArgs<ICellOccupier> e)
{
if (e.Occupier is Building building)
{
RemoveBibs(building);
}
Technos.Remove(e.Occupier);
}
private void Triggers_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
foreach (var (_, building) in Buildings.OfType<Building>())
{
if (!string.IsNullOrEmpty(building.Trigger))
{
if (Triggers.Where(t => building.Trigger.Equals(t.Name)).FirstOrDefault() == null)
{
building.Trigger = Trigger.None;
}
}
}
}
}
}

View file

@ -0,0 +1,96 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
namespace MobiusEditor.Model
{
public class TheaterTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return (context is MapContext) && (sourceType == typeof(string));
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return (context is MapContext) && (destinationType == typeof(string));
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (!(value is TheaterType) || !CanConvertTo(context, destinationType))
{
return null;
}
return (value as TheaterType)?.Name;
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (!CanConvertFrom(context, value?.GetType()))
{
return null;
}
var map = (context as MapContext).Map;
return map.TheaterTypes.Where(t => t.Equals(value)).FirstOrDefault() ?? map.TheaterTypes.First();
}
}
public class MapSection : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int x;
[DefaultValue(0)]
public int X { get => x; set => SetField(ref x, value); }
private int y;
[DefaultValue(0)]
public int Y { get => y; set => SetField(ref y, value); }
private int width;
[DefaultValue(0)]
public int Width { get => width; set => SetField(ref width, value); }
private int height;
[DefaultValue(0)]
public int Height { get => height; set => SetField(ref height, value); }
private TheaterType theater;
[TypeConverter(typeof(TheaterTypeConverter))]
[DefaultValue(null)]
public TheaterType Theater { get => theater; set => SetField(ref theater, value); }
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
{
return false;
}
field = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

View file

@ -0,0 +1,250 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using MobiusEditor.Interface;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
namespace MobiusEditor.Model
{
public class OccupierAddedEventArgs<T> : EventArgs
{
public readonly int Cell;
public readonly Point Location;
public readonly T Occupier;
public OccupierAddedEventArgs(CellMetrics metrics, int cell, T occupier)
{
Cell = cell;
metrics.GetLocation(cell, out Location);
Occupier = occupier;
}
public OccupierAddedEventArgs(CellMetrics metrics, Point location, T occupier)
{
Location = location;
metrics.GetCell(location, out Cell);
Occupier = occupier;
}
}
public class OccupierRemovedEventArgs<T> : EventArgs
{
public readonly int Cell;
public readonly Point Location;
public readonly T Occupier;
public OccupierRemovedEventArgs(CellMetrics metrics, int cell, T occupier)
{
Cell = cell;
metrics.GetLocation(cell, out Location);
Occupier = occupier;
}
public OccupierRemovedEventArgs(CellMetrics metrics, Point location, T occupier)
{
Location = location;
metrics.GetCell(location, out Cell);
Occupier = occupier;
}
}
public class OccupierSet<T> : IEnumerable<(Point Location, T Occupier)>, IEnumerable where T : class, ICellOccupier
{
private readonly CellMetrics metrics;
private readonly Dictionary<T, Point> occupiers = new Dictionary<T, Point>();
private readonly T[,] occupierCells;
public T this[Point location] => this[location.X, location.Y];
public T this[int x, int y] => Contains(x, y) ? occupierCells[y, x] : null;
public T this[int cell] => metrics.GetLocation(cell, out Point location) ? this[location] : null;
public Point? this[T occupier] => occupiers.ContainsKey(occupier) ? occupiers[occupier] : default;
public IEnumerable<T> Occupiers => occupiers.Keys;
public event EventHandler<OccupierAddedEventArgs<T>> OccupierAdded;
public event EventHandler<OccupierRemovedEventArgs<T>> OccupierRemoved;
public event EventHandler<EventArgs> Cleared;
public OccupierSet(CellMetrics metrics)
{
this.metrics = metrics;
occupierCells = new T[metrics.Height, metrics.Width];
}
public bool CanAdd(Point location, T occupier, bool[,] occupyMask)
{
if ((occupier == null) || Contains(occupier))
{
return false;
}
var occupyPoints = GetOccupyPoints(location, occupyMask).ToArray();
return !occupyPoints.Any(p => !Contains(p) || (this[p] != null));
}
public bool CanAdd(int x, int y, T occupier, bool[,] occupyMask) => CanAdd(new Point(x, y), occupier, occupyMask);
public bool CanAdd(int cell, T occupier, bool[,] occupyMask) => metrics.GetLocation(cell, out Point location) ? CanAdd(location, occupier, occupyMask) : false;
public bool CanAdd(Point location, T occupier) => (occupier != null) ? CanAdd(location, occupier, occupier.OccupyMask) : false;
public bool CanAdd(int x, int y, T occupier) => (occupier != null) ? CanAdd(x, y, occupier, occupier.OccupyMask) : false;
public bool CanAdd(int cell, T occupier) => (occupier != null) ? CanAdd(cell, occupier, occupier.OccupyMask) : false;
public bool Add(Point location, T occupier, bool[,] occupyMask)
{
if (!DoAdd(location, occupier, occupyMask))
{
return false;
}
OnOccupierAdded(new OccupierAddedEventArgs<T>(metrics, location, occupier));
return true;
}
public bool Add(int x, int y, T occupier, bool[,] occupyMask) => Add(new Point(x, y), occupier, occupyMask);
public bool Add(int cell, T occupier, bool[,] occupyMask) => metrics.GetLocation(cell, out Point location) ? Add(location, occupier, occupyMask) : false;
public bool Add(Point location, T occupier) => (occupier != null) ? Add(location, occupier, occupier.OccupyMask) : false;
public bool Add(int x, int y, T occupier) => (occupier != null) ? Add(x, y, occupier, occupier.OccupyMask) : false;
public bool Add(int cell, T occupier) => (occupier != null) ? Add(cell, occupier, occupier.OccupyMask) : false;
public void Clear()
{
occupiers.Clear();
Array.Clear(occupierCells, 0, occupierCells.Length);
OnCleared();
}
public bool Contains(int x, int y) => ((x >= 0) && (x < occupierCells.GetLength(1)) && (y >= 0) && (y < occupierCells.GetLength(0)));
public bool Contains(Point location) => Contains(location.X, location.Y);
public bool Contains(int cell) => metrics.GetLocation(cell, out Point location) ? Contains(location) : false;
public bool Contains(T occupier) => occupiers.ContainsKey(occupier);
public IEnumerator<(Point Location, T Occupier)> GetEnumerator() => occupiers.Select(kv => (kv.Value, kv.Key)).GetEnumerator();
public bool Remove(T occupier)
{
var oldLocation = this[occupier];
if (!DoRemove(occupier))
{
return false;
}
OnOccupierRemoved(new OccupierRemovedEventArgs<T>(metrics, oldLocation.Value, occupier));
return true;
}
public bool Remove(Point location) => Remove(this[location]);
public bool Remove(int x, int y) => Remove(new Point(x, y));
public bool Remove(int cell) => metrics.GetLocation(cell, out Point location) ? Remove(location) : false;
public IEnumerable<(Point Location, U Occupier)> OfType<U>() where U : T => this.Where(i => i.Occupier is U).Select(i => (i.Location, (U)i.Occupier));
protected virtual void OnOccupierAdded(OccupierAddedEventArgs<T> e)
{
OccupierAdded?.Invoke(this, e);
}
protected virtual void OnOccupierRemoved(OccupierRemovedEventArgs<T> e)
{
OccupierRemoved?.Invoke(this, e);
}
protected virtual void OnCleared()
{
Cleared?.Invoke(this, new EventArgs());
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
private bool DoAdd(Point location, T occupier, bool[,] occupyMask)
{
if ((occupier == null) || Contains(occupier))
{
return false;
}
var occupyPoints = GetOccupyPoints(location, occupyMask).ToArray();
if (occupyPoints.Any(p => !Contains(p) || (this[p] != null)))
{
return false;
}
occupiers[occupier] = location;
foreach (var p in occupyPoints)
{
occupierCells[p.Y, p.X] = occupier;
}
return true;
}
private bool DoRemove(T occupier)
{
if ((occupier == null) || !occupiers.TryGetValue(occupier, out Point location))
{
return false;
}
occupiers.Remove(occupier);
for (var y = location.Y; y < metrics.Height; ++y)
{
for (var x = location.X; x < metrics.Width; ++x)
{
if (occupierCells[y, x] == occupier)
{
occupierCells[y, x] = null;
}
}
}
return true;
}
private static IEnumerable<Point> GetOccupyPoints(Point location, bool[,] occupyMask)
{
for (var y = 0; y < occupyMask.GetLength(0); ++y)
{
for (var x = 0; x < occupyMask.GetLength(1); ++x)
{
if (occupyMask[y, x])
{
yield return location + new Size(x, y);
}
}
}
}
private static IEnumerable<Point> GetOccupyPoints(Point location, T occupier) => GetOccupyPoints(location, occupier.OccupyMask);
}
}

View file

@ -0,0 +1,107 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using MobiusEditor.Interface;
using MobiusEditor.Utility;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
namespace MobiusEditor.Model
{
public class OverlapperSet<T> : IEnumerable<(Point Location, T Overlapper)>, IEnumerable where T : class, ICellOverlapper
{
private readonly CellMetrics metrics;
private readonly Dictionary<T, Rectangle> overlappers = new Dictionary<T, Rectangle>();
public Rectangle? this[T overlapper] => Contains(overlapper) ? overlappers[overlapper] : default;
public IEnumerable<T> Overlappers => overlappers.Keys;
public OverlapperSet(CellMetrics metrics)
{
this.metrics = metrics;
}
public bool Add(Point location, T overlapper)
{
if ((overlapper == null) || Contains(overlapper))
{
return false;
}
var rectangle = overlapper.OverlapBounds;
rectangle.Offset(location);
overlappers[overlapper] = rectangle;
return true;
}
public bool Add(int x, int y, T occupier) => Add(new Point(x, y), occupier);
public bool Add(int cell, T overlapper) => metrics.GetLocation(cell, out Point location) ? Add(location, overlapper) : false;
public void Clear() => overlappers.Clear();
public bool Contains(T occupier) => overlappers.ContainsKey(occupier);
public void CopyTo(OverlapperSet<T> other)
{
foreach (var (Location, Occupier) in this)
{
other.Add(Location, Occupier);
}
}
public IEnumerator<(Point Location, T Overlapper)> GetEnumerator() => overlappers.Select(kv => (kv.Value.Location, kv.Key)).GetEnumerator();
public bool Remove(T overlapper)
{
if ((overlapper == null) || !overlappers.TryGetValue(overlapper, out Rectangle overlapRect))
{
return false;
}
overlappers.Remove(overlapper);
return true;
}
public ISet<Point> Overlaps(IEnumerable<Rectangle> rectangles)
{
var rectangleSet = new HashSet<Rectangle>(rectangles);
while (true)
{
var count = rectangleSet.Count;
var overlap = overlappers.Values.Where(x => rectangleSet.Any(y => x.IntersectsWith(y))).ToArray();
rectangleSet.UnionWith(overlap);
if (rectangleSet.Count == count)
{
break;
}
}
return rectangleSet.SelectMany(x => x.Points()).ToHashSet();
}
public ISet<Point> Overlaps(Rectangle rectangle) => Overlaps(rectangle.Yield());
public ISet<Point> Overlaps(IEnumerable<Point> points) => Overlaps(points.Select(p => new Rectangle(p, new Size(1, 1))));
public ISet<Point> Overlaps(Point point) => Overlaps(point.Yield());
public IEnumerable<(Point Location, U Overlapper)> OfType<U>() where U : T => this.Where(i => i.Overlapper is U).Select(i => (i.Location, (U)i.Overlapper));
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}

View file

@ -0,0 +1,32 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using MobiusEditor.Interface;
using System.Drawing;
namespace MobiusEditor.Model
{
public class Overlay : ICellOccupier
{
public OverlayType Type { get; set; }
public int Icon { get; set; }
public Size OverlapSize => new Size(1, 1);
public bool[,] OccupyMask => Type.OccupyMask;
public Color Tint { get; set; } = Color.White;
}
}

View file

@ -0,0 +1,128 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using MobiusEditor.Interface;
using MobiusEditor.Utility;
using System;
using System.Drawing;
namespace MobiusEditor.Model
{
[Flags]
public enum OverlayTypeFlag
{
None = 0,
TiberiumOrGold = (1 << 0),
Gems = (1 << 1),
Wall = (1 << 2),
Crate = (1 << 3),
Flag = (1 << 4),
}
public class OverlayType : ICellOccupier, IBrowsableType
{
public sbyte ID { get; private set; }
public string Name { get; private set; }
public string DisplayName { get; private set; }
public TheaterType[] Theaters { get; private set; }
public OverlayTypeFlag Flag { get; private set; }
public Image Thumbnail { get; set; }
public bool[,] OccupyMask => new bool[1, 1] { { true } };
public bool IsResource => (Flag & (OverlayTypeFlag.TiberiumOrGold | OverlayTypeFlag.Gems)) != OverlayTypeFlag.None;
public bool IsTiberiumOrGold => (Flag & OverlayTypeFlag.TiberiumOrGold) != OverlayTypeFlag.None;
public bool IsGem => (Flag & OverlayTypeFlag.Gems) != OverlayTypeFlag.None;
public bool IsWall => (Flag & OverlayTypeFlag.Wall) != OverlayTypeFlag.None;
public bool IsCrate => (Flag & OverlayTypeFlag.Crate) != OverlayTypeFlag.None;
public bool IsFlag => (Flag & OverlayTypeFlag.Flag) != OverlayTypeFlag.None;
public bool IsPlaceable => (Flag & ~OverlayTypeFlag.Crate) == OverlayTypeFlag.None;
public OverlayType(sbyte id, string name, string textId, TheaterType[] theaters, OverlayTypeFlag flag)
{
ID = id;
Name = name;
DisplayName = Globals.TheGameTextManager[textId];
Theaters = theaters;
Flag = flag;
}
public OverlayType(sbyte id, string name, string textId, OverlayTypeFlag flag)
: this(id, name, textId, null, flag)
{
}
public OverlayType(sbyte id, string name, string textId, TheaterType[] theaters)
: this(id, name, textId, theaters, OverlayTypeFlag.None)
{
}
public OverlayType(sbyte id, string name, OverlayTypeFlag flag)
: this(id, name, name, null, flag)
{
}
public OverlayType(sbyte id, string name, string textId)
: this(id, name, textId, null, OverlayTypeFlag.None)
{
}
public override bool Equals(object obj)
{
if (obj is OverlayType)
{
return this == obj;
}
else if (obj is sbyte)
{
return ID == (sbyte)obj;
}
else if (obj is string)
{
return string.Equals(Name, obj as string, StringComparison.OrdinalIgnoreCase);
}
return base.Equals(obj);
}
public override int GetHashCode()
{
return ID.GetHashCode();
}
public override string ToString()
{
return Name;
}
public void Init(TheaterType theater)
{
if (Globals.TheTilesetManager.GetTileData(theater.Tilesets, Name, 0, out Tile tile))
{
Thumbnail = new Bitmap(tile.Image, tile.Image.Width, tile.Image.Height);
}
}
}
}

View file

@ -0,0 +1,30 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using MobiusEditor.Interface;
using System.Drawing;
namespace MobiusEditor.Model
{
public class Smudge
{
public SmudgeType Type { get; set; }
public int Icon { get; set; }
public int Data { get; set; }
public Color Tint { get; set; } = Color.White;
}
}

View file

@ -0,0 +1,116 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using MobiusEditor.Interface;
using MobiusEditor.Utility;
using System;
using System.Drawing;
namespace MobiusEditor.Model
{
[Flags]
public enum SmudgeTypeFlag
{
None = 0,
Bib = 1,
Bib1 = 3,
Bib2 = 5,
Bib3 = 9,
}
public class SmudgeType : IBrowsableType
{
public sbyte ID { get; private set; }
public string Name { get; private set; }
public string DisplayName => Name;
public Size Size { get; set; }
public SmudgeTypeFlag Flag { get; private set; }
public Size RenderSize { get; set; }
public Image Thumbnail { get; set; }
public SmudgeType(sbyte id, string name, Size size, SmudgeTypeFlag flag)
{
ID = id;
Name = name;
Size = size;
Flag = flag;
}
public SmudgeType(sbyte id, string name, Size size)
: this(id, name, size, SmudgeTypeFlag.None)
{
}
public SmudgeType(sbyte id, string name)
: this(id, name, new Size(1, 1), SmudgeTypeFlag.None)
{
}
public override bool Equals(object obj)
{
if (obj is SmudgeType)
{
return this == obj;
}
else if (obj is sbyte)
{
return ID == (sbyte)obj;
}
else if (obj is string)
{
return string.Equals(Name, obj as string, StringComparison.OrdinalIgnoreCase);
}
return base.Equals(obj);
}
public override int GetHashCode()
{
return ID.GetHashCode();
}
public override string ToString()
{
return Name;
}
public void Init(TheaterType theater)
{
if (Globals.TheTilesetManager.GetTileData(theater.Tilesets, Name, 0, out Tile tile))
{
if ((tile.Image.Width * Globals.TileHeight) > (tile.Image.Height * Globals.TileWidth))
{
RenderSize = new Size(
tile.Image.Width * Globals.TileWidth / tile.Image.Width,
tile.Image.Height * Globals.TileWidth / tile.Image.Width
);
}
else
{
RenderSize = new Size(
tile.Image.Width * Globals.TileHeight / tile.Image.Height,
tile.Image.Height * Globals.TileHeight / tile.Image.Height
);
}
Thumbnail = new Bitmap(tile.Image, tile.Image.Width, tile.Image.Height);
}
}
}
}

View file

@ -0,0 +1,37 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using Steamworks;
using System.ComponentModel;
namespace MobiusEditor.Model
{
public class SteamSection
{
[DefaultValue(null)]
public string Title { get; set; }
[DefaultValue(null)]
public string Description { get; set; }
[DefaultValue(null)]
public string PreviewFile { get; set; }
[DefaultValue(ERemoteStoragePublishedFileVisibility.k_ERemoteStoragePublishedFileVisibilityPublic)]
public ERemoteStoragePublishedFileVisibility Visibility { get; set; }
[DefaultValue(typeof(ulong), "0")]
public ulong PublishedFileId { get; set; }
}
}

View file

@ -0,0 +1,158 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using MobiusEditor.Interface;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MobiusEditor.Model
{
public class TeamTypeClass : ICloneable
{
public ITechnoType Type { get; set; }
public byte Count { get; set; }
public TeamTypeClass Clone()
{
return new TeamTypeClass()
{
Type = Type,
Count = Count
};
}
object ICloneable.Clone()
{
return Clone();
}
}
public class TeamTypeMission : ICloneable
{
public string Mission { get; set; }
public int Argument { get; set; }
public TeamTypeMission Clone()
{
return new TeamTypeMission()
{
Mission = Mission,
Argument = Argument
};
}
object ICloneable.Clone()
{
return Clone();
}
}
public class TeamType : INamedType, ICloneable
{
public static readonly string None = "None";
public string Name { get; set; }
public HouseType House { get; set; }
public bool IsRoundAbout { get; set; }
public bool IsLearning { get; set; }
public bool IsSuicide { get; set; }
public bool IsAutocreate { get; set; }
public bool IsMercenary { get; set; }
public int RecruitPriority { get; set; }
public byte MaxAllowed { get; set; }
public byte InitNum { get; set; }
public byte Fear { get; set; }
public bool IsReinforcable { get; set; }
public bool IsPrebuilt { get; set; }
public int Origin { get; set; }
public string Trigger { get; set; } = Model.Trigger.None;
public List<TeamTypeClass> Classes { get; } = new List<TeamTypeClass>();
public List<TeamTypeMission> Missions { get; } = new List<TeamTypeMission>();
public TeamType Clone()
{
var teamType = new TeamType()
{
Name = Name,
House = House,
IsRoundAbout = IsRoundAbout,
IsLearning = IsLearning,
IsSuicide = IsSuicide,
IsAutocreate = IsAutocreate,
IsMercenary = IsMercenary,
RecruitPriority = RecruitPriority,
MaxAllowed = MaxAllowed,
InitNum = InitNum,
Fear = Fear,
IsReinforcable = IsReinforcable,
IsPrebuilt = IsPrebuilt,
Origin = Origin,
Trigger = Trigger
};
teamType.Classes.AddRange(Classes.Select(c => c.Clone()));
teamType.Missions.AddRange(Missions.Select(m => m.Clone()));
return teamType;
}
public override bool Equals(object obj)
{
if (obj is TeamType)
{
return this == obj;
}
else if (obj is string)
{
return string.Equals(Name, obj as string, StringComparison.OrdinalIgnoreCase);
}
return base.Equals(obj);
}
public override int GetHashCode()
{
return Name.GetHashCode();
}
public override string ToString()
{
return Name;
}
object ICloneable.Clone()
{
return Clone();
}
}
}

View file

@ -0,0 +1,30 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using MobiusEditor.Interface;
using System.Drawing;
namespace MobiusEditor.Model
{
public class Template : ICellOccupier
{
public TemplateType Type { get; set; }
public int Icon { get; set; }
public Size OverlapSize => new Size(1, 1);
public bool[,] OccupyMask => new bool[1, 1] { { true } };
}
}

View file

@ -0,0 +1,133 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using MobiusEditor.Interface;
using MobiusEditor.Utility;
using System;
using System.Drawing;
namespace MobiusEditor.Model
{
[Flags]
public enum TemplateTypeFlag
{
None = 0,
Clear = (1 << 1),
Water = (1 << 2),
OreMine = (1 << 3),
}
public class TemplateType : IBrowsableType
{
public ushort ID { get; private set; }
public string Name { get; private set; }
public string DisplayName => Name;
public int IconWidth { get; private set; }
public int IconHeight { get; private set; }
public Size IconSize => new Size(IconWidth, IconHeight);
public int NumIcons => IconWidth * IconHeight;
public bool[,] IconMask { get; set; }
public Image Thumbnail { get; set; }
public TheaterType[] Theaters { get; private set; }
public TemplateTypeFlag Flag { get; private set; }
public TemplateType(ushort id, string name, int iconWidth, int iconHeight, TheaterType[] theaters, TemplateTypeFlag flag)
{
ID = id;
Name = name;
IconWidth = iconWidth;
IconHeight = iconHeight;
Theaters = theaters;
Flag = flag;
}
public TemplateType(ushort id, string name, int iconWidth, int iconHeight, TheaterType[] theaters)
: this(id, name, iconWidth, iconHeight, theaters, TemplateTypeFlag.None)
{
}
public override bool Equals(object obj)
{
if (obj is TemplateType)
{
return this == obj;
}
else if (obj is byte)
{
return ID == (byte)obj;
}
else if (obj is ushort)
{
return ID == (ushort)obj;
}
else if (obj is string)
{
return string.Equals(Name, obj as string, StringComparison.OrdinalIgnoreCase);
}
return base.Equals(obj);
}
public override int GetHashCode()
{
return ID.GetHashCode();
}
public override string ToString()
{
return Name;
}
public void Init(TheaterType theater)
{
var size = new Size(Globals.OriginalTileWidth / 4, Globals.OriginalTileWidth / 4);
var iconSize = Math.Max(IconWidth, IconHeight);
var thumbnail = new Bitmap(iconSize * size.Width, iconSize * size.Height);
var mask = new bool[IconWidth, IconHeight];
Array.Clear(mask, 0, mask.Length);
bool found = false;
using (var g = Graphics.FromImage(thumbnail))
{
g.Clear(Color.Transparent);
int icon = 0;
for (var y = 0; y < IconHeight; ++y)
{
for (var x = 0; x < IconWidth; ++x, ++icon)
{
if (Globals.TheTilesetManager.GetTileData(theater.Tilesets, Name, icon, out Tile tile))
{
g.DrawImage(tile.Image, x * size.Width, y * size.Height, size.Width, size.Height);
found = mask[x, y] = true;
}
}
}
}
Thumbnail = found ? thumbnail : null;
IconMask = mask;
}
}
}

View file

@ -0,0 +1,71 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.CompilerServices;
using MobiusEditor.Interface;
namespace MobiusEditor.Model
{
public class Terrain : ICellOverlapper, ICellOccupier, INotifyPropertyChanged, ICloneable
{
public event PropertyChangedEventHandler PropertyChanged;
private TerrainType type;
public TerrainType Type { get => type; set => SetField(ref type, value); }
private int icon;
public int Icon { get => icon; set => SetField(ref icon, value); }
public Rectangle OverlapBounds => Type.OverlapBounds;
public bool[,] OccupyMask => Type.OccupyMask;
private string trigger = Model.Trigger.None;
public string Trigger { get => trigger; set => SetField(ref trigger, value); }
public Color Tint { get; set; } = Color.White;
public Terrain Clone()
{
return new Terrain()
{
Type = Type,
Icon = Icon,
Trigger = Trigger
};
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
{
return false;
}
field = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
object ICloneable.Clone()
{
return Clone();
}
}
}

View file

@ -0,0 +1,117 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using MobiusEditor.Interface;
using MobiusEditor.Utility;
using System;
using System.Drawing;
namespace MobiusEditor.Model
{
public class TerrainType : ICellOverlapper, ICellOccupier, IBrowsableType
{
public sbyte ID { get; private set; }
public string Name { get; private set; }
public string DisplayName => Name;
public Rectangle OverlapBounds => new Rectangle(
Point.Empty,
new Size(((RenderSize.Width + Globals.TileWidth - 1) / Globals.TileWidth), ((RenderSize.Height + Globals.TileHeight - 1) / Globals.TileHeight))
);
public bool[,] OccupyMask { get; private set; }
public Size Size => new Size(OccupyMask.GetLength(1), OccupyMask.GetLength(0));
public TheaterType[] Theaters { get; private set; }
public bool IsTransformable { get; private set; }
public TemplateTypeFlag TemplateType { get; private set; }
public Size RenderSize { get; set; }
public Image Thumbnail { get; set; }
public TerrainType(sbyte id, string name, TheaterType[] theaters, bool[,] occupyMask, bool isTransformable, TemplateTypeFlag templateType)
{
ID = id;
Name = name;
Theaters = theaters;
OccupyMask = occupyMask;
IsTransformable = isTransformable;
TemplateType = templateType;
}
public TerrainType(sbyte id, string name, TheaterType[] theaters, bool[,] occupyMask, bool isTransformable)
: this(id, name, theaters, occupyMask, isTransformable, TemplateTypeFlag.None)
{
}
public TerrainType(sbyte id, string name, TheaterType[] theaters, bool[,] occupyMask, TemplateTypeFlag templateType)
: this(id, name, theaters, occupyMask, false, templateType)
{
}
public TerrainType(sbyte id, string name, TheaterType[] theaters, bool[,] occupyMask)
: this(id, name, theaters, occupyMask, false, TemplateTypeFlag.None)
{
}
public override bool Equals(object obj)
{
if (obj is TerrainType)
{
return this == obj;
}
else if (obj is sbyte)
{
return ID == (sbyte)obj;
}
else if (obj is string)
{
return string.Equals(Name, obj as string, StringComparison.OrdinalIgnoreCase);
}
return base.Equals(obj);
}
public override int GetHashCode()
{
return ID.GetHashCode();
}
public override string ToString()
{
return Name;
}
public void Init(TheaterType theater)
{
string tileName = Name;
if ((TemplateType & TemplateTypeFlag.OreMine) != TemplateTypeFlag.None)
{
tileName = "OREMINE";
}
if (Globals.TheTilesetManager.GetTileData(theater.Tilesets, tileName, IsTransformable ? 22 : 0, out Tile tile))
{
RenderSize = new Size(tile.Image.Width / Globals.TileScale, tile.Image.Height / Globals.TileScale);
Thumbnail = new Bitmap(tile.Image, tile.Image.Width / 2, tile.Image.Height / 2);
}
}
}
}

View file

@ -0,0 +1,63 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using System;
using System.Collections.Generic;
namespace MobiusEditor.Model
{
public class TheaterType
{
public sbyte ID { get; private set; }
public string Name { get; private set; }
public IEnumerable<string> Tilesets { get; private set; }
public TheaterType(sbyte id, string name, IEnumerable<string> tilesets)
{
ID = id;
Name = name;
Tilesets = tilesets;
}
public override bool Equals(object obj)
{
if (obj is TheaterType)
{
return this == obj;
}
else if (obj is sbyte)
{
return ID == (sbyte)obj;
}
else if (obj is string)
{
return string.Equals(Name, obj as string, StringComparison.OrdinalIgnoreCase);
}
return base.Equals(obj);
}
public override int GetHashCode()
{
return ID.GetHashCode();
}
public override string ToString()
{
return Name;
}
}
}

View file

@ -0,0 +1,154 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using MobiusEditor.Interface;
using System;
namespace MobiusEditor.Model
{
public enum TriggerPersistantType
{
Volatile = 0,
SemiPersistant = 1,
Persistant = 2
}
public enum TriggerMultiStyleType
{
Only = 0,
And = 1,
Or = 2,
Linked = 3
}
public class TriggerEvent : ICloneable
{
public static readonly string None = "None";
public string EventType { get; set; }
public string Team { get; set; }
public long Data { get; set; }
public TriggerEvent Clone()
{
return new TriggerEvent()
{
EventType = EventType,
Team = Team,
Data = Data
};
}
object ICloneable.Clone()
{
return Clone();
}
}
public class TriggerAction : ICloneable
{
public static readonly string None = "None";
public string ActionType { get; set; }
public string Trigger { get; set; }
public string Team { get; set; }
public long Data { get; set; }
public TriggerAction Clone()
{
return new TriggerAction()
{
ActionType = ActionType,
Trigger = Trigger,
Team = Team,
Data = Data
};
}
object ICloneable.Clone()
{
return Clone();
}
}
public class Trigger : INamedType, ICloneable
{
public static readonly string None = "None";
public string Name { get; set; }
public TriggerPersistantType PersistantType { get; set; } = TriggerPersistantType.Volatile;
public string House { get; set; }
public TriggerMultiStyleType EventControl { get; set; } = TriggerMultiStyleType.Only;
public TriggerEvent Event1 { get; private set; } = new TriggerEvent { EventType = TriggerEvent.None };
public TriggerEvent Event2 { get; private set; } = new TriggerEvent { EventType = TriggerEvent.None };
public TriggerAction Action1 { get; private set; } = new TriggerAction { ActionType = TriggerEvent.None };
public TriggerAction Action2 { get; private set; } = new TriggerAction { ActionType = TriggerEvent.None };
public Trigger Clone()
{
return new Trigger()
{
Name = Name,
PersistantType = PersistantType,
House = House,
EventControl = EventControl,
Event1 = Event1.Clone(),
Event2 = Event2.Clone(),
Action1 = Action1.Clone(),
Action2 = Action2.Clone()
};
}
public override bool Equals(object obj)
{
if (obj is Trigger)
{
return this == obj;
}
else if (obj is string)
{
return string.Equals(Name, obj as string, StringComparison.OrdinalIgnoreCase);
}
return base.Equals(obj);
}
public override int GetHashCode()
{
return Name.GetHashCode();
}
public override string ToString()
{
return Name;
}
object ICloneable.Clone()
{
return Clone();
}
}
}

View file

@ -0,0 +1,29 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
namespace MobiusEditor.Model
{
public class TypeItem<T>
{
public string Name { get; private set; }
public T Type { get; private set; }
public TypeItem(string name, T type)
{
Name = name;
Type = type;
}
}
}

View file

@ -0,0 +1,83 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using MobiusEditor.Interface;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.CompilerServices;
namespace MobiusEditor.Model
{
public class Unit : ICellOverlapper, ICellOccupier, INotifyPropertyChanged, ICloneable
{
public event PropertyChangedEventHandler PropertyChanged;
private UnitType type;
public UnitType Type { get => type; set => SetField(ref type, value); }
public Rectangle OverlapBounds => Type.OverlapBounds;
public bool[,] OccupyMask => Type.OccupyMask;
private HouseType house;
public HouseType House { get => house; set => SetField(ref house, value); }
private int strength;
public int Strength { get => strength; set => SetField(ref strength, value); }
private DirectionType direction;
public DirectionType Direction { get => direction; set => SetField(ref direction, value); }
private string mission;
public string Mission { get => mission; set => SetField(ref mission, value); }
private string trigger = Model.Trigger.None;
public string Trigger { get => trigger; set => SetField(ref trigger, value); }
public Color Tint { get; set; } = Color.White;
public Unit Clone()
{
return new Unit()
{
Type = Type,
House = House,
Strength = Strength,
Direction = Direction,
Mission = Mission,
Trigger = Trigger
};
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
{
return false;
}
field = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
object ICloneable.Clone()
{
return Clone();
}
}
}

View file

@ -0,0 +1,137 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using MobiusEditor.Interface;
using MobiusEditor.Render;
using MobiusEditor.Utility;
using System;
using System.Drawing;
namespace MobiusEditor.Model
{
public static class UnitTypeIDMask
{
public const sbyte Aircraft = 1 << 5;
public const sbyte Vessel = 1 << 6;
}
public class UnitType : ICellOverlapper, ICellOccupier, ITechnoType, IBrowsableType
{
public sbyte ID { get; private set; }
public string Name { get; private set; }
public string DisplayName { get; private set; }
public Rectangle OverlapBounds => new Rectangle(-1, -1, 3, 3);
public bool[,] OccupyMask => new bool[1, 1] { { true } };
public string OwnerHouse { get; private set; }
public bool HasTurret { get; private set; }
public bool IsFixedWing { get; private set; }
public bool IsUnit => !IsAircraft && !IsVessel;
public bool IsAircraft => (ID & UnitTypeIDMask.Aircraft) != 0;
public bool IsVessel => (ID & UnitTypeIDMask.Vessel) != 0;
public Size RenderSize { get; set; }
public Image Thumbnail { get; set; }
public UnitType(sbyte id, string name, string textId, string ownerHouse, bool hasTurret, bool isFixedWing)
{
ID = id;
Name = name;
DisplayName = Globals.TheGameTextManager[textId];
OwnerHouse = ownerHouse;
HasTurret = hasTurret;
IsFixedWing = isFixedWing;
}
public UnitType(sbyte id, string name, string textId, string ownerHouse, bool hasTurret)
: this(id, name, textId, ownerHouse, hasTurret, false)
{
}
public UnitType(sbyte id, string name, string textId)
: this(id, name, textId, null, false)
{
}
public UnitType(sbyte id, string name, string textId, string ownerHouse)
: this(id, name, textId, ownerHouse, false)
{
}
public UnitType(sbyte id, string name, string textId, bool hasTurret)
: this(id, name, textId, null, hasTurret)
{
}
public override bool Equals(object obj)
{
if (obj is UnitType)
{
return this == obj;
}
else if (obj is sbyte)
{
return ID == (sbyte)obj;
}
else if (obj is string)
{
return string.Equals(Name, obj as string, StringComparison.OrdinalIgnoreCase);
}
return base.Equals(obj);
}
public override int GetHashCode()
{
return ID.GetHashCode();
}
public override string ToString()
{
return Name;
}
public void Init(GameType gameType, TheaterType theater, HouseType house, DirectionType direction)
{
if (Globals.TheTilesetManager.GetTileData(theater.Tilesets, Name, 0, out Tile tile))
{
RenderSize = new Size(tile.Image.Width / Globals.TileScale, tile.Image.Height / Globals.TileScale);
}
var mockUnit = new Unit()
{
Type = this,
House = house,
Strength = 256,
Direction = direction
};
var unitThumbnail = new Bitmap(Globals.TileWidth * 3, Globals.TileHeight * 3);
using (var g = Graphics.FromImage(unitThumbnail))
{
MapRenderer.Render(gameType, theater, new Point(1, 1), Globals.TileSize, mockUnit).Item2(g);
}
Thumbnail = unitThumbnail;
}
}
}

View file

@ -0,0 +1,69 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// The Command & Conquer Map Editor and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// The Command & Conquer Map Editor and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
using MobiusEditor.Interface;
using System;
namespace MobiusEditor.Model
{
public enum WaypointFlag
{
None,
PlayerStart
}
public class Waypoint : INamedType
{
public string Name { get; private set; }
public WaypointFlag Flag { get; private set; }
public int? Cell { get; set; }
public Waypoint(string name, WaypointFlag flag)
{
Name = name;
Flag = flag;
}
public Waypoint(string name)
: this(name, WaypointFlag.None)
{
}
public override bool Equals(object obj)
{
if (obj is Waypoint)
{
return this == obj;
}
else if (obj is string)
{
return string.Equals(Name, obj as string, StringComparison.OrdinalIgnoreCase);
}
return base.Equals(obj);
}
public override int GetHashCode()
{
return Name.GetHashCode();
}
public override string ToString()
{
return Name;
}
}
}