1 module d2dmon
is aliced
;
8 // ////////////////////////////////////////////////////////////////////////// //
78 // ////////////////////////////////////////////////////////////////////////// //
104 // ////////////////////////////////////////////////////////////////////////// //
144 // ////////////////////////////////////////////////////////////////////////// //
145 __gshared ItemTemplate
[ItemType
.max
+1] itemTpls
;
146 __gshared MonsterTemplate
[MonType
.max
+1] monTpls
;
149 // ////////////////////////////////////////////////////////////////////////// //
151 int len
; // in frames
152 this (int alen
) { len
= (alen
< 0 ?
0 : alen
); }
153 override string
toString () const {
154 import std
.string
: format
;
155 return "<noop>(%s)".format(len
);
160 class AnimActionRemove
: AnimAction
{
161 this () { super(0); }
162 override string
toString () const {
163 import std
.string
: format
;
164 return (len
!= 1 ?
"@remove(%s)".format(len
) : "@remove");
169 class AnimActionSpr
: AnimAction
{
171 this (const(char)[] aspr
, int alen
) { super(alen
); spr
= (aspr
!is null ? aspr
.idup
: null); }
172 override string
toString () const {
173 import std
.string
: format
;
174 return (len
!= 1 ?
"%s(%s)".format(spr
, len
) : spr
);
179 class AnimActionJump
: AnimAction
{
180 int dest
; // absolute; <0: unresolved forward
181 this (int adest
) { super(0); dest
= adest
; }
182 override string
toString () const {
183 import std
.string
: format
;
184 return "@goto(%s) %s".format(len
, dest
);
189 class AnimActionJumpState
: AnimActionJump
{
191 this (int adest
, string aname
) { super(adest
); stname
= aname
; }
192 override string
toString () const {
193 import std
.string
: format
;
194 return "@goto(%s) %s".format(len
, dest
);
199 // ////////////////////////////////////////////////////////////////////////// //
200 struct ActionScript
{
203 int[string
] fwdlabels
;
207 AnimAction
[] actions
;
208 int[string
] actlabels
;
219 int getLabelId(bool canadd
=false) (string name
) {
220 if (auto lbi
= name
in actlabels
) return *lbi
;
221 if (auto lbi
= name
in fwdlabels
) return *lbi
;
225 fwdlabels
[name
] = res
;
233 // check unresolved labels
234 foreach (auto kv
; fwdlabels
.byKeyValue
) {
235 if (kv
.value
!= -1) throw new Exception("undefined label: '"~kv
.key
~"'");
242 string
labelAt (int idx
) {
243 foreach (auto kv
; actlabels
.byKeyValue
) if (kv
.value
== idx
) return kv
.key
;
248 private import std
.stdio
: File
;
249 void dump (File fo
) {
250 fo
.writeln(" states {");
251 foreach (immutable idx
, auto act
; actions
) {
252 foreach (auto kv
; actlabels
.byKeyValue
) if (kv
.value
== idx
) fo
.writeln(" ", kv
.key
, ": /*", idx
, "*/");
253 if (auto jp = cast(AnimActionJumpState
)act
) {
254 fo
.write(" @setstate");
255 if (jp.len
!= 0) fo
.write("(", jp.len
, ")");
256 fo
.write(" ", jp.stname
);
257 auto lab
= labelAt(jp.dest
);
258 if (lab
!= jp.stname
) fo
.write(", ", lab
);
260 } else if (auto jp = cast(AnimActionJump
)act
) {
262 if (jp.len
!= 0) fo
.write("(", jp.len
, ")");
263 fo
.writeln(" ", labelAt(jp.dest
), ";");
265 fo
.writeln(" ", act
.toString
, ";");
273 // ////////////////////////////////////////////////////////////////////////// //
274 void parseCommand(PR
) (ref ActionScript asc
, ref PR parser
, bool allowFrameCount
) {
275 int parseFrameCount () {
276 if (!allowFrameCount
) return 0;
277 if (parser
.tk
== parser
.TType
.Delim
&& parser
.tchar
== '(') {
280 if (parser
.tnum
< 0) parser
.error("positive number expected");
281 auto res
= parser
.tnum
;
283 parser
.skipDelim(')');
289 switch (parser
.tstr
) {
292 asc
.actions
~= new AnimActionRemove();
296 if (asc
.lastLabel
.length
== 0) parser
.error("there is no label to loop");
298 asc
.actions
~= new AnimActionJump(asc
.getLabelId
!true(asc
.lastLabel
));
299 asc
.actions
[$-1].len
= parseFrameCount();
304 asc
.actions
~= new AnimActionJump(asc
.getLabelId
!true(parser
.tstr
.idup
));
306 asc
.actions
[$-1].len
= parseFrameCount();
308 case "@setstate": // state[label]
311 auto st
= parser
.tstr
.idup
;
314 if (parser
.tk
== parser
.TType
.Delim
&& parser
.tchar
== '[') {
317 lb
= parser
.tstr
.idup
;
319 parser
.skipDelim(']');
321 asc
.actions
~= new AnimActionJumpState(asc
.getLabelId
!true(lb
), st
);
322 asc
.actions
[$-1].len
= parseFrameCount();
327 parser
.skipDelim('(');
330 parser
.skipDelim(')');
332 default: parser
.error("unknown command: '"~parser
.tstr
.idup
~"'");
337 // open "{" skipped, eats closing "}"
338 void parseStates(PR
) (ref ActionScript asc
, ref PR parser
) {
340 if (parser
.tk
== parser
.TType
.Delim
) {
341 if (parser
.tchar
== '}') { parser
.popFront(); break; }
342 if (parser
.tchar
== ';') { parser
.popFront(); continue mainloop
; }
343 parser
.error("unexpected token");
347 if (parser
.isLabel
) {
348 asc
.lastLabel
= parser
.tstr
[0..$-1].idup
;
349 if (parser
.isCommand
) parser
.error("invalid label name: '"~asc
.lastLabel
~"'");
350 if (asc
.lastLabel
in asc
.actlabels
) parser
.error("duplicate label: '"~asc
.lastLabel
~"'");
352 asc
.actlabels
[asc
.lastLabel
] = cast(int)asc
.actions
.length
;
353 if (auto lbidx
= asc
.lastLabel
in asc
.fwdlabels
) {
354 // fix forward references
355 foreach (AnimAction act
; asc
.actions
) {
356 if (auto jp = cast(AnimActionJump
)act
) {
357 if (jp.dest
== *lbidx
) jp.dest
= cast(int)asc
.actions
.length
;
360 *lbidx
= -1; // mark as processed
365 if (parser
.isCommand
) {
366 asc
.parseCommand(parser
, true);
367 parser
.skipDelim(';');
371 string spr
= parser
.tstr
.idup
;
374 if (parser
.tk
== parser
.TType
.Delim
&& parser
.tchar
== '(') {
377 if (parser
.tnum
< 0) parser
.error("positive number expected");
378 frcount
= parser
.tnum
;
380 parser
.skipDelim(')');
382 asc
.actions
~= new AnimActionSpr(spr
, frcount
);
384 if (parser
.isCommand
) {
385 // sprite action should last 0 frames, last command should last frcount frames
386 while (parser
.isCommand
) {
387 asc
.actions
[$-1].len
= 0;
388 asc
.parseCommand(parser
, false);
389 asc
.actions
[$-1].len
= frcount
;
392 parser
.skipDelim(';');
397 // ////////////////////////////////////////////////////////////////////////// //
398 class ThingTemplate
{
402 string classname
= null;
403 string name
; // thing name
404 @FieldIntPos int radius
= -1;
405 @FieldIntPos int height
= -1;
406 @FieldIntPos int hitpoints
= -1;
407 @FieldStr string base
= null;
408 ActionScript asc
; // action script
410 // should be at starting "{"
411 this(PR
) (ref PR parser
) {
412 if (classname
is null) classname
= "Thing";
414 parser
.popFront(); // skip name
415 parser
.skipDelim('=');
416 if (parser
.tk
!= parser
.TType
.Num
) parser
.error("number expected");
417 if (parser
.tnum
< 0) parser
.error("positive number expected");
418 int res
= parser
.tnum
;
419 parser
.popFront(); // skip number
420 parser
.skipDelim(';');
425 parser
.popFront(); // skip name
426 parser
.skipDelim('=');
427 if (parser
.tk
!= parser
.TType
.Str
) parser
.error("string expected");
428 string res
= parser
.tstr
.idup
;
429 parser
.popFront(); // skip string
430 parser
.skipDelim(';');
435 name
= parser
.tstr
.idup
;
437 parser
.skipDelim('{');
439 if (parser
.empty
) parser
.error("unexpected end of file");
440 if (parser
.tk
== parser
.TType
.Delim
) {
441 if (parser
.tchar
== '}') { parser
.popFront(); break; }
442 if (parser
.tchar
== ';') { parser
.popFront(); continue mainloop
; }
443 parser
.error("unexpected token");
446 if (parser
.tstr
== "states") {
448 parser
.skipDelim('{');
449 asc
.parseStates(parser
);
452 auto fld = findField(parser
.tstr
);
453 if (!fld.valid
) parser
.error("unexpected token: '"~parser
.tstr
.idup
~"'");
455 case fld.Type
.IntPos
: fld = getEqIntPos(); break;
456 case fld.Type
.Str
: fld = getStr(); break;
457 default: parser
.error("invalid type for field '"~fld.name
~"'");
464 final void err (string msg
) { throw new Exception("'"~name
~"': "~msg
); }
467 if (radius
< 0) err("undefined radius");
468 if (height
< 0) err("undefined height");
469 if (hitpoints
< 0) hitpoints
= 0;
470 if (base
is null) err("undefined base path");
477 IntPos
, // positive integer
484 @property bool valid () const pure nothrow @safe @nogc => (ptr
!is null);
486 void opAssign (int v
) {
487 if (ptr
is null) throw new Exception("cannot assign to inexistent field '"~name
~"'");
490 if (v
< 0) throw new Exception("cannot assign negative number to intpos field '"~name
~"'");
491 *(cast(int*)ptr
) = v
;
494 import std
.conv
: to
;
495 *(cast(string
*)ptr
) = to
!string(v
);
497 default: throw new Exception("cannot assign number to invalid typed field '"~name
~"'");
501 void opAssign(T
) (T v
) if (is(T
== char[]) ||
is(T
== const(char)[]) ||
is(T
== immutable(char)[])) {
502 if (ptr
is null) throw new Exception("cannot assign to inexistent field '"~name
~"'");
505 import std
.conv
: to
;
507 if (n
< 0) throw new Exception("cannot assign negative number to intpos field '"~name
~"'");
508 *(cast(int*)ptr
) = n
;
512 *(cast(string
*)ptr
) = null;
514 static if (is(T
== immutable(char)[])) {
515 *(cast(string
*)ptr
) = v
;
517 *(cast(string
*)ptr
) = v
.idup
;
521 default: throw new Exception("cannot assign string to invalid typed field '"~name
~"'");
526 mixin(FindFieldMixin
);
528 enum FindFieldMixin
= q
{
529 FieldInfo
findField (const(char)[] name
) {
530 foreach (string mem
; __traits(allMembers
, typeof(this))) {
531 static if (is(typeof(mixin("this."~mem
)))) {
533 import std
.traits
: hasUDA
;
534 static if (hasUDA
!(mixin("this."~mem
), FieldIntPos
)) {
535 return FieldInfo(FieldInfo
.Type
.IntPos
, mixin("cast(void*)&(this."~mem
~")"), mem
);
536 } else static if (hasUDA
!(mixin("this."~mem
), FieldStr
)) {
537 return FieldInfo(FieldInfo
.Type
.Str
, mixin("cast(void*)&(this."~mem
~")"), mem
);
542 return FieldInfo(FieldInfo
.Type
.None
, null, "<null>");
546 private import std
.stdio
: File
;
548 void dump (File fo
) {
549 fo
.writeln(classname
, " ", name
, " {");
550 foreach (string mem
; __traits(allMembers
, typeof(this))) {
551 static if (is(typeof(mixin("this."~mem
)))) {
552 import std
.traits
: hasUDA
;
553 static if (hasUDA
!(mixin("this."~mem
), FieldIntPos
)) {
554 mixin("fo.writeln(` "~mem
~" = `, this."~mem
~", `;`);");
555 } else static if (hasUDA
!(mixin("this."~mem
), FieldStr
)) {
556 mixin("fo.writeln(` "~mem
~" = \"`, this."~mem
~", `\";`);");
562 fo
.writeln("//=============");
569 // ////////////////////////////////////////////////////////////////////////// //
570 final class ItemTemplate
: ThingTemplate
{
571 mixin("override "~FindFieldMixin
);
572 mixin("override "~DumpMixin
);
574 override void checkProps () {
579 this(PR
) (ref PR parser
) { classname
= "Item"; super(parser
); }
583 // ////////////////////////////////////////////////////////////////////////// //
584 final class MonsterTemplate
: ThingTemplate
{
585 @FieldIntPos int painin
= -1;
586 @FieldIntPos int xvel
= -1;
587 @FieldIntPos int yvel
= -1;
588 @FieldIntPos int slophit
= -1;
589 @FieldIntPos int painout
= -1;
591 mixin("override "~FindFieldMixin
);
592 mixin("override "~DumpMixin
);
594 override void checkProps () {
595 if (hitpoints
< 0) err("undefined hitpoints");
596 if (painin
< 0) painin
= 0;
597 if (xvel
< 0) xvel
= 0;
598 if (yvel
< 0) yvel
= 0;
599 if (slophit
< 0) slophit
= 0;
600 if (painout
< 0) painout
= 0;
604 // "Actor" is skipped
605 this(PR
) (ref PR parser
) { classname
= "Actor"; super(parser
); }
609 // ////////////////////////////////////////////////////////////////////////// //
610 public void loadTemplates () {
611 import std
.conv
: to
;
612 conwriteln("loading monsters...");
614 auto parser
= TextParser(loadTextFile("monsters.txt"));
615 while (!parser
.empty
) {
616 if (parser
.tstr
!= "Actor") parser
.error("'Actor' expected, got '"~parser
.tstr
.idup
~"'");
621 midx
= cast(int)(to
!MonType(parser
.tstr
));
622 } catch (Exception e
) {
623 conwriteln("unknown monster: '", parser
.tstr
, "'");
626 if (midx
< 1 || midx
> MonType
.max
) {
627 conwriteln("unknown monster: '", parser
.tstr
, "'");
630 monTpls
[midx
] = new MonsterTemplate(parser
);
631 conwriteln(monTpls
[midx
].name
, ": loaded");
635 conwriteln("loading items...");
637 auto parser
= TextParser(loadTextFile("items.txt"));
638 while (!parser
.empty
) {
639 if (parser
.tstr
!= "Item") parser
.error("'Item' expected, got '"~parser
.tstr
.idup
~"'");
644 midx
= cast(int)(to
!ItemType(parser
.tstr
));
645 } catch (Exception e
) {
646 conwriteln("unknown item: '", parser
.tstr
, "'");
649 if (midx
< 1 || midx
> ItemType
.max
) {
650 conwriteln("unknown item: '", parser
.tstr
, "'");
653 itemTpls
[midx
] = new ItemTemplate(parser
);
654 conwriteln(itemTpls
[midx
].name
, ": loaded");