Skip to content

Commit 94b3f1b

Browse files
committed
Initial version of MESS. Supports the following features:
- 'macro_insert_map' entity, which inserts the contents of another map at the entity's position. Also supports rotating and scaling. - Placeholder substitution of entity property names and values in inserted maps.
0 parents  commit 94b3f1b

30 files changed

+1534
-0
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/.vs/*
2+
*/bin/*
3+
*/obj/*

MESS.sln

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio 15
4+
VisualStudioVersion = 15.0.28307.438
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MESS", "MESS\MESS.csproj", "{0E37B023-13DD-48FE-863D-554351916B16}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{0E37B023-13DD-48FE-863D-554351916B16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{0E37B023-13DD-48FE-863D-554351916B16}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{0E37B023-13DD-48FE-863D-554351916B16}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{0E37B023-13DD-48FE-863D-554351916B16}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
GlobalSection(ExtensibilityGlobals) = postSolution
23+
SolutionGuid = {CD43AB92-6BB5-4EEF-8FC6-66E599B9870C}
24+
EndGlobalSection
25+
EndGlobal

MESS/App.config

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<configuration>
3+
<startup>
4+
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/>
5+
</startup>
6+
</configuration>

MESS/Formats/MapExtensions.cs

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using MESS.Mapping;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
5+
namespace MESS.Formats
6+
{
7+
static class MapExtensions
8+
{
9+
public static IEnumerable<Entity> GenerateEntities(this Path path)
10+
{
11+
// NOTE: This always yields the previous entity, because its target may have been updated by the current entity,
12+
// and it may be too late to update an entity after it has already been yielded.
13+
14+
var index = 0;
15+
Entity previousEntity = null;
16+
foreach (var corner in path.Corners)
17+
{
18+
var entity = CreateEntityForCorner(corner);
19+
20+
if (previousEntity != null)
21+
yield return previousEntity;
22+
23+
previousEntity = entity;
24+
index += 1;
25+
}
26+
27+
if (path.Type == PathType.Circular)
28+
{
29+
// Point the last corner back at the first:
30+
previousEntity["target"] = path.Name;
31+
}
32+
else if (path.Type == PathType.PingPong)
33+
{
34+
// Generate additional corners for the way back (in reverse order, excluding the first and last corner):
35+
foreach (var corner in path.Corners.Skip(1).Take(path.Corners.Count - 2).Reverse())
36+
{
37+
var entity = CreateEntityForCorner(corner);
38+
39+
if (previousEntity != null)
40+
yield return previousEntity;
41+
42+
previousEntity = entity;
43+
index += 1;
44+
}
45+
previousEntity["target"] = path.Name;
46+
}
47+
48+
if (previousEntity != null)
49+
yield return previousEntity;
50+
51+
Entity CreateEntityForCorner(Corner corner)
52+
{
53+
var entity = new Entity();
54+
foreach (var property in corner.Properties)
55+
entity[property.Key] = property.Value;
56+
57+
entity.ClassName = path.ClassName;
58+
entity.Origin = corner.Position;
59+
60+
var targetname = corner.NameOverride;
61+
if (string.IsNullOrEmpty(targetname))
62+
targetname = (index == 0) ? path.Name : $"{path.Name}{index:00}";
63+
entity["targetname"] = targetname;
64+
65+
if (previousEntity != null)
66+
previousEntity["target"] = targetname;
67+
68+
return entity;
69+
}
70+
}
71+
}
72+
}

MESS/Formats/MapFormat.cs

+176
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
using MESS.Mapping;
2+
using MESS.Spatial;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Text;
6+
7+
namespace MESS.Formats
8+
{
9+
/// <summary>
10+
/// The text-based MAP file format only stores entities and brushes.
11+
/// The special 'worldspawn' entity contains all map properties and world geometry.
12+
/// </summary>
13+
public static class MapFormat
14+
{
15+
public static Map Load(Stream stream)
16+
{
17+
using (var reader = new StreamReader(stream, Encoding.UTF8))
18+
{
19+
var map = new Map();
20+
21+
while (!reader.EndOfStream)
22+
{
23+
var line = reader.ReadLine();
24+
if (!line.Trim().StartsWith("{"))
25+
continue;
26+
27+
var entity = ReadEntity(reader);
28+
if (entity == null)
29+
break;
30+
31+
if (entity.ClassName == "worldspawn")
32+
{
33+
foreach (var kv in entity.Properties)
34+
map.Properties[kv.Key] = kv.Value;
35+
36+
map.WorldGeometry.AddRange(entity.Brushes);
37+
}
38+
else
39+
{
40+
map.Entities.Add(entity);
41+
}
42+
}
43+
return map;
44+
}
45+
}
46+
47+
public static void Save(Map map, Stream stream)
48+
{
49+
using (var writer = new StreamWriter(stream, Encoding.UTF8))
50+
{
51+
var worldspawn = new Entity();
52+
worldspawn.ClassName = "worldspawn";
53+
worldspawn["sounds"] = "1";
54+
worldspawn["MaxRange"] = "4096";
55+
worldspawn["mapversion"] = "220";
56+
foreach (var kv in map.Properties)
57+
worldspawn.Properties[kv.Key] = kv.Value;
58+
worldspawn.Brushes.AddRange(map.WorldGeometry);
59+
60+
WriteEntity(writer, worldspawn);
61+
foreach (var entity in map.Entities)
62+
WriteEntity(writer, entity);
63+
64+
foreach (var path in map.Paths)
65+
WritePath(writer, path);
66+
}
67+
}
68+
69+
70+
private static Entity ReadEntity(TextReader reader)
71+
{
72+
var entity = new Entity();
73+
while (true)
74+
{
75+
var line = reader.ReadLine().Trim();
76+
if (line.StartsWith("\""))
77+
{
78+
var parts = line.Split('"'); // NOTE: There is no support for escaping double quotes!
79+
entity.Properties[parts[1]] = parts[3];
80+
}
81+
else if (line.StartsWith("{"))
82+
{
83+
entity.Brushes.Add(ReadBrush(reader));
84+
}
85+
else if (line.StartsWith("}"))
86+
{
87+
break;
88+
}
89+
else
90+
{
91+
throw new InvalidDataException($"Expected key-value pair, brush or end of entity, but found '{line}'.");
92+
}
93+
}
94+
return entity;
95+
}
96+
97+
private static Brush ReadBrush(TextReader reader)
98+
{
99+
var brush = new Brush();
100+
while (true)
101+
{
102+
var line = reader.ReadLine().Trim();
103+
if (line.StartsWith("("))
104+
brush.Faces.Add(ReadFace(line));
105+
else if (line.StartsWith("}"))
106+
break;
107+
else
108+
throw new InvalidDataException($"Expected face or end of brush, but found '{line}'.");
109+
}
110+
return brush;
111+
}
112+
113+
private static Face ReadFace(string line)
114+
{
115+
var parts = line.Split();
116+
if (parts.Length < 31)
117+
throw new InvalidDataException($"Unexpected face format: '{line}'.");
118+
119+
return new Face {
120+
PlanePoints = new[] {
121+
new Vector3D(float.Parse(parts[1]), float.Parse(parts[2]), float.Parse(parts[3])),
122+
new Vector3D(float.Parse(parts[6]), float.Parse(parts[7]), float.Parse(parts[8])),
123+
new Vector3D(float.Parse(parts[11]), float.Parse(parts[12]), float.Parse(parts[13])),
124+
},
125+
TextureName = parts[15],
126+
TextureRightAxis = new Vector3D(float.Parse(parts[17]), float.Parse(parts[18]), float.Parse(parts[19])),
127+
TextureDownAxis = new Vector3D(float.Parse(parts[23]), float.Parse(parts[24]), float.Parse(parts[25])),
128+
TextureShift = new Vector2D(float.Parse(parts[20]), float.Parse(parts[26])),
129+
TextureAngle = float.Parse(parts[28]),
130+
TextureScale = new Vector2D(float.Parse(parts[29]), float.Parse(parts[30])),
131+
};
132+
}
133+
134+
135+
private static void WriteEntity(TextWriter writer, Entity entity)
136+
{
137+
writer.WriteLine("{");
138+
139+
foreach (var property in entity.Properties)
140+
writer.WriteLine($"\"{property.Key}\" \"{property.Value}\"");
141+
142+
foreach (var brush in entity.Brushes)
143+
WriteBrush(writer, brush);
144+
145+
writer.WriteLine("}");
146+
}
147+
148+
private static void WriteBrush(TextWriter writer, Brush brush)
149+
{
150+
writer.WriteLine("{");
151+
152+
foreach (var face in brush.Faces)
153+
WriteFace(writer, face);
154+
155+
writer.WriteLine("}");
156+
}
157+
158+
private static void WriteFace(TextWriter writer, Face face)
159+
{
160+
foreach (var point in face.PlanePoints)
161+
writer.Write($"( {point.X} {point.Y} {point.Z} ) ");
162+
163+
writer.Write(face.TextureName);
164+
writer.Write(' ');
165+
writer.Write($"[ {face.TextureRightAxis.X} {face.TextureRightAxis.Y} {face.TextureRightAxis.Z} {face.TextureShift.X} ] ");
166+
writer.Write($"[ {face.TextureDownAxis.X} {face.TextureDownAxis.Y} {face.TextureDownAxis.Z} {face.TextureShift.Y} ] ");
167+
writer.WriteLine($"{face.TextureAngle} {face.TextureScale.X} {face.TextureScale.Y} ");
168+
}
169+
170+
private static void WritePath(TextWriter writer, Mapping.Path path)
171+
{
172+
foreach (var entity in path.GenerateEntities())
173+
WriteEntity(writer, entity);
174+
}
175+
}
176+
}

0 commit comments

Comments
 (0)