UltimateZoneBuilder/Source/Core/Map/Thing.cs
MaxED 0369c969d1 According to dotnetperls.com, "new Dictionary<string, [anything]>(StringComparer.Ordinal)" works 17% faster than "new Dictionary<string, [anything]>()", so let's stick that everywhere and see what happens :)
Draw Curve Mode: added settings panel.
Sectors mode: added "Make Door" button to the toolbar.
Swapped Side panel and Info panel z-order. 
Interface: split toolbar into 3 separate toolbars. All toolbar buttons are now viewable at 1024x768.
Interface: grouped stuff in "Modes" menu a bit better.
Interface: added "Draw [stuff]" buttons to modes toolbar.
Interface: reorganized main menu. Hope it makes more sense now.
API: added General.Interface.AddModesButton() and General.Interface.AddModesMenu(), which can be used to add buttons to specific group in "Modes" toolbar and menu items to specific group in "Modes" menu, so actions, which behave like an editing mode, but are not part of one can be added there.
2014-02-26 14:11:06 +00:00

526 lines
13 KiB
C#

#region ================== Copyright (c) 2007 Pascal vd Heiden
/*
* Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com
* This program is released under GNU General Public License
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#endregion
#region ================== Namespaces
using System;
using System.Collections.Generic;
using CodeImp.DoomBuilder.Geometry;
using CodeImp.DoomBuilder.Rendering;
using CodeImp.DoomBuilder.Config;
using CodeImp.DoomBuilder.IO;
using CodeImp.DoomBuilder.VisualModes;
using CodeImp.DoomBuilder.GZBuilder.Data;
#endregion
namespace CodeImp.DoomBuilder.Map
{
public sealed class Thing : SelectableElement
{
#region ================== Constants
public const int NUM_ARGS = 5;
#endregion
#region ================== Variables
// Map
private MapSet map;
// Sector
private Sector sector;
// List items
private LinkedListNode<Thing> selecteditem;
// Properties
private int type;
private Vector3D pos;
private int angledoom; // Angle as entered / stored in file
private float anglerad; // Angle in radians
private Dictionary<string, bool> flags;
private int tag;
private int action;
private int[] args;
private float scale; //mxd. Used in model rendering
private bool isModel; //mxd
// Configuration
private float size;
private PixelColor color;
private bool fixedsize;
private bool directional; //mxd. If true, we need to render an arrow
#endregion
#region ================== Properties
public MapSet Map { get { return map; } }
public int Type { get { return type; } set { BeforePropsChange(); type = value; UpdateModelStatus(); } } //mxd
public Vector3D Position { get { return pos; } }
public float Scale { get { return scale; } } //mxd
public float Angle { get { return anglerad; } }
public int AngleDoom { get { return angledoom; } }
internal Dictionary<string, bool> Flags { get { return flags; } }
public int Action { get { return action; } set { BeforePropsChange(); action = value; } }
public int[] Args { get { return args; } }
public float Size { get { return size; } }
public PixelColor Color { get { return color; } }
public bool FixedSize { get { return fixedsize; } }
public int Tag { get { return tag; } set { BeforePropsChange(); tag = value; if((tag < General.Map.FormatInterface.MinTag) || (tag > General.Map.FormatInterface.MaxTag)) throw new ArgumentOutOfRangeException("Tag", "Invalid tag number"); } }
public Sector Sector { get { return sector; } }
public bool IsModel { get { return isModel; } } //mxd
public bool IsDirectional { get { return directional; } } //mxd
#endregion
#region ================== Constructor / Disposer
// Constructor
internal Thing(MapSet map, int listindex)
{
// Initialize
this.map = map;
this.listindex = listindex;
this.flags = new Dictionary<string, bool>(StringComparer.Ordinal);
this.args = new int[NUM_ARGS];
this.scale = 1.0f; //mxd
if(map == General.Map.Map)
General.Map.UndoRedo.RecAddThing(this);
// We have no destructor
GC.SuppressFinalize(this);
}
// Disposer
public override void Dispose()
{
// Not already disposed?
if(!isdisposed)
{
// Already set isdisposed so that changes can be prohibited
isdisposed = true;
if(map == General.Map.Map)
General.Map.UndoRedo.RecRemThing(this);
// Remove from main list
map.RemoveThing(listindex);
// Remove from sector
//if(sector != null) sector.DetachThing(sectorlistitem);
// Clean up
map = null;
sector = null;
// Dispose base
base.Dispose();
}
}
#endregion
#region ================== Management
// Call this before changing properties
protected override void BeforePropsChange()
{
if(map == General.Map.Map)
General.Map.UndoRedo.RecPrpThing(this);
}
// Serialize / deserialize
new internal void ReadWrite(IReadWriteStream s)
{
if(!s.IsWriting) BeforePropsChange();
base.ReadWrite(s);
if(s.IsWriting)
{
s.wInt(flags.Count);
foreach(KeyValuePair<string, bool> f in flags)
{
s.wString(f.Key);
s.wBool(f.Value);
}
}
else
{
int c; s.rInt(out c);
flags = new Dictionary<string, bool>(c, StringComparer.Ordinal);
for(int i = 0; i < c; i++)
{
string t; s.rString(out t);
bool b; s.rBool(out b);
flags.Add(t, b);
}
}
s.rwInt(ref type);
s.rwVector3D(ref pos);
s.rwInt(ref angledoom);
s.rwInt(ref tag);
s.rwInt(ref action);
for(int i = 0; i < NUM_ARGS; i++) s.rwInt(ref args[i]);
if(!s.IsWriting) {
anglerad = Angle2D.DoomToReal(angledoom);
UpdateModelStatus(); //mxd
}
}
// This copies all properties to another thing
public void CopyPropertiesTo(Thing t)
{
t.BeforePropsChange();
// Copy properties
t.type = type;
t.UpdateModelStatus();
t.anglerad = anglerad;
t.angledoom = angledoom;
t.pos = pos;
t.flags = new Dictionary<string,bool>(flags);
t.tag = tag;
t.action = action;
t.args = (int[])args.Clone();
t.size = size;
t.color = color;
t.directional = directional;
t.fixedsize = fixedsize;
base.CopyPropertiesTo(t);
}
// This determines which sector the thing is in and links it
public void DetermineSector()
{
//mxd
sector = map.GetSectorByCoordinates(pos);
}
// This determines which sector the thing is in and links it
public void DetermineSector(VisualBlockMap blockmap)
{
// Find nearest sectors using the blockmap
List<Sector> possiblesectors = blockmap.GetBlock(blockmap.GetBlockCoordinates(pos)).Sectors;
// Check in which sector we are
sector = null;
foreach(Sector s in possiblesectors)
{
if(s.Intersect(pos))
{
sector = s;
break;
}
}
}
//mxd. This checks if the thing has model override
internal void UpdateModelStatus() {
if(General.Map.Data == null) {
isModel = false;
return;
}
isModel = General.Map.Data.ModeldefEntries.ContainsKey(type);
if(!isModel) return;
if(General.Map.Data.ModeldefEntries[type].LoadState == ModelLoadState.None)
isModel = General.Map.Data.ProcessModel(type);
}
// This translates the flags into UDMF fields
internal void TranslateToUDMF()
{
// First make a single integer with all flags
int bits = 0;
int flagbit;
foreach(KeyValuePair<string, bool> f in flags)
if(int.TryParse(f.Key, out flagbit) && f.Value) bits |= flagbit;
// Now make the new flags
flags.Clear();
foreach(FlagTranslation f in General.Map.Config.ThingFlagsTranslation)
{
// Flag found in bits?
if((bits & f.Flag) == f.Flag)
{
// Add fields and remove bits
bits &= ~f.Flag;
for(int i = 0; i < f.Fields.Count; i++)
flags[f.Fields[i]] = f.FieldValues[i];
}
else
{
// Add fields with inverted value
for(int i = 0; i < f.Fields.Count; i++)
flags[f.Fields[i]] = !f.FieldValues[i];
}
}
}
// This translates UDMF fields back into the normal flags
internal void TranslateFromUDMF()
{
// Make copy of the flags
Dictionary<string, bool> oldfields = new Dictionary<string, bool>(flags);
// Make the flags
flags.Clear();
foreach(KeyValuePair<string, string> f in General.Map.Config.ThingFlags)
{
// Flag must be numeric
int flagbit;
if(int.TryParse(f.Key, out flagbit))
{
foreach(FlagTranslation ft in General.Map.Config.ThingFlagsTranslation)
{
if(ft.Flag == flagbit)
{
// Only set this flag when the fields match
bool fieldsmatch = true;
for(int i = 0; i < ft.Fields.Count; i++)
{
if(!oldfields.ContainsKey(ft.Fields[i]) || (oldfields[ft.Fields[i]] != ft.FieldValues[i]))
{
fieldsmatch = false;
break;
}
}
// Field match? Then add the flag.
if(fieldsmatch)
{
flags.Add(f.Key, true);
break;
}
}
}
}
}
}
// Selected
protected override void DoSelect()
{
base.DoSelect();
selecteditem = map.SelectedThings.AddLast(this);
}
// Deselect
protected override void DoUnselect()
{
base.DoUnselect();
if(selecteditem.List != null) selecteditem.List.Remove(selecteditem);
selecteditem = null;
}
#endregion
#region ================== Changes
// This moves the thing
// NOTE: This does not update sector! (call DetermineSector)
public void Move(Vector3D newpos)
{
BeforePropsChange();
// Change position
this.pos = newpos;
if(type != General.Map.Config.Start3DModeThingType)
General.Map.IsChanged = true;
}
// This moves the thing
// NOTE: This does not update sector! (call DetermineSector)
public void Move(Vector2D newpos)
{
BeforePropsChange();
// Change position
this.pos = new Vector3D(newpos.x, newpos.y, pos.z);
if(type != General.Map.Config.Start3DModeThingType)
General.Map.IsChanged = true;
}
// This moves the thing
// NOTE: This does not update sector! (call DetermineSector)
public void Move(float x, float y, float zoffset)
{
BeforePropsChange();
// Change position
this.pos = new Vector3D(x, y, zoffset);
if(type != General.Map.Config.Start3DModeThingType)
General.Map.IsChanged = true;
}
// This rotates the thing
public void Rotate(float newangle)
{
BeforePropsChange();
// Change angle
this.anglerad = newangle;
this.angledoom = Angle2D.RealToDoom(newangle);
if(type != General.Map.Config.Start3DModeThingType)
General.Map.IsChanged = true;
}
// This rotates the thing
public void Rotate(int newangle)
{
BeforePropsChange();
// Change angle
this.anglerad = Angle2D.DoomToReal(newangle);
this.angledoom = newangle;
if(type != General.Map.Config.Start3DModeThingType)
General.Map.IsChanged = true;
}
// This updates all properties
// NOTE: This does not update sector! (call DetermineSector)
public void Update(int type, float x, float y, float zoffset, int angle,
Dictionary<string, bool> flags, int tag, int action, int[] args)
{
// Apply changes
this.type = type;
this.anglerad = Angle2D.DoomToReal(angle);
this.angledoom = angle;
this.flags = new Dictionary<string, bool>(flags);
this.tag = tag;
this.action = action;
this.args = new int[NUM_ARGS];
args.CopyTo(this.args, 0);
this.Move(x, y, zoffset);
UpdateModelStatus(); //mxd
}
// This updates the settings from configuration
public void UpdateConfiguration()
{
ThingTypeInfo ti;
// Lookup settings
ti = General.Map.Data.GetThingInfo(type);
// Apply size
size = ti.Radius;
fixedsize = ti.FixedSize;
scale = ti.SpriteScale.Width; //mxd
// Color valid?
if((ti.Color >= 0) && (ti.Color < ColorCollection.NUM_THING_COLORS))
{
// Apply color
color = General.Colors.Colors[ti.Color + ColorCollection.THING_COLORS_OFFSET];
}
else
{
// Unknown thing color
color = General.Colors.Colors[ColorCollection.THING_COLORS_OFFSET];
}
// Apply icon offset (arrow or dot)
//if(ti.Arrow) iconoffset = 0f; else iconoffset = 0.25f;
directional = ti.Arrow; //mxd
}
#endregion
#region ================== Methods
// This checks and returns a flag without creating it
public bool IsFlagSet(string flagname)
{
if(flags.ContainsKey(flagname))
return flags[flagname];
else
return false;
}
// This sets a flag
public void SetFlag(string flagname, bool value)
{
if(!flags.ContainsKey(flagname) || (IsFlagSet(flagname) != value))
{
BeforePropsChange();
flags[flagname] = value;
}
}
// This returns a copy of the flags dictionary
public Dictionary<string, bool> GetFlags()
{
return new Dictionary<string,bool>(flags);
}
// This clears all flags
public void ClearFlags()
{
BeforePropsChange();
flags.Clear();
}
// This snaps the vertex to the grid
public void SnapToGrid()
{
// Calculate nearest grid coordinates
this.Move(General.Map.Grid.SnappedToGrid(pos));
}
// This snaps the vertex to the map format accuracy
public void SnapToAccuracy()
{
// Round the coordinates
Vector3D newpos = new Vector3D((float)Math.Round(pos.x, General.Map.FormatInterface.VertexDecimals),
(float)Math.Round(pos.y, General.Map.FormatInterface.VertexDecimals),
(float)Math.Round(pos.z, General.Map.FormatInterface.VertexDecimals));
this.Move(newpos);
}
// This returns the distance from given coordinates
public float DistanceToSq(Vector2D p)
{
return Vector2D.DistanceSq(p, pos);
}
// This returns the distance from given coordinates
public float DistanceTo(Vector2D p)
{
return Vector2D.Distance(p, pos);
}
#endregion
}
}