1 |
using System; |
2 |
using System.Collections.Generic; |
3 |
using System.Xml; |
4 |
using Oni.Metadata; |
5 |
|
6 |
namespace Oni.Xml |
7 |
{ |
8 |
internal class ObjcXmlImporter : RawXmlImporter |
9 |
{ |
10 |
private readonly Dictionary<ObjectMetadata.TypeTag, Action> typeReaders = new Dictionary<ObjectMetadata.TypeTag, Action>(); |
11 |
private int nextId; |
12 |
|
13 |
private ObjcXmlImporter(XmlReader xml, BinaryWriter writer) |
14 |
: base(xml, writer) |
15 |
{ |
16 |
InitTypeReaders(typeReaders); |
17 |
} |
18 |
|
19 |
public static void Import(XmlReader xml, BinaryWriter writer) |
20 |
{ |
21 |
var importer = new ObjcXmlImporter(xml, writer); |
22 |
importer.Import(); |
23 |
} |
24 |
|
25 |
private void Import() |
26 |
{ |
27 |
Writer.Write(39); |
28 |
|
29 |
nextId = 1; |
30 |
|
31 |
while (Xml.IsStartElement()) |
32 |
{ |
33 |
int objectStartPosition = Writer.Position; |
34 |
Writer.Write(0); |
35 |
|
36 |
BeginStruct(objectStartPosition); |
37 |
|
38 |
ReadObject(); |
39 |
|
40 |
Writer.Position = Utils.Align4(Writer.Position); |
41 |
|
42 |
int objectSize = Writer.Position - objectStartPosition - 4; |
43 |
Writer.WriteAt(objectStartPosition, objectSize); |
44 |
} |
45 |
|
46 |
Writer.Write(0); |
47 |
} |
48 |
|
49 |
private ObjectMetadata.TypeTag ReadObject() |
50 |
{ |
51 |
var id = Xml.GetAttribute("Id"); |
52 |
int objectId = string.IsNullOrEmpty(id) ? nextId++ : XmlConvert.ToInt32(id); |
53 |
var objectTag = Xml.GetAttribute("Type"); |
54 |
|
55 |
if (objectTag == null) |
56 |
objectTag = Xml.LocalName; |
57 |
|
58 |
var objectType = MetaEnum.Parse<ObjectMetadata.TypeTag>(objectTag); |
59 |
|
60 |
Xml.ReadStartElement(); |
61 |
Xml.MoveToContent(); |
62 |
|
63 |
Writer.Write((int)objectType); |
64 |
Writer.Write(objectId); |
65 |
ObjectMetadata.Header.Accept(this); |
66 |
typeReaders[objectType](); |
67 |
|
68 |
Xml.ReadEndElement(); |
69 |
|
70 |
return objectType; |
71 |
} |
72 |
|
73 |
private void ReadCharacter() |
74 |
{ |
75 |
ObjectMetadata.Character.Accept(this); |
76 |
} |
77 |
|
78 |
private void ReadCombatProfile() |
79 |
{ |
80 |
ObjectMetadata.CombatProfile.Accept(this); |
81 |
} |
82 |
|
83 |
private void ReadConsole() |
84 |
{ |
85 |
Xml.ReadStartElement(); |
86 |
Xml.MoveToContent(); |
87 |
|
88 |
ReadStruct(ObjectMetadata.Console); |
89 |
ReadEventList(); |
90 |
|
91 |
Xml.ReadEndElement(); |
92 |
} |
93 |
|
94 |
private void ReadDoor() |
95 |
{ |
96 |
Xml.ReadStartElement(); |
97 |
Xml.MoveToContent(); |
98 |
|
99 |
ReadStruct(ObjectMetadata.Door); |
100 |
ReadEventList(); |
101 |
|
102 |
Xml.ReadEndElement(); |
103 |
} |
104 |
|
105 |
private void ReadFlag() |
106 |
{ |
107 |
ObjectMetadata.Flag.Accept(this); |
108 |
} |
109 |
|
110 |
private void ReadFurniture() |
111 |
{ |
112 |
ObjectMetadata.Furniture.Accept(this); |
113 |
} |
114 |
|
115 |
private struct MeleeMove |
116 |
{ |
117 |
public int Type; |
118 |
public float[] Params; |
119 |
} |
120 |
|
121 |
private void ReadMeleeProfile() |
122 |
{ |
123 |
Xml.ReadStartElement(); |
124 |
Xml.MoveToContent(); |
125 |
|
126 |
ReadStruct(ObjectMetadata.MeleeProfile); |
127 |
|
128 |
int countFieldsPosition = Writer.Position; |
129 |
Writer.Write(0); |
130 |
Writer.Write(0); |
131 |
Writer.Write(0); |
132 |
Writer.Write(0); |
133 |
|
134 |
int attackCount = 0; |
135 |
int evadeCount = 0; |
136 |
int maneuverCount = 0; |
137 |
var moves = new List<MeleeMove>(); |
138 |
|
139 |
attackCount = ReadMeleeTechniques("Attacks", moves); |
140 |
evadeCount = ReadMeleeTechniques("Evades", moves); |
141 |
maneuverCount = ReadMeleeTechniques("Maneuvers", moves); |
142 |
|
143 |
foreach (var move in moves) |
144 |
{ |
145 |
Writer.Write(move.Type); |
146 |
Writer.Write(move.Params); |
147 |
} |
148 |
|
149 |
int oldPosition = Writer.Position; |
150 |
Writer.Position = countFieldsPosition; |
151 |
Writer.Write(attackCount); |
152 |
Writer.Write(evadeCount); |
153 |
Writer.Write(maneuverCount); |
154 |
Writer.Write(moves.Count); |
155 |
Writer.Position = oldPosition; |
156 |
|
157 |
Xml.ReadEndElement(); |
158 |
} |
159 |
|
160 |
private int ReadMeleeTechniques(string xmlName, List<MeleeMove> moves) |
161 |
{ |
162 |
if (Xml.IsStartElement(xmlName) && Xml.IsEmptyElement) |
163 |
{ |
164 |
Xml.Skip(); |
165 |
return 0; |
166 |
} |
167 |
|
168 |
Xml.ReadStartElement(xmlName); |
169 |
Xml.MoveToContent(); |
170 |
|
171 |
int techniqueCount = 0; |
172 |
|
173 |
for (; Xml.IsStartElement("Technique"); techniqueCount++) |
174 |
{ |
175 |
Xml.ReadStartElement(); |
176 |
Xml.MoveToContent(); |
177 |
|
178 |
ReadStruct(ObjectMetadata.MeleeTechnique); |
179 |
int moveCountPosition = Writer.Position; |
180 |
Writer.Write(0); |
181 |
Writer.Write(moves.Count); |
182 |
|
183 |
int moveCount = 0; |
184 |
|
185 |
if (Xml.IsStartElement("Moves") && Xml.IsEmptyElement) |
186 |
{ |
187 |
Xml.Skip(); |
188 |
} |
189 |
else |
190 |
{ |
191 |
Xml.ReadStartElement("Moves"); |
192 |
Xml.MoveToContent(); |
193 |
|
194 |
for (; Xml.IsStartElement(); moveCount++) |
195 |
{ |
196 |
ReadMeleeMove(moves); |
197 |
} |
198 |
|
199 |
Xml.ReadEndElement(); |
200 |
} |
201 |
|
202 |
Xml.ReadEndElement(); |
203 |
|
204 |
Writer.WriteAt(moveCountPosition, moveCount); |
205 |
} |
206 |
|
207 |
Xml.ReadEndElement(); |
208 |
|
209 |
return techniqueCount; |
210 |
} |
211 |
|
212 |
private void ReadMeleeMove(List<MeleeMove> moves) |
213 |
{ |
214 |
var category = (ObjectMetadata.MeleeMoveCategory)Enum.Parse(typeof(ObjectMetadata.MeleeMoveCategory), Xml.LocalName); |
215 |
|
216 |
var typeText = Xml.GetAttribute("Type"); |
217 |
int type; |
218 |
var moveParams = new float[3]; |
219 |
|
220 |
switch (category) |
221 |
{ |
222 |
default: |
223 |
case ObjectMetadata.MeleeMoveCategory.Attack: |
224 |
type = Convert.ToInt32(MetaEnum.Parse<ObjectMetadata.MeleeMoveAttackType>(typeText)); |
225 |
break; |
226 |
case ObjectMetadata.MeleeMoveCategory.Evade: |
227 |
type = Convert.ToInt32(MetaEnum.Parse<ObjectMetadata.MeleeMoveEvadeType>(typeText)); |
228 |
break; |
229 |
case ObjectMetadata.MeleeMoveCategory.Throw: |
230 |
type = Convert.ToInt32(MetaEnum.Parse<ObjectMetadata.MeleeMoveThrowType>(typeText)); |
231 |
break; |
232 |
|
233 |
case ObjectMetadata.MeleeMoveCategory.Position: |
234 |
ObjectMetadata.MeleeMovePositionType positionType = MetaEnum.Parse<ObjectMetadata.MeleeMovePositionType>(typeText); |
235 |
|
236 |
if ((ObjectMetadata.MeleeMovePositionType.RunForward <= positionType |
237 |
&& positionType <= ObjectMetadata.MeleeMovePositionType.RunBack) |
238 |
|| ObjectMetadata.MeleeMovePositionType.CloseForward <= positionType) |
239 |
{ |
240 |
moveParams[0] = XmlConvert.ToSingle(Xml.GetAttribute("MinRunInDist")); |
241 |
moveParams[1] = XmlConvert.ToSingle(Xml.GetAttribute("MaxRunInDist")); |
242 |
moveParams[2] = XmlConvert.ToSingle(Xml.GetAttribute("ToleranceRange")); |
243 |
} |
244 |
|
245 |
type = Convert.ToInt32(positionType); |
246 |
break; |
247 |
|
248 |
case ObjectMetadata.MeleeMoveCategory.Maneuver: |
249 |
type = Convert.ToInt32(MetaEnum.Parse<ObjectMetadata.MeleeMoveManeuverType>(typeText)); |
250 |
ObjectMetadata.MeleeMoveTypeInfo typeInfo = ObjectMetadata.MeleeMoveManeuverTypeInfo[type]; |
251 |
|
252 |
for (int k = 0; k < typeInfo.ParamNames.Length; k++) |
253 |
moveParams[k] = XmlConvert.ToSingle(Xml.GetAttribute(typeInfo.ParamNames[k])); |
254 |
|
255 |
break; |
256 |
} |
257 |
|
258 |
moves.Add(new MeleeMove() |
259 |
{ |
260 |
Type = (((int)category) << 24) | (type & 0xffffff), |
261 |
Params = moveParams |
262 |
}); |
263 |
|
264 |
Xml.Skip(); |
265 |
} |
266 |
|
267 |
private void ReadNeutralBehavior() |
268 |
{ |
269 |
Xml.ReadStartElement(); |
270 |
Xml.MoveToContent(); |
271 |
|
272 |
ReadStruct(ObjectMetadata.NeutralBehavior); |
273 |
int countFieldPosition = Writer.Position; |
274 |
Writer.WriteUInt16(0); |
275 |
ReadStruct(ObjectMetadata.NeutralBehaviorParams); |
276 |
|
277 |
Xml.ReadStartElement("DialogLines"); |
278 |
short count = 0; |
279 |
|
280 |
for (; Xml.IsStartElement("DialogLine"); count++) |
281 |
ObjectMetadata.NeutralBehaviorDialogLine.Accept(this); |
282 |
|
283 |
Xml.ReadEndElement(); |
284 |
Xml.ReadEndElement(); |
285 |
|
286 |
Writer.WriteAt(countFieldPosition, count); |
287 |
} |
288 |
|
289 |
private void ReadParticle() |
290 |
{ |
291 |
ObjectMetadata.Particle.Accept(this); |
292 |
} |
293 |
|
294 |
private void ReadPatrolPath() |
295 |
{ |
296 |
Xml.ReadStartElement(); |
297 |
Xml.MoveToContent(); |
298 |
|
299 |
ReadStruct(ObjectMetadata.PatrolPath); |
300 |
int lengthFieldPosition = Writer.Position; |
301 |
Writer.Write(0); |
302 |
ReadStruct(ObjectMetadata.PatrolPathInfo); |
303 |
|
304 |
int count = 0; |
305 |
bool isEmpty = Xml.IsEmptyElement; |
306 |
|
307 |
Xml.ReadStartElement("Points"); |
308 |
|
309 |
if (!isEmpty) |
310 |
{ |
311 |
int loopStart = -1; |
312 |
|
313 |
for (; Xml.IsStartElement(); count++) |
314 |
{ |
315 |
if (ReadPatrolPathPoint()) |
316 |
loopStart = count; |
317 |
} |
318 |
|
319 |
if (loopStart != -1) |
320 |
{ |
321 |
if (loopStart == 0) |
322 |
{ |
323 |
Writer.Write((int)ObjectMetadata.PatrolPathPointType.Loop); |
324 |
} |
325 |
else |
326 |
{ |
327 |
Writer.Write((int)ObjectMetadata.PatrolPathPointType.LoopFrom); |
328 |
Writer.Write(loopStart); |
329 |
} |
330 |
|
331 |
if (Xml.NodeType == XmlNodeType.EndElement && Xml.LocalName == "Loop") |
332 |
Xml.ReadEndElement(); |
333 |
} |
334 |
|
335 |
Xml.ReadEndElement(); |
336 |
} |
337 |
|
338 |
Xml.ReadEndElement(); |
339 |
|
340 |
Writer.WriteAt(lengthFieldPosition, count); |
341 |
} |
342 |
|
343 |
private bool ReadPatrolPathPoint() |
344 |
{ |
345 |
var pointType = MetaEnum.Parse<ObjectMetadata.PatrolPathPointType>(Xml.LocalName); |
346 |
|
347 |
switch (pointType) |
348 |
{ |
349 |
case ObjectMetadata.PatrolPathPointType.Loop: |
350 |
if (Xml.IsEmptyElement) |
351 |
{ |
352 |
Xml.Skip(); |
353 |
} |
354 |
else |
355 |
{ |
356 |
Xml.ReadStartElement(); |
357 |
Xml.MoveToContent(); |
358 |
} |
359 |
return true; |
360 |
} |
361 |
|
362 |
Writer.Write((int)pointType); |
363 |
|
364 |
switch (pointType) |
365 |
{ |
366 |
case ObjectMetadata.PatrolPathPointType.Stop: |
367 |
case ObjectMetadata.PatrolPathPointType.StopLooking: |
368 |
case ObjectMetadata.PatrolPathPointType.StopScanning: |
369 |
case ObjectMetadata.PatrolPathPointType.FreeFacing: |
370 |
break; |
371 |
|
372 |
case ObjectMetadata.PatrolPathPointType.IgnorePlayer: |
373 |
Writer.WriteByte(Xml.GetAttribute("Value") == "Yes" ? 1 : 0); |
374 |
break; |
375 |
|
376 |
case ObjectMetadata.PatrolPathPointType.ForkScript: |
377 |
case ObjectMetadata.PatrolPathPointType.CallScript: |
378 |
Writer.Write(XmlConvert.ToInt16(Xml.GetAttribute("ScriptId"))); |
379 |
break; |
380 |
|
381 |
case ObjectMetadata.PatrolPathPointType.MoveToFlag: |
382 |
case ObjectMetadata.PatrolPathPointType.LookAtFlag: |
383 |
case ObjectMetadata.PatrolPathPointType.MoveAndFaceFlag: |
384 |
Writer.Write(XmlConvert.ToInt16(Xml.GetAttribute("FlagId"))); |
385 |
break; |
386 |
|
387 |
case ObjectMetadata.PatrolPathPointType.MovementMode: |
388 |
Writer.Write(Convert.ToInt32(MetaEnum.Parse<ObjectMetadata.PatrolPathMovementMode>(Xml.GetAttribute("Mode")))); |
389 |
break; |
390 |
|
391 |
case ObjectMetadata.PatrolPathPointType.LockFacing: |
392 |
Writer.Write(Convert.ToInt32(MetaEnum.Parse<ObjectMetadata.PatrolPathFacing>(Xml.GetAttribute("Facing")))); |
393 |
break; |
394 |
|
395 |
case ObjectMetadata.PatrolPathPointType.Pause: |
396 |
Writer.Write(XmlConvert.ToInt32(Xml.GetAttribute("Frames"))); |
397 |
break; |
398 |
|
399 |
case ObjectMetadata.PatrolPathPointType.GlanceAtFlagFor: |
400 |
Writer.Write(XmlConvert.ToInt16(Xml.GetAttribute("FlagId"))); |
401 |
Writer.Write(XmlConvert.ToInt32(Xml.GetAttribute("Frames"))); |
402 |
break; |
403 |
|
404 |
case ObjectMetadata.PatrolPathPointType.Scan: |
405 |
Writer.Write(XmlConvert.ToInt16(Xml.GetAttribute("Frames"))); |
406 |
Writer.Write(XmlConvert.ToSingle(Xml.GetAttribute("Rotation"))); |
407 |
break; |
408 |
|
409 |
case ObjectMetadata.PatrolPathPointType.MoveThroughFlag: |
410 |
case ObjectMetadata.PatrolPathPointType.MoveNearFlag: |
411 |
Writer.Write(XmlConvert.ToInt16(Xml.GetAttribute("FlagId"))); |
412 |
Writer.Write(XmlConvert.ToSingle(Xml.GetAttribute("Distance"))); |
413 |
break; |
414 |
|
415 |
case ObjectMetadata.PatrolPathPointType.MoveToFlagLookAndWait: |
416 |
Writer.Write(XmlConvert.ToInt16(Xml.GetAttribute("Frames"))); |
417 |
Writer.Write(XmlConvert.ToInt16(Xml.GetAttribute("FlagId"))); |
418 |
Writer.Write(XmlConvert.ToSingle(Xml.GetAttribute("Rotation"))); |
419 |
break; |
420 |
|
421 |
case ObjectMetadata.PatrolPathPointType.FaceToFlagAndFire: |
422 |
Writer.Write(XmlConvert.ToInt16(Xml.GetAttribute("FlagId"))); |
423 |
Writer.Write(XmlConvert.ToInt16(Xml.GetAttribute("Frames"))); |
424 |
Writer.Write(XmlConvert.ToSingle(Xml.GetAttribute("Spread"))); |
425 |
break; |
426 |
|
427 |
case ObjectMetadata.PatrolPathPointType.LookAtPoint: |
428 |
case ObjectMetadata.PatrolPathPointType.MoveToPoint: |
429 |
Writer.Write(XmlConvert.ToSingle(Xml.GetAttribute("X"))); |
430 |
Writer.Write(XmlConvert.ToSingle(Xml.GetAttribute("Y"))); |
431 |
Writer.Write(XmlConvert.ToSingle(Xml.GetAttribute("Z"))); |
432 |
break; |
433 |
|
434 |
case ObjectMetadata.PatrolPathPointType.MoveThroughPoint: |
435 |
Writer.Write(XmlConvert.ToSingle(Xml.GetAttribute("X"))); |
436 |
Writer.Write(XmlConvert.ToSingle(Xml.GetAttribute("Y"))); |
437 |
Writer.Write(XmlConvert.ToSingle(Xml.GetAttribute("Z"))); |
438 |
Writer.Write(XmlConvert.ToSingle(Xml.GetAttribute("Distance"))); |
439 |
break; |
440 |
|
441 |
default: |
442 |
throw new NotSupportedException(string.Format("Unsupported path point type {0}", pointType)); |
443 |
} |
444 |
|
445 |
Xml.Skip(); |
446 |
|
447 |
return false; |
448 |
} |
449 |
|
450 |
private void ReadPowerUp() |
451 |
{ |
452 |
ObjectMetadata.PowerUp.Accept(this); |
453 |
} |
454 |
|
455 |
private void ReadSound() |
456 |
{ |
457 |
Xml.ReadStartElement(); |
458 |
Xml.MoveToContent(); |
459 |
|
460 |
ReadStruct(ObjectMetadata.Sound); |
461 |
|
462 |
var volumeType = MetaEnum.Parse<ObjectMetadata.SoundVolumeType>(Xml.LocalName); |
463 |
|
464 |
Writer.Write((int)volumeType); |
465 |
|
466 |
switch (volumeType) |
467 |
{ |
468 |
case ObjectMetadata.SoundVolumeType.Box: |
469 |
MetaType.BoundingBox.Accept(this); |
470 |
break; |
471 |
|
472 |
case ObjectMetadata.SoundVolumeType.Sphere: |
473 |
ObjectMetadata.SoundSphere.Accept(this); |
474 |
break; |
475 |
} |
476 |
|
477 |
ReadStruct(ObjectMetadata.SoundParams); |
478 |
|
479 |
if (volumeType == ObjectMetadata.SoundVolumeType.Sphere) |
480 |
Writer.Skip(16); |
481 |
|
482 |
Xml.ReadEndElement(); |
483 |
} |
484 |
|
485 |
private void ReadTriggerVolume() |
486 |
{ |
487 |
ObjectMetadata.TriggerVolume.Accept(this); |
488 |
} |
489 |
|
490 |
private void ReadTrigger() |
491 |
{ |
492 |
Xml.ReadStartElement(); |
493 |
Xml.MoveToContent(); |
494 |
|
495 |
ReadStruct(ObjectMetadata.Trigger); |
496 |
ReadEventList(); |
497 |
|
498 |
Xml.ReadEndElement(); |
499 |
} |
500 |
|
501 |
private void ReadTurret() |
502 |
{ |
503 |
ObjectMetadata.Turret.Accept(this); |
504 |
} |
505 |
|
506 |
private void ReadWeapon() |
507 |
{ |
508 |
ObjectMetadata.Weapon.Accept(this); |
509 |
} |
510 |
|
511 |
private void ReadEventList() |
512 |
{ |
513 |
int countFieldPosition = Writer.Position; |
514 |
Writer.WriteUInt16(0); |
515 |
|
516 |
if (Xml.IsStartElement("Events") && Xml.IsEmptyElement) |
517 |
{ |
518 |
Xml.ReadStartElement(); |
519 |
return; |
520 |
} |
521 |
|
522 |
Xml.ReadStartElement("Events"); |
523 |
Xml.MoveToContent(); |
524 |
|
525 |
short eventCount = 0; |
526 |
|
527 |
while (Xml.IsStartElement()) |
528 |
{ |
529 |
var eventType = MetaEnum.Parse<ObjectMetadata.EventType>(Xml.Name); |
530 |
|
531 |
Writer.Write((short)eventType); |
532 |
|
533 |
switch (eventType) |
534 |
{ |
535 |
case ObjectMetadata.EventType.None: |
536 |
break; |
537 |
case ObjectMetadata.EventType.Script: |
538 |
Writer.Write(Xml.GetAttribute("Function"), 32); |
539 |
break; |
540 |
default: |
541 |
Writer.Write(XmlConvert.ToInt16(Xml.GetAttribute("TargetId"))); |
542 |
break; |
543 |
} |
544 |
|
545 |
eventCount++; |
546 |
Xml.Skip(); |
547 |
} |
548 |
|
549 |
Writer.WriteAt(countFieldPosition, eventCount); |
550 |
Xml.ReadEndElement(); |
551 |
} |
552 |
|
553 |
private void InitTypeReaders(Dictionary<ObjectMetadata.TypeTag, Action> typeReaders) |
554 |
{ |
555 |
typeReaders.Add(ObjectMetadata.TypeTag.CHAR, ReadCharacter); |
556 |
typeReaders.Add(ObjectMetadata.TypeTag.CMBT, ReadCombatProfile); |
557 |
typeReaders.Add(ObjectMetadata.TypeTag.CONS, ReadConsole); |
558 |
typeReaders.Add(ObjectMetadata.TypeTag.DOOR, ReadDoor); |
559 |
typeReaders.Add(ObjectMetadata.TypeTag.FLAG, ReadFlag); |
560 |
typeReaders.Add(ObjectMetadata.TypeTag.FURN, ReadFurniture); |
561 |
typeReaders.Add(ObjectMetadata.TypeTag.MELE, ReadMeleeProfile); |
562 |
typeReaders.Add(ObjectMetadata.TypeTag.NEUT, ReadNeutralBehavior); |
563 |
typeReaders.Add(ObjectMetadata.TypeTag.PART, ReadParticle); |
564 |
typeReaders.Add(ObjectMetadata.TypeTag.PATR, ReadPatrolPath); |
565 |
typeReaders.Add(ObjectMetadata.TypeTag.PWRU, ReadPowerUp); |
566 |
typeReaders.Add(ObjectMetadata.TypeTag.SNDG, ReadSound); |
567 |
typeReaders.Add(ObjectMetadata.TypeTag.TRGV, ReadTriggerVolume); |
568 |
typeReaders.Add(ObjectMetadata.TypeTag.TRIG, ReadTrigger); |
569 |
typeReaders.Add(ObjectMetadata.TypeTag.TURR, ReadTurret); |
570 |
typeReaders.Add(ObjectMetadata.TypeTag.WEAP, ReadWeapon); |
571 |
} |
572 |
} |
573 |
} |