mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2025-02-17 09:32:34 +00:00
Reverted "Delete Item" action to the way it worked in DB2. Added "Dissolve Item" action, which works the way "Delete Item" worked in previous revisions of GZDB. Added "Auto Clear Sidedef Textures" action, "Edit" menu and toolbar button, which toggle automatic removal of sidedef textures when floor or ceiling height is changed or when geometry is drawn, copied or pasted. Draw Settings panel: upper/lower texture overrides can now be used. Draw Settings panel: added 2 sets of buttons, which allow to quickly set or clear textures in current selection. Things are now rendered behind AND on top of the grid/linedefs/vertices when they are dragged. Redesigned hints system. They are now shown in a side panel. Edit area auto-focusing is now disabled when script editor is open. Texture Browser form: no texture group was selected when opening the form in some cases. Fixed several strange/misleading text messages.
638 lines
16 KiB
C#
638 lines
16 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.IO;
|
|
using CodeImp.DoomBuilder.Geometry;
|
|
using CodeImp.DoomBuilder.Data;
|
|
using CodeImp.DoomBuilder.GZBuilder.Tools;
|
|
|
|
#endregion
|
|
|
|
namespace CodeImp.DoomBuilder.Map
|
|
{
|
|
public sealed class Sidedef : MapElement
|
|
{
|
|
#region ================== Variables
|
|
|
|
// Map
|
|
private MapSet map;
|
|
|
|
// List items
|
|
private LinkedListNode<Sidedef> sectorlistitem;
|
|
|
|
// Owner
|
|
private Linedef linedef;
|
|
|
|
// Sector
|
|
private Sector sector;
|
|
|
|
// Properties
|
|
private int offsetx;
|
|
private int offsety;
|
|
private string texnamehigh;
|
|
private string texnamemid;
|
|
private string texnamelow;
|
|
private long longtexnamehigh;
|
|
private long longtexnamemid;
|
|
private long longtexnamelow;
|
|
|
|
//mxd. UDMF properties
|
|
private Dictionary<string, bool> flags;
|
|
|
|
// Clone
|
|
private int serializedindex;
|
|
|
|
#endregion
|
|
|
|
#region ================== Properties
|
|
|
|
public MapSet Map { get { return map; } }
|
|
public bool IsFront { get { return (linedef != null) ? (this == linedef.Front) : false; } }
|
|
public Linedef Line { get { return linedef; } }
|
|
public Sidedef Other { get { if(this == linedef.Front) return linedef.Back; else return linedef.Front; } }
|
|
public Sector Sector { get { return sector; } }
|
|
internal Dictionary<string, bool> Flags { get { return flags; } } //mxd
|
|
public float Angle { get { if(IsFront) return linedef.Angle; else return Angle2D.Normalized(linedef.Angle + Angle2D.PI); } }
|
|
public int OffsetX { get { return offsetx; } set { BeforePropsChange(); offsetx = value; } }
|
|
public int OffsetY { get { return offsety; } set { BeforePropsChange(); offsety = value; } }
|
|
public string HighTexture { get { return texnamehigh; } }
|
|
public string MiddleTexture { get { return texnamemid; } }
|
|
public string LowTexture { get { return texnamelow; } }
|
|
public long LongHighTexture { get { return longtexnamehigh; } }
|
|
public long LongMiddleTexture { get { return longtexnamemid; } }
|
|
public long LongLowTexture { get { return longtexnamelow; } }
|
|
internal int SerializedIndex { get { return serializedindex; } set { serializedindex = value; } }
|
|
|
|
#endregion
|
|
|
|
#region ================== Constructor / Disposer
|
|
|
|
// Constructor
|
|
internal Sidedef(MapSet map, int listindex, Linedef l, bool front, Sector s)
|
|
{
|
|
// Initialize
|
|
this.map = map;
|
|
this.listindex = listindex;
|
|
this.texnamehigh = "-";
|
|
this.texnamemid = "-";
|
|
this.texnamelow = "-";
|
|
this.longtexnamehigh = MapSet.EmptyLongName;
|
|
this.longtexnamemid = MapSet.EmptyLongName;
|
|
this.longtexnamelow = MapSet.EmptyLongName;
|
|
this.flags = new Dictionary<string, bool>(); //mxd
|
|
|
|
// Attach linedef
|
|
this.linedef = l;
|
|
if(l != null)
|
|
{
|
|
if(front)
|
|
l.AttachFrontP(this);
|
|
else
|
|
l.AttachBackP(this);
|
|
}
|
|
|
|
// Attach sector
|
|
SetSectorP(s);
|
|
|
|
if(map == General.Map.Map)
|
|
General.Map.UndoRedo.RecAddSidedef(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.RecRemSidedef(this);
|
|
|
|
// Remove from main list
|
|
map.RemoveSidedef(listindex);
|
|
|
|
// Detach from linedef
|
|
if(linedef != null) linedef.DetachSidedefP(this);
|
|
|
|
// Detach from sector
|
|
SetSectorP(null);
|
|
|
|
// Clean up
|
|
sectorlistitem = null;
|
|
linedef = null;
|
|
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.RecPrpSidedef(this);
|
|
}
|
|
|
|
// Serialize / deserialize (passive: this doesn't record)
|
|
new internal void ReadWrite(IReadWriteStream s)
|
|
{
|
|
if(!s.IsWriting) BeforePropsChange();
|
|
|
|
base.ReadWrite(s);
|
|
|
|
//mxd
|
|
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);
|
|
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 offsetx);
|
|
s.rwInt(ref offsety);
|
|
s.rwString(ref texnamehigh);
|
|
s.rwString(ref texnamemid);
|
|
s.rwString(ref texnamelow);
|
|
s.rwLong(ref longtexnamehigh);
|
|
s.rwLong(ref longtexnamemid);
|
|
s.rwLong(ref longtexnamelow);
|
|
}
|
|
|
|
// This copies all properties to another sidedef
|
|
public void CopyPropertiesTo(Sidedef s)
|
|
{
|
|
s.BeforePropsChange();
|
|
|
|
// Copy properties
|
|
s.offsetx = offsetx;
|
|
s.offsety = offsety;
|
|
s.texnamehigh = texnamehigh;
|
|
s.texnamemid = texnamemid;
|
|
s.texnamelow = texnamelow;
|
|
s.longtexnamehigh = longtexnamehigh;
|
|
s.longtexnamemid = longtexnamemid;
|
|
s.longtexnamelow = longtexnamelow;
|
|
s.flags = new Dictionary<string, bool>(flags); //mxd
|
|
base.CopyPropertiesTo(s);
|
|
}
|
|
|
|
// This changes sector
|
|
public void SetSector(Sector newsector)
|
|
{
|
|
if(map == General.Map.Map)
|
|
General.Map.UndoRedo.RecRefSidedefSector(this);
|
|
|
|
// Change sector
|
|
SetSectorP(newsector);
|
|
}
|
|
|
|
internal void SetSectorP(Sector newsector)
|
|
{
|
|
// Detach from sector
|
|
if(sector != null && !sector.IsDisposed) //mxd
|
|
sector.DetachSidedefP(sectorlistitem);
|
|
|
|
// Change sector
|
|
sector = newsector;
|
|
|
|
// Attach to sector
|
|
if(sector != null && !sector.IsDisposed) //mxd
|
|
sectorlistitem = sector.AttachSidedefP(this);
|
|
|
|
General.Map.IsChanged = true;
|
|
}
|
|
|
|
// This sets the linedef
|
|
public void SetLinedef(Linedef ld, bool front)
|
|
{
|
|
if(linedef != null) linedef.DetachSidedefP(this);
|
|
|
|
if(ld != null)
|
|
{
|
|
if(front)
|
|
ld.AttachFront(this);
|
|
else
|
|
ld.AttachBack(this);
|
|
}
|
|
}
|
|
|
|
// This sets the linedef (passive: this doesn't tell the linedef and doesn't record)
|
|
internal void SetLinedefP(Linedef ld)
|
|
{
|
|
linedef = ld;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Methods
|
|
|
|
// This checks and returns a flag without creating it
|
|
public bool IsFlagSet(string flagname)
|
|
{
|
|
return (flags.ContainsKey(flagname) && flags[flagname]);
|
|
}
|
|
|
|
// 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 removes textures that are not required
|
|
public void RemoveUnneededTextures(bool removemiddle)
|
|
{
|
|
RemoveUnneededTextures(removemiddle, !General.Settings.AutoClearSidedefTextures, false);
|
|
}
|
|
|
|
// This removes textures that are not required
|
|
public void RemoveUnneededTextures(bool removemiddle, bool shiftMiddle, bool force)
|
|
{
|
|
bool changed = false; //mxd
|
|
string curMidTex = this.texnamemid; //mxd
|
|
|
|
// The middle texture can be removed regardless of any sector tag or linedef action
|
|
if(!MiddleRequired() && removemiddle)
|
|
{
|
|
BeforePropsChange(); //mxd
|
|
changed = true; //mxd
|
|
this.texnamemid = "-";
|
|
this.longtexnamemid = MapSet.EmptyLongName;
|
|
General.Map.IsChanged = true;
|
|
}
|
|
|
|
// Check if the line or sectors have no action or tags because
|
|
// if they do, any texture on this side could be needed
|
|
if(force || ((linedef.Tag <= 0) && (linedef.Action == 0) && (sector.Tag <= 0) &&
|
|
((Other == null) || (Other.sector.Tag <= 0))))
|
|
{
|
|
if(!HighRequired())
|
|
{
|
|
if(shiftMiddle) { //mxd
|
|
if(string.IsNullOrEmpty(this.texnamehigh) || this.texnamehigh == "-")
|
|
SetTextureHigh(curMidTex);
|
|
} else {
|
|
if(!changed) { //mxd
|
|
BeforePropsChange();
|
|
changed = true;
|
|
}
|
|
this.texnamehigh = "-";
|
|
this.longtexnamehigh = MapSet.EmptyLongName;
|
|
General.Map.IsChanged = true;
|
|
}
|
|
}
|
|
|
|
if(!LowRequired())
|
|
{
|
|
if(shiftMiddle) { //mxd
|
|
if(string.IsNullOrEmpty(this.texnamelow) || this.texnamelow == "-")
|
|
SetTextureLow(curMidTex);
|
|
} else {
|
|
if(!changed) BeforePropsChange(); //mxd
|
|
this.texnamelow = "-";
|
|
this.longtexnamelow = MapSet.EmptyLongName;
|
|
General.Map.IsChanged = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//mxd
|
|
internal void ShiftTextures() {
|
|
BeforePropsChange();
|
|
|
|
if(string.IsNullOrEmpty(texnamehigh) || texnamehigh == "-" && HighRequired()) {
|
|
if(General.Map.Options.OverrideTopTexture) {
|
|
texnamehigh = General.Map.Options.DefaultTopTexture;
|
|
longtexnamehigh = Lump.MakeLongName(texnamehigh);
|
|
} else {
|
|
texnamehigh = texnamemid;
|
|
longtexnamehigh = longtexnamemid;
|
|
}
|
|
}
|
|
|
|
if(string.IsNullOrEmpty(texnamelow) || texnamelow == "-" && LowRequired()) {
|
|
if(General.Map.Options.OverrideBottomTexture) {
|
|
texnamelow = General.Map.Options.DefaultBottomTexture;
|
|
longtexnamelow = Lump.MakeLongName(texnamelow);
|
|
} else {
|
|
texnamelow = texnamemid;
|
|
longtexnamelow = longtexnamemid;
|
|
}
|
|
}
|
|
|
|
General.Map.IsChanged = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This checks if a texture is required
|
|
/// </summary>
|
|
public bool HighRequired()
|
|
{
|
|
// Doublesided?
|
|
if(Other != null)
|
|
{
|
|
// Texture is required when ceiling of other side is lower
|
|
return (Other.sector.CeilHeight < this.sector.CeilHeight);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This checks if a texture is required
|
|
/// </summary>
|
|
public bool MiddleRequired()
|
|
{
|
|
// Texture is required when the line is singlesided
|
|
return (Other == null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// This checks if a texture is required
|
|
/// </summary>
|
|
public bool LowRequired()
|
|
{
|
|
// Doublesided?
|
|
if(Other != null)
|
|
{
|
|
// Texture is required when floor of other side is higher
|
|
return (Other.sector.FloorHeight > this.sector.FloorHeight);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This returns the height of the upper wall part. Returns 0 when no upper part exists.
|
|
/// </summary>
|
|
public int GetHighHeight()
|
|
{
|
|
Sidedef other = this.Other;
|
|
if(other != null)
|
|
{
|
|
int top = this.sector.CeilHeight;
|
|
int bottom = other.sector.CeilHeight;
|
|
int height = top - bottom;
|
|
return (height > 0) ? height : 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This returns the height of the middle wall part.
|
|
/// </summary>
|
|
public int GetMiddleHeight()
|
|
{
|
|
Sidedef other = this.Other;
|
|
if(other != null)
|
|
{
|
|
int top = Math.Min(this.Sector.CeilHeight, other.Sector.CeilHeight);
|
|
int bottom = Math.Max(this.Sector.FloorHeight, other.Sector.FloorHeight);
|
|
int height = top - bottom;
|
|
return (height > 0) ? height : 0;
|
|
}
|
|
else
|
|
{
|
|
int top = this.Sector.CeilHeight;
|
|
int bottom = this.Sector.FloorHeight;
|
|
int height = top - bottom;
|
|
return (height > 0) ? height : 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This returns the height of the lower wall part. Returns 0 when no lower part exists.
|
|
/// </summary>
|
|
public int GetLowHeight()
|
|
{
|
|
Sidedef other = this.Other;
|
|
if(other != null)
|
|
{
|
|
int top = other.sector.FloorHeight;
|
|
int bottom = this.sector.FloorHeight;
|
|
int height = top - bottom;
|
|
return (height > 0) ? height : 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// This creates a checksum from the sidedef properties
|
|
// Used for faster sidedefs compression
|
|
public uint GetChecksum()
|
|
{
|
|
CRC crc = new CRC();
|
|
crc.Add(sector.FixedIndex);
|
|
crc.Add(offsetx);
|
|
crc.Add(offsety);
|
|
crc.Add(longtexnamehigh);
|
|
crc.Add(longtexnamelow);
|
|
crc.Add(longtexnamemid);
|
|
return (uint)(crc.Value & 0x00000000FFFFFFFF);
|
|
}
|
|
|
|
// This copies textures to another sidedef
|
|
// And possibly also the offsets
|
|
public void AddTexturesTo(Sidedef s)
|
|
{
|
|
int copyoffsets = 0;
|
|
|
|
// s cannot be null
|
|
if(s == null) return;
|
|
|
|
s.BeforePropsChange();
|
|
|
|
// Upper texture set?
|
|
if((texnamehigh.Length > 0) && (texnamehigh != "-"))
|
|
{
|
|
// Copy upper texture
|
|
s.texnamehigh = texnamehigh;
|
|
s.longtexnamehigh = longtexnamehigh;
|
|
|
|
// Counts as a half coice for copying offsets
|
|
copyoffsets += 1;
|
|
}
|
|
|
|
// Middle texture set?
|
|
if((texnamemid.Length > 0) && (texnamemid != "-"))
|
|
{
|
|
// Copy middle texture
|
|
s.texnamemid = texnamemid;
|
|
s.longtexnamemid = longtexnamemid;
|
|
|
|
// Counts for copying offsets
|
|
copyoffsets += 2;
|
|
}
|
|
|
|
// Lower texture set?
|
|
if((texnamelow.Length > 0) && (texnamelow != "-"))
|
|
{
|
|
// Copy middle texture
|
|
s.texnamelow = texnamelow;
|
|
s.longtexnamelow = longtexnamelow;
|
|
|
|
// Counts as a half coice for copying offsets
|
|
copyoffsets += 1;
|
|
}
|
|
|
|
// Copy offsets also?
|
|
if(copyoffsets >= 2)
|
|
{
|
|
// Copy offsets
|
|
s.offsetx = offsetx;
|
|
s.offsety = offsety;
|
|
}
|
|
|
|
General.Map.IsChanged = true;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Changes
|
|
|
|
// This updates all properties
|
|
public void Update(int offsetx, int offsety, string thigh, string tmid, string tlow) {
|
|
Update(offsetx, offsety, thigh, tmid, tlow, new Dictionary<string, bool>());
|
|
}
|
|
|
|
//mxd. This updates all properties (UDMF version)
|
|
public void Update(int offsetx, int offsety, string thigh, string tmid, string tlow, Dictionary<string, bool> flags)
|
|
{
|
|
BeforePropsChange();
|
|
|
|
// Apply changes
|
|
this.offsetx = offsetx;
|
|
this.offsety = offsety;
|
|
this.flags = new Dictionary<string, bool>(flags); //mxd
|
|
SetTextureMid(tmid);
|
|
SetTextureLow(tlow);
|
|
SetTextureHigh(thigh);
|
|
}
|
|
|
|
// This sets texture
|
|
public void SetTextureHigh(string name)
|
|
{
|
|
BeforePropsChange();
|
|
|
|
texnamehigh = name;
|
|
longtexnamehigh = Lump.MakeLongName(name);
|
|
General.Map.IsChanged = true;
|
|
}
|
|
|
|
// This sets texture
|
|
public void SetTextureMid(string name)
|
|
{
|
|
BeforePropsChange();
|
|
|
|
texnamemid = name;
|
|
longtexnamemid = Lump.MakeLongName(name);
|
|
General.Map.IsChanged = true;
|
|
}
|
|
|
|
// This sets texture
|
|
public void SetTextureLow(string name)
|
|
{
|
|
BeforePropsChange();
|
|
|
|
texnamelow = name;
|
|
longtexnamelow = Lump.MakeLongName(name);
|
|
General.Map.IsChanged = true;
|
|
}
|
|
|
|
// This sets udmf texture offset
|
|
public void SetUdmfTextureOffsetX(int offset) {
|
|
this.Fields.BeforeFieldsChange();
|
|
|
|
//top
|
|
if(HighTexture.Length > 1 && General.Map.Data.GetFlatExists(HighTexture)) {
|
|
ImageData texture = General.Map.Data.GetFlatImage(HighTexture);
|
|
float scaleTop = Fields.GetValue("scalex_top", 1.0f);
|
|
|
|
float value = Fields.GetValue("offsetx_top", 0f);
|
|
float result = (float)(Math.Round(value + offset * scaleTop) % texture.Width);
|
|
UDMFTools.SetFloat(Fields, "offsetx_top", result);
|
|
}
|
|
|
|
//middle
|
|
if(MiddleTexture.Length > 1 && General.Map.Data.GetFlatExists(MiddleTexture)) {
|
|
ImageData texture = General.Map.Data.GetFlatImage(MiddleTexture);
|
|
float scaleMid = Fields.GetValue("scalex_mid", 1.0f);
|
|
|
|
float value = Fields.GetValue("offsetx_mid", 0f);
|
|
float result = (float)(Math.Round(value + offset * scaleMid) % texture.Width);
|
|
UDMFTools.SetFloat(Fields, "offsetx_mid", result);
|
|
}
|
|
|
|
//bottom
|
|
if(LowTexture.Length > 1 && General.Map.Data.GetFlatExists(LowTexture)) {
|
|
ImageData texture = General.Map.Data.GetFlatImage(LowTexture);
|
|
float scaleLow = Fields.GetValue("scalex_bottom", 1.0f);
|
|
|
|
float value = Fields.GetValue("offsetx_bottom", 0f);
|
|
float result = (float)(Math.Round(value + offset * scaleLow) % texture.Width);
|
|
UDMFTools.SetFloat(Fields, "offsetx_bottom", result);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|