1 |
using System; |
2 |
using System.Collections.Generic; |
3 |
|
4 |
namespace Oni.Level |
5 |
{ |
6 |
internal class LevelDatWriter |
7 |
{ |
8 |
private readonly Importer importer; |
9 |
private readonly DatLevel level; |
10 |
|
11 |
public class DatLevel |
12 |
{ |
13 |
public string name; |
14 |
public string skyName; |
15 |
public readonly List<ObjectSetup> physics = new List<ObjectSetup>(); |
16 |
public readonly List<Physics.ObjectParticle> particles = new List<Physics.ObjectParticle>(); |
17 |
public readonly List<ScriptCharacter> characters = new List<ScriptCharacter>(); |
18 |
public readonly List<Corpse> corpses = new List<Corpse>(); |
19 |
public Akira.PolygonMesh model; |
20 |
} |
21 |
|
22 |
private LevelDatWriter(Importer importer, DatLevel level) |
23 |
{ |
24 |
this.importer = importer; |
25 |
this.level = level; |
26 |
} |
27 |
|
28 |
public static void Write(Importer importer, DatLevel level) |
29 |
{ |
30 |
var writer = new LevelDatWriter(importer, level); |
31 |
writer.WriteONLV(); |
32 |
} |
33 |
|
34 |
private void WriteONLV() |
35 |
{ |
36 |
var onlv = importer.CreateInstance(TemplateTag.ONLV, level.name); |
37 |
var oboa = importer.CreateInstance(TemplateTag.OBOA); |
38 |
var aisa = importer.CreateInstance(TemplateTag.AISA); |
39 |
var onoa = importer.CreateInstance(TemplateTag.ONOA); |
40 |
var envp = importer.CreateInstance(TemplateTag.ENVP); |
41 |
var crsa = importer.CreateInstance(TemplateTag.CRSA); |
42 |
var onsk = importer.CreateInstance(TemplateTag.ONSK, level.skyName); |
43 |
var akev = importer.CreateInstance(TemplateTag.AKEV, level.name); |
44 |
|
45 |
using (var writer = onlv.OpenWrite()) |
46 |
{ |
47 |
writer.Write(level.name, 64); |
48 |
writer.Write(akev); |
49 |
writer.Write(oboa); |
50 |
writer.Write(0); |
51 |
writer.Write(0); |
52 |
writer.Write(0); |
53 |
writer.Write(onsk); |
54 |
writer.Write(0.0f); |
55 |
writer.Write(aisa); |
56 |
writer.Write(0); |
57 |
writer.Write(0); |
58 |
writer.Write(0); |
59 |
writer.Write(onoa); |
60 |
writer.Write(envp); |
61 |
writer.Skip(644); |
62 |
writer.Write(crsa); |
63 |
} |
64 |
|
65 |
WriteOBOA(oboa); |
66 |
WriteAISA(aisa); |
67 |
WriteONOA(onoa); |
68 |
WriteENVP(envp, level.particles); |
69 |
WriteCRSA(crsa, level.corpses); |
70 |
} |
71 |
|
72 |
private void WriteOBOA(ImporterDescriptor oboa) |
73 |
{ |
74 |
var objects = level.physics; |
75 |
var m3ga = new ImporterDescriptor[objects.Count]; |
76 |
var oban = new ImporterDescriptor[objects.Count]; |
77 |
var envp = new ImporterDescriptor[objects.Count]; |
78 |
|
79 |
for (int i = 0; i < objects.Count; i++) |
80 |
{ |
81 |
var obj = objects[i]; |
82 |
|
83 |
var m3gms = new List<ImporterDescriptor>(); |
84 |
|
85 |
foreach (var geom in obj.Geometries) |
86 |
{ |
87 |
if (geom is string) |
88 |
m3gms.Add(importer.CreateInstance(TemplateTag.M3GM, (string)geom)); |
89 |
else |
90 |
m3gms.Add(Motoko.GeometryDatWriter.Write((Motoko.Geometry)geom, importer.ImporterFile)); |
91 |
} |
92 |
|
93 |
m3ga[i] = importer.CreateInstance(TemplateTag.M3GA); |
94 |
|
95 |
WriteM3GA(m3ga[i], m3gms); |
96 |
|
97 |
if (obj.Animation != null) |
98 |
oban[i] = importer.CreateInstance(TemplateTag.OBAN, obj.Animation.Name); |
99 |
|
100 |
if (obj.Particles.Count > 0) |
101 |
{ |
102 |
envp[i] = importer.CreateInstance(TemplateTag.ENVP); |
103 |
WriteENVP(envp[i], obj.Particles); |
104 |
} |
105 |
} |
106 |
|
107 |
int unused = 32; |
108 |
|
109 |
using (var writer = oboa.OpenWrite(22)) |
110 |
{ |
111 |
writer.WriteUInt16(objects.Count + unused); |
112 |
|
113 |
for (int i = 0; i != objects.Count; i++) |
114 |
{ |
115 |
var obj = objects[i]; |
116 |
|
117 |
writer.Write(m3ga[i]); |
118 |
writer.Write(oban[i]); |
119 |
writer.Write(envp[i]); |
120 |
writer.Write((uint)(obj.Flags | Physics.ObjectSetupFlags.InUse)); |
121 |
//writer.Write(obj.DoorGunkIndex); |
122 |
writer.Write(0); |
123 |
writer.Write(obj.DoorScriptId); |
124 |
writer.Write((uint)obj.PhysicsType); |
125 |
writer.Write(obj.ScriptId); |
126 |
writer.Write(obj.Position); |
127 |
writer.Write(obj.Orientation); |
128 |
writer.Write(obj.Scale); |
129 |
writer.WriteMatrix4x3(obj.Origin); |
130 |
writer.Write(obj.Name, 64); |
131 |
writer.Write(obj.FileName, 64); |
132 |
} |
133 |
|
134 |
writer.Skip(unused * 240); |
135 |
} |
136 |
} |
137 |
|
138 |
private void WriteM3GA(ImporterDescriptor m3ga, ICollection<ImporterDescriptor> geometries) |
139 |
{ |
140 |
using (var writer = m3ga.OpenWrite(20)) |
141 |
{ |
142 |
writer.Write(geometries.Count); |
143 |
writer.Write(geometries); |
144 |
} |
145 |
} |
146 |
|
147 |
private void WriteAISA(ImporterDescriptor aisa) |
148 |
{ |
149 |
var characterClasses = new Dictionary<string, ImporterDescriptor>(StringComparer.Ordinal); |
150 |
var weaponClasses = new Dictionary<string, ImporterDescriptor>(StringComparer.Ordinal); |
151 |
|
152 |
foreach (var chr in level.characters) |
153 |
{ |
154 |
if (!characterClasses.ContainsKey(chr.className)) |
155 |
characterClasses.Add(chr.className, importer.CreateInstance(TemplateTag.ONCC, chr.className)); |
156 |
|
157 |
if (!string.IsNullOrEmpty(chr.weaponClassName) && !weaponClasses.ContainsKey(chr.weaponClassName)) |
158 |
weaponClasses.Add(chr.weaponClassName, importer.CreateInstance(TemplateTag.ONWC, chr.weaponClassName)); |
159 |
} |
160 |
|
161 |
using (var writer = aisa.OpenWrite(22)) |
162 |
{ |
163 |
writer.WriteUInt16(level.characters.Count); |
164 |
|
165 |
foreach (var chr in level.characters) |
166 |
{ |
167 |
ImporterDescriptor characterClass, weaponClass; |
168 |
|
169 |
characterClasses.TryGetValue(chr.className, out characterClass); |
170 |
|
171 |
if (!string.IsNullOrEmpty(chr.weaponClassName)) |
172 |
weaponClasses.TryGetValue(chr.weaponClassName, out weaponClass); |
173 |
else |
174 |
weaponClass = null; |
175 |
|
176 |
writer.Write(chr.name, 32); |
177 |
writer.WriteInt16(chr.scriptId); |
178 |
writer.WriteInt16(chr.flagId); |
179 |
writer.WriteUInt16((ushort)chr.flags); |
180 |
writer.WriteUInt16((ushort)chr.team); |
181 |
writer.Write(characterClass); |
182 |
writer.Skip(36); |
183 |
writer.Write(chr.onSpawn, 32); |
184 |
writer.Write(chr.onDeath, 32); |
185 |
writer.Write(chr.onSeenEnemy, 32); |
186 |
writer.Write(chr.onAlarmed, 32); |
187 |
writer.Write(chr.onHurt, 32); |
188 |
writer.Write(chr.onDefeated, 32); |
189 |
writer.Write(chr.onOutOfAmmo, 32); |
190 |
writer.Write(chr.onNoPath, 32); |
191 |
writer.Write(weaponClass); |
192 |
writer.WriteInt16(chr.ammo); |
193 |
writer.Skip(10); |
194 |
} |
195 |
} |
196 |
} |
197 |
|
198 |
private void WriteONOA(ImporterDescriptor onoa) |
199 |
{ |
200 |
var map = new Dictionary<int, List<int>>(); |
201 |
int pi = 0; |
202 |
|
203 |
foreach (var poly in level.model.Polygons) |
204 |
{ |
205 |
if (poly.ObjectId > 0) |
206 |
{ |
207 |
List<int> indices; |
208 |
|
209 |
int objectId = poly.ObjectType << 24 | poly.ObjectId; |
210 |
|
211 |
if (!map.TryGetValue(objectId, out indices)) |
212 |
{ |
213 |
indices = new List<int>(); |
214 |
map[objectId] = indices; |
215 |
} |
216 |
|
217 |
indices.Add(pi); |
218 |
} |
219 |
|
220 |
pi++; |
221 |
} |
222 |
|
223 |
var elt = new List<KeyValuePair<int, ImporterDescriptor>>(); |
224 |
|
225 |
foreach (var pair in map) |
226 |
{ |
227 |
var idxa = importer.CreateInstance(TemplateTag.IDXA); |
228 |
|
229 |
elt.Add(new KeyValuePair<int, ImporterDescriptor>(pair.Key, idxa)); |
230 |
|
231 |
using (var idxaWriter = idxa.OpenWrite(20)) |
232 |
{ |
233 |
idxaWriter.Write(pair.Value.Count); |
234 |
idxaWriter.Write(pair.Value.ToArray()); |
235 |
} |
236 |
} |
237 |
|
238 |
using (var writer = onoa.OpenWrite(20)) |
239 |
{ |
240 |
writer.Write(elt.Count); |
241 |
|
242 |
foreach (var e in elt) |
243 |
{ |
244 |
writer.Write(e.Key); |
245 |
writer.Write(e.Value); |
246 |
} |
247 |
} |
248 |
} |
249 |
|
250 |
private void WriteENVP(ImporterDescriptor envp, List<Physics.ObjectParticle> particles) |
251 |
{ |
252 |
using (var writer = envp.OpenWrite(22)) |
253 |
{ |
254 |
writer.WriteUInt16(particles.Count); |
255 |
|
256 |
foreach (var particle in particles) |
257 |
{ |
258 |
writer.Write(particle.ParticleClass, 64); |
259 |
writer.Write(particle.Tag, 48); |
260 |
writer.WriteMatrix4x3(particle.Matrix); |
261 |
writer.Write(particle.DecalScale); |
262 |
writer.Write((ushort)particle.Flags); |
263 |
writer.Skip(38); |
264 |
} |
265 |
} |
266 |
} |
267 |
|
268 |
private void WriteCRSA(ImporterDescriptor crsa, List<Corpse> corpses) |
269 |
{ |
270 |
// |
271 |
// Ensure that there are at least 20 corpses |
272 |
// |
273 |
|
274 |
while (corpses.Count < 20) |
275 |
corpses.Add(new Corpse()); |
276 |
|
277 |
int fixedCount = corpses.Count(c => c.IsFixed); |
278 |
int usedCount = corpses.Count(c => c.IsUsed); |
279 |
|
280 |
// |
281 |
// Ensure that there are at least 5 unused corpses |
282 |
// so new corpses can be created at runtime |
283 |
// |
284 |
|
285 |
while (corpses.Count - usedCount < 5) |
286 |
corpses.Add(new Corpse()); |
287 |
|
288 |
corpses.Sort((x, y) => x.Order.CompareTo(y.Order)); |
289 |
|
290 |
var onccDesriptors = new Dictionary<string, ImporterDescriptor>(); |
291 |
|
292 |
using (var writer = crsa.OpenWrite(12)) |
293 |
{ |
294 |
writer.Write(fixedCount); |
295 |
writer.Write(usedCount); |
296 |
writer.Write(corpses.Count); |
297 |
|
298 |
foreach (var corpse in corpses) |
299 |
{ |
300 |
writer.Write(corpse.FileName ?? "", 32); |
301 |
writer.Skip(128); |
302 |
|
303 |
if (corpse.IsUsed) |
304 |
{ |
305 |
ImporterDescriptor oncc = null; |
306 |
|
307 |
if (!string.IsNullOrEmpty(corpse.CharacterClass)) |
308 |
{ |
309 |
if (!onccDesriptors.TryGetValue(corpse.CharacterClass, out oncc)) |
310 |
{ |
311 |
oncc = importer.CreateInstance(TemplateTag.ONCC, corpse.CharacterClass); |
312 |
onccDesriptors.Add(corpse.CharacterClass, oncc); |
313 |
} |
314 |
} |
315 |
|
316 |
writer.Write(oncc); |
317 |
|
318 |
foreach (var transform in corpse.Transforms) |
319 |
writer.WriteMatrix4x3(transform); |
320 |
|
321 |
writer.Write(corpse.BoundingBox); |
322 |
} |
323 |
else |
324 |
{ |
325 |
writer.Skip(940); |
326 |
} |
327 |
} |
328 |
} |
329 |
} |
330 |
} |
331 |
} |