most console input line management moved to "iv.cmdcon"
[dd2d.git] / dengapi.d
blob46c6e16f357566dd66f40daeca035d9882cf0d29
1 /* DooM2D: Midnight on the Firing Line
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 module dengapi is aliced;
20 private:
21 import core.atomic;
22 import core.thread;
23 import core.time;
25 import iv.cmdcon;
26 import iv.glbinds;
27 import glutils;
28 import wadarc;
30 import d2dmap;
31 import d2dadefs;
32 import d2dimage;
33 import d2dsprite;
34 import dacs;
36 import d2dunigrid;
37 import render;
40 // ////////////////////////////////////////////////////////////////////////// //
41 private extern (C) void _d_print_throwable (Throwable t);
44 // ////////////////////////////////////////////////////////////////////////// //
45 import arsd.color;
46 import arsd.png;
49 // ////////////////////////////////////////////////////////////////////////// //
50 public __gshared ActorId cameraChick;
53 // ////////////////////////////////////////////////////////////////////////// //
54 public void addInternalActorFields () {
55 // &0xffff: position in drawlist
56 // (>>16)&0xff: drawlist index
57 //Actor.addField("0drawlistpos", Actor.Field.Type.Uint);
61 // ////////////////////////////////////////////////////////////////////////// //
62 public {
63 enum PLK_UP = (1<<0);
64 enum PLK_DOWN = (1<<1);
65 enum PLK_LEFT = (1<<2);
66 enum PLK_RIGHT = (1<<3);
67 enum PLK_FIRE = (1<<4);
68 enum PLK_JUMP = (1<<5);
69 enum PLK_USE = (1<<6);
72 __gshared uint plrKeysLast;
73 __gshared uint plrKeyState;
76 public void plrKeyDown (uint plidx, uint mask) {
77 if (mask == 0 || plidx > 0) return;
78 plrKeysLast |= mask;
79 plrKeyState |= mask;
83 public void plrKeyUp (uint plidx, uint mask) {
84 if (mask == 0 || plidx > 0) return;
85 plrKeyState &= ~mask;
89 public void plrKeyUpDown (uint plidx, uint mask, bool down) {
90 if (down) plrKeyDown(plidx, mask); else plrKeyUp(plidx, mask);
94 void plrKeysFix (uint plidx) {
95 if (plidx > 0) return;
96 //conwritefln!"plrKeyState=0x%02x; plrKeysLast=0x%02x"(plrKeyState, plrKeysLast);
97 foreach (uint n; 0..12) {
98 if ((plrKeyState&(1<<n)) == 0) plrKeysLast &= ~(1<<n);
103 // ////////////////////////////////////////////////////////////////////////// //
104 void updateActorClasses () {
105 foreach (string ctype; dacsClassTypes) {
106 foreach (auto mod; dacsClassModules(ctype)) {
107 if (mod.rmoduletype.classtype == "map") {
108 // map scripts, not here
109 } else {
110 auto adef = registerActorDef(mod.rmoduletype.classtype, mod.rmoduletype.classname);
111 conwriteln("found info for actor '", mod.rmoduletype.classtype, ":", mod.rmoduletype.classname, "'");
113 auto animInitFn = FuncPool.findByFQMG(mod.name~".initializeAnim", ":void");
114 if (animInitFn !is null) adef.setAnimInitFunc(animInitFn);
117 auto initFn = FuncPool.findByFQMG(mod.name~".initialize", ":void:Actor");
118 if (initFn !is null) adef.setInitFunc(initFn);
121 auto thinkFn = FuncPool.findByFQMG(mod.name~".think", ":void:Actor");
122 if (thinkFn !is null) adef.setThinkFunc(thinkFn);
128 // setup HUD scripts
129 if (auto mod = dacsModuleFor("engine", "hud")) {
130 hudScripts.fiDraw = FuncPool.findByFQMG(mod.name~".drawHud", ":void");
135 // ////////////////////////////////////////////////////////////////////////// //
136 private string modLoader (string modname) {
137 static string getTextFile (string fname) {
138 try {
139 auto res = loadTextFile(fname);
140 conwriteln("loading DACS module file '", fname, "' (", res.length, " bytes)");
141 if (res is null) res = "";
142 return res;
143 } catch (Exception) {}
144 return null;
147 string res;
149 //res = getTextFile(modname~".dacs");
150 //if (res !is null) return res;
152 res = getTextFile("scripts/"~modname~".dacs");
153 if (res !is null) return res;
154 assert(res is null);
156 string[] parts = ["scripts"];
157 string mn = modname;
158 while (mn.length > 0) {
159 int pos = 0;
160 if (mn[0] >= 'A' && mn[0] <= 'Z') ++pos;
161 while (pos < mn.length && (mn[pos] < 'A' || mn[pos] > 'Z')) ++pos;
162 if (mn[0] >= 'A' && mn[0] <= 'Z') {
163 parts ~= cast(char)(mn[0]+32)~mn[1..pos];
164 } else {
165 parts ~= mn[0..pos];
167 mn = mn[pos..$];
170 import std.array : join;
171 string path = parts.join("/")~".dacs";
172 res = getTextFile(path);
173 if (res !is null) return res;
174 assert(res is null);
176 assert(res is null);
177 throw new Exception("module '"~modname~"' not found");
181 __gshared string[] dacsModules;
183 public void registerWadScripts () {
184 try {
185 import std.array : split;
186 auto t = loadTextFile("scripts/dacsmain.txt");
187 foreach (string line; t.split('\n')) {
188 while (line.length && line[0] <= ' ') line = line[1..$];
189 if (line.length == 0 || line[0] == ';' || line[0] == '#') continue;
190 while (line.length && line[$-1] <= ' ') line = line[0..$-1];
191 import std.algorithm : canFind;
192 if (!dacsModules.canFind(line)) dacsModules ~= line;
194 } catch (Exception e) { return; }
198 public void loadWadScripts () {
199 if (moduleLoader is null) moduleLoader = (string modname) => modLoader(modname);
200 try {
201 //conwriteln("loading main DACS module '", mainmod, "'");
202 foreach (string mod; dacsModules) parseModule(mod);
203 parseComplete();
204 updateActorClasses();
205 setupDAPI();
206 dacsFinalizeCompiler();
207 } catch (CompilerException e) {
208 _d_print_throwable(e);
209 conwriteln("PARSE ERROR: ", e.file, " at ", e.line);
210 conwriteln(e.toString);
211 assert(0);
216 // ////////////////////////////////////////////////////////////////////////// //
217 struct HudScripts {
218 FuncPool.FuncInfo fiDraw;
220 void runDraw () {
221 if (fiDraw !is null) {
222 //conwriteln("HUD SCRIPT!");
223 fiDraw();
229 public __gshared HudScripts hudScripts;
232 // ////////////////////////////////////////////////////////////////////////// //
233 public __gshared LevelMap map;
234 //public __gshared DACSVM dvm;
235 public __gshared uint prngSyncSeed = 0x29a;
236 public __gshared uint prngSeed = 0x29a; // unsynced
237 public __gshared string curmapname;
238 public __gshared string nextmapname; // nonempty: go to next level
240 struct MapScripts {
241 FuncPool.FuncInfo fiInit; // called after level map is loaded
242 FuncPool.FuncInfo fiLoaded; // called after mosters set
243 FuncPool.FuncInfo fiUnloading; // called before level is unloaded (i.e. player finished the level)
244 FuncPool.FuncInfo fiPreThink; // called before monster think
245 FuncPool.FuncInfo fiPostThink; // called after monster think
247 void runInit () { if (fiInit !is null) fiInit(); }
248 void runLoaded () { if (fiLoaded !is null) fiLoaded(); }
249 void runUnloading () { if (fiUnloading !is null) fiUnloading(); }
250 void runPreThink () { if (fiPreThink !is null) fiPreThink(); }
251 void runPostThink () { if (fiPostThink !is null) fiPostThink(); }
254 public __gshared MapScripts mapscripts;
257 // generate next map name for exit
258 public string genNextMapName (int lnum) {
259 import std.algorithm : endsWith;
260 import std.path;
261 string ext;
262 string nn = curmapname;
263 if (nn.endsWith(".d2m")) {
264 ext = ".d2m";
265 nn = nn[0..$-4];
267 if (nn[$-1] < '0' || nn[$-1] > '9') return null;
268 uint mapnum = 0, mmul = 1;
269 while (nn.length > 0 && nn[$-1] >= '0' && nn[$-1] <= '9') {
270 mapnum += (nn[$-1]-'0')*mmul;
271 mmul *= 10;
272 nn = nn[0..$-1];
274 ++mapnum;
275 if (lnum > 0 && lnum < 100) mapnum = lnum;
276 if (mapnum > 99) return null; // alas
277 import std.string : format;
278 return "%s%02u%s".format(nn, mapnum, ext);
282 public void clearMapScripts () {
283 mapscripts = mapscripts.init; // remove old scripts
287 public void setupMapScripts () {
288 void setupFromModule(T) (T mod) {
289 mapscripts.fiInit = FuncPool.findByFQMG(mod.name~".initialize", ":void");
290 mapscripts.fiLoaded = FuncPool.findByFQMG(mod.name~".loaded", ":void");
291 mapscripts.fiUnloading = FuncPool.findByFQMG(mod.name~".unloading", ":void");
292 mapscripts.fiPreThink = FuncPool.findByFQMG(mod.name~".prethink", ":void");
293 mapscripts.fiPostThink = FuncPool.findByFQMG(mod.name~".postthink", ":void");
296 import std.path : baseName, setExtension;
297 clearMapScripts();
298 string mapname = curmapname.baseName.setExtension("");
299 // normal map scripts
300 if (auto mod = dacsModuleFor("map", mapname)) {
301 conwriteln("found module for map '", mapname, "'");
302 setupFromModule(mod);
303 return;
305 // not found, try "unnamed map"
306 if (auto mod = dacsModuleFor("map", " ")) {
307 conwriteln("using module for unknownmap '", mapname, "'");
308 setupFromModule(mod);
309 return;
314 // ////////////////////////////////////////////////////////////////////////// //
315 // Park-Miller-Carta Pseudo-Random Number Generator, based on David G. Carta paper
316 // 31 bit of randomness
317 // seed is previous result, as usual
318 public uint prngR31next (uint seed) {
319 if (seed == 0) seed = 0x29a;
320 uint lo = 16807*(seed&0xffff);
321 uint hi = 16807*(seed>>16);
322 lo += (hi&0x7fff)<<16;
323 lo += hi>>15;
324 //if (lo > 0x7fffffff) lo -= 0x7fffffff; // should be >=, actually
325 lo = (lo&0x7FFFFFFF)+(lo>>31); // same as previous code, but branch-less
326 return lo;
330 // "synced" random, using common seed for all players
331 public uint syncrandu31 () {
332 pragma(inline, true);
333 return (prngSyncSeed = prngR31next(prngSyncSeed));
337 // "unsynced" random, seed isn't saved
338 public uint unsyncrandu31 () {
339 pragma(inline, true);
340 return (prngSeed = prngR31next(prngSeed));
344 // ////////////////////////////////////////////////////////////////////////// //
345 version(dacs_use_vm) {
346 // this is VAT function, argument order is reversed
347 void doWrite(bool donl) (FuncPool.FuncInfo fi, DACSVM vm) {
348 import std.stdio;
349 auto count = vm.popInt();
350 while (count-- > 0) {
351 auto type = vm.popInt();
352 switch (cast(VATArgType)type) {
353 case VATArgType.Int: write(vm.popInt()); break;
354 case VATArgType.Uint: write(vm.popUint()); break;
355 case VATArgType.Float: write(vm.popFloat()); break;
356 case VATArgType.StrId: write(StrId(vm.popUint()).get); break;
357 case VATArgType.ActorId: write("<actor>"); vm.popUint(); break; //TODO
358 default: write("<invalid-type>"); vm.popUint(); break; //TODO
361 static if (donl) writeln();
362 // push dummy return value
363 vm.pushInt(0);
365 } else {
366 extern(C) void doWrite(bool donl) (uint argc, ...) {
367 import core.vararg;
368 import std.stdio;
369 mainloop: while (argc >= 2) {
370 argc -= 2;
371 int tp = va_arg!int(_argptr);
372 switch (tp) {
373 case VATArgType.Int:
374 auto v = va_arg!int(_argptr);
375 write(v);
376 break;
377 case VATArgType.Uint:
378 auto v = va_arg!uint(_argptr);
379 write(v);
380 break;
381 case VATArgType.Float:
382 auto v = va_arg!float(_argptr);
383 write(v);
384 break;
385 case VATArgType.StrId:
386 auto v = StrId(va_arg!uint(_argptr)).get;
387 write(v);
388 break;
389 case VATArgType.ActorId:
390 auto v = ActorId(va_arg!uint(_argptr));
391 write("<actor:", v.valid, ":", v.id, ">");
392 //write("<actor>");
393 break;
394 default: write("<invalid-type>"); break mainloop;
397 static if (donl) writeln();
402 // ////////////////////////////////////////////////////////////////////////// //
403 void animClearFrames (StrId classtype, StrId classname, StrId state) {
404 auto adef = findActorDef(classtype.get, classname.get);
405 if (adef is null) throw new Exception("animClearFrames: actor '"~classtype.get~":"~classname.get~"' not found!");
406 adef.clearFrames(state);
410 void animAddFrame (StrId classtype, StrId classname, StrId state, uint dir, StrId sprname) {
411 auto adef = findActorDef(classtype.get, classname.get);
412 if (adef is null) throw new Exception("animAddFrame: actor '"~classtype.get~":"~classname.get~"' not found!");
413 if (dir != 0) dir = 1; //TODO: process mirror flag here
414 adef.addFrame(state, dir, sprname);
418 // ////////////////////////////////////////////////////////////////////////// //
419 void setupDAPI () {
420 conwriteln("setting up D API");
422 FuncPool["write"] = &doWrite!false;
423 FuncPool["writeln"] = &doWrite!true;
425 FuncPool["syncrandu31"] = &syncrandu31;
427 FuncPool["animClearFrames"] = &animClearFrames;
428 FuncPool["animAddFrame"] = &animAddFrame;
430 FuncPool["actorSetAnimation"] = function void (ActorId me, StrId state) {
431 if (!me.valid) return;
432 if (auto adef = findActorDef(me.classtype!string, me.classname!string)) {
433 me.zAnimstate = state;
434 me.zAnimidx = 0;
438 //FuncPool["getPlayer"] = function ActorId () { assert(0); };
439 //FuncPool["isPlayer"] = function int (ActorId me) { return (me == players.ptr[0] ? 1 : 0); };
441 FuncPool["getPlayerCount"] = function int () => 1;
443 FuncPool["getPlayerActor"] = function ActorId (uint pnum) {
444 if (pnum == 1) return players.ptr[0];
445 return ActorId(0);
448 FuncPool["getPlayerButtons"] = function uint (uint pidx) { return (pidx == 1 ? plrKeysLast : 0); };
450 FuncPool["mapGetTypeTile"] = function int (int x, int y) {
451 int res = 0;
452 if (map !is null && x >= 0 && y >= 0 && x < map.width && y < map.height) {
453 res = map.tiles.ptr[LevelMap.Type].ptr[y*map.width+x];
454 if (res != LevelMap.TILE_ACTTRAP) res &= 0x7f;
456 return res;
459 FuncPool["mapGetTile"] = function int (uint layer, int x, int y) {
460 return (map !is null && x >= 0 && y >= 0 && x < map.width && y < map.height && layer >= 0 && layer <= 1 ? map.tiles.ptr[LevelMap.Front+layer].ptr[y*map.width+x] : 0);
463 FuncPool["mapSetTypeTile"] = function void (int x, int y, int tid) {
464 if (map is null || x < 0 || y < 0 || x >= map.width || y >= map.height || tid < 0 || tid > 255) return;
465 auto p = map.tiles.ptr[LevelMap.Type].ptr+y*map.width+x;
466 if (*p != tid) {
467 *p = cast(ubyte)tid;
468 import render : mapDirty;
469 mapDirty((1<<LevelMap.Type)|(1<<LevelMap.LightMask));
473 FuncPool["mapSetTile"] = function void (uint layer, int x, int y, int tid) {
474 if (map is null || layer < 0 || layer > 1 || x < 0 || y < 0 || x >= map.width || y >= map.height || tid < 0 || tid > 255) return;
475 auto p = map.tiles.ptr[LevelMap.Front+layer].ptr+y*map.width+x;
476 if (*p != tid) {
477 *p = cast(ubyte)tid;
478 import render : mapDirty;
479 if (layer == 0) {
480 // front
481 mapDirty((1<<LevelMap.Front)|(1<<LevelMap.AllLiquids)|(1<<LevelMap.LiquidMask));
482 } else {
483 // back
484 mapDirty((1<<LevelMap.Back)|(1<<LevelMap.AllLiquids)|(1<<LevelMap.LiquidMask));
489 FuncPool["mapGetWaterTexture"] = function int (int fg) {
490 if (fg < 0 || fg >= map.wallnames.length) return 0;
491 switch (map.wallnames.ptr[fg]) {
492 case "_water_0": return 1;
493 case "_water_1": return 2;
494 case "_water_2": return 3;
495 default:
497 return 0;
500 FuncPool["getMapViewHeight"] = function int () {
501 import render : vlWidth, vlHeight, getScale;
502 return vlHeight/getScale;
505 FuncPool["actorsOverlap"] = function int (ActorId a, ActorId b) { return (actorsOverlap(a, b) ? 1 : 0); };
507 FuncPool["actorRemove"] = function void (ActorId me) {
508 if (me.valid) {
509 if ((me.fget_flags&AF_NOCOLLISION) == 0) ugActorModify!false(me); // remove from grid
510 Actor.remove(me);
514 FuncPool["rewindTouchList"] = &rewindTouchList;
515 FuncPool["getNextTouchListItem"] = &getNextTouchListItem;
517 FuncPool["actorListRewind"] = &xactorListRewind;
518 FuncPool["actorListNext"] = &xactorListNext;
520 FuncPool["getCheatNoDoors"] = function int () { import render : cheatNoDoors; return (cheatNoDoors ? 1 : 0); };
521 FuncPool["getCheatNoWallClip"] = function int () { import render : cheatNoWallClip; return (cheatNoWallClip ? 1 : 0); };
522 FuncPool["getCheatNoCeilClip"] = function int () { return 0; };
523 FuncPool["getCheatNoLiftClip"] = function int () { return 0; };
525 FuncPool["addMessage"] = function void (string text, int pause, bool noreplace) {
526 import render : postAddMessage;
527 postAddMessage(text, pause, noreplace);
530 import d2dparts : dotAddBlood, dotAddSpark, dotAddWater;
532 FuncPool["dotAddBlood"] = &dotAddBlood;
533 FuncPool["dotAddSpark"] = &dotAddSpark;
534 FuncPool["dotAddWater"] = &dotAddWater;
536 function void (int x, int y, int xv, int yv, int n, int color) {
537 conwriteln("dotAddWater: x=", x, "; y=", y, "; xv=", xv, "; yv=", yv, "; n=", n, "; color=", color);
538 dotAddWater(x, y, xv, yv, n, color);
542 FuncPool["gactLevelExit"] = function void (int lnum) {
543 if (nextmapname.length == 0) {
544 string nmname = genNextMapName(lnum);
545 if (nmname.length == 0) assert(0, "no level!");
546 nextmapname = nmname;
550 FuncPool["actorSpawn"] = &actorSpawn;
552 // return previous one
553 FuncPool["setCameraChick"] = function ActorId (ActorId act) {
554 auto res = cameraChick;
555 cameraChick = act;
556 return res;
559 FuncPool["MapWidth"] = function int () { return (map !is null ? map.width : 1); };
560 FuncPool["MapHeight"] = function int () { return (map !is null ? map.height : 1); };
562 FuncPool["viewportWidth"] = function int () { return vlWidth; };
563 FuncPool["viewportHeight"] = function int () { return vlHeight; };
565 FuncPool["spriteWidth"] = function int (string spfile) {
566 auto im = loadSprite(spfile, true);
567 return (im !is null ? im.vga.width : 0);
570 FuncPool["spriteHeight"] = function int (string spfile) {
571 auto im = loadSprite(spfile, true);
572 return (im !is null ? im.vga.height : 0);
575 FuncPool["drawSpriteAt"] = function void (string spfile, int x, int y) {
576 auto im = loadSprite(spfile, true);
577 if (im !is null) {
578 im.drawAtXY(x, y);
581 FuncPool["drawSpriteAtNoOfs"] = function void (string spfile, int x, int y) {
582 auto im = loadSprite(spfile, true);
583 if (im !is null) {
584 im.drawAtXY(x+im.vga.sx, y+im.vga.sy-im.vga.height);
588 FuncPool["drawTextRGBA"] = function void (string text, int x, int y, int r, int g, int b, int a) {
589 import d2dfont : smbwDrawText;
590 if (r < 0) r = 0; else if (r > 255) r = 255;
591 if (g < 0) g = 0; else if (g > 255) g = 255;
592 if (b < 0) b = 0; else if (b > 255) b = 255;
593 if (a < 0) a = 0; else if (a > 255) a = 255;
594 glColor4f(r/255.0f, g/255.0f, b/255.0f, a/255.0f);
595 smbwDrawText(x, y, text);
596 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
599 FuncPool["drawTextBigRGBA"] = function void (string text, int x, int y, int r, int g, int b, int a) {
600 import d2dfont : bfDrawText;
601 if (r < 0) r = 0; else if (r > 255) r = 255;
602 if (g < 0) g = 0; else if (g > 255) g = 255;
603 if (b < 0) b = 0; else if (b > 255) b = 255;
604 if (a < 0) a = 0; else if (a > 255) a = 255;
605 glColor4f(r/255.0f, g/255.0f, b/255.0f, a/255.0f);
606 bfDrawText(x, y, text);
607 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
610 FuncPool["textWidth"] = function int (string text) { import d2dfont : smTextWidth; return smTextWidth(text); };
611 FuncPool["textHeight"] = function int (string text) { import d2dfont : smTextHeight; return smTextHeight(text); };
613 FuncPool["textWidthBig"] = function int (string text) { import d2dfont : bfTextWidth; return bfTextWidth(text); };
614 FuncPool["textHeightBig"] = function int (string text) { import d2dfont : bfTextHeight; return bfTextHeight(text); };
616 FuncPool["drawRectRGBA"] = function void (int x, int y, int w, int h, int r, int g, int b, int a) {
617 if (w < 1 || h < 1) return;
618 if (r < 0) r = 0; else if (r > 255) r = 255;
619 if (g < 0) g = 0; else if (g > 255) g = 255;
620 if (b < 0) b = 0; else if (b > 255) b = 255;
621 if (a < 0) a = 0; else if (a > 255) a = 255;
622 glColor4f(r/255.0f, g/255.0f, b/255.0f, a/255.0f);
623 bindTexture(0);
624 glRectf(x, y, x+w-1, y+h-1);
625 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
630 public void registerAPI () {
631 //Actor.actorSize += 256;
632 conwriteln("actor size is ", Actor.actorSize, " bytes");
634 version(dacs_use_vm) FuncPool.vm = new DACSVM();
635 //dvm = new DACSVM();
637 // initialize actor animation
638 ActorDef.forEach((adef) => adef.callAnimInit());
640 conwriteln("API registered");
644 // ////////////////////////////////////////////////////////////////////////// //
645 mixin(Actor.FieldGetMixin!("classtype", StrId)); // fget_classtype
646 mixin(Actor.FieldGetMixin!("classname", StrId)); // fget_classname
647 mixin(Actor.FieldGetMixin!("x", int));
648 mixin(Actor.FieldGetMixin!("y", int));
649 mixin(Actor.FieldGetMixin!("dir", uint));
650 mixin(Actor.FieldGetMixin!("height", int));
651 mixin(Actor.FieldGetMixin!("radius", int));
652 mixin(Actor.FieldGetMixin!("flags", uint));
653 mixin(Actor.FieldGetMixin!("zAnimstate", StrId));
654 mixin(Actor.FieldGetMixin!("zAnimidx", int));
656 mixin(Actor.FieldSetMixin!("zAnimidx", int));
657 mixin(Actor.FieldSetMixin!("flags", uint));
660 public bool actorsOverlap (ActorId a, ActorId b) {
661 if (!a.valid || !b.valid) return false;
662 if (a.id == b.id) return false; // no self-overlap
664 int ax = a.fget_x;
665 int bx = b.fget_x;
666 int ar = a.fget_radius;
667 int br = b.fget_radius;
669 if (ax-ar > bx+br || ax+ar < bx-br) return false;
671 int ay = a.fget_y;
672 int by = b.fget_y;
673 int ah = a.fget_height;
674 int bh = b.fget_height;
676 //return (ay > by-bh && ay-ah < by);
677 return (by-bh <= ay && by >= ay-ah);
681 // ////////////////////////////////////////////////////////////////////////// //
682 public __gshared ActorId[2] players;
685 public void loadAllMonsterGraphics () {
686 ActorDef.forEach((adef) {
687 conwriteln("loading graphics for '", adef.classtype.get, ":", adef.classname.get, "'");
688 adef.loadGraphics();
690 //Actor.dumpActors();
691 realiseSpriteAtlases();
695 ActorId actorSpawn (StrId classtype, StrId classname, int x, int y, uint dir) {
696 auto adef = findActorDef(classtype, classname);
697 if (adef is null) return ActorId(0);
698 auto aid = Actor.alloc;
699 aid.classtype = classtype;
700 aid.classname = classname;
701 aid.state = StrPool.MNST_SLEEP;
702 aid.x = x;
703 aid.y = y;
704 aid.dir = (dir ? 1 : 0);
705 adef.callInit(aid);
706 if ((aid.fget_flags&AF_NOCOLLISION) == 0) ugActorModify!true(aid);
707 return aid;
711 public void loadMapMonsters () {
712 assert(map !is null);
713 ugClear();
714 players[] = ActorId(0);
715 //conwriteln(players[0].valid, "; ", players[0].id);
716 foreach (ref thing; map.things) {
717 if (thing.dmonly) continue;
718 auto did = (thing.type&0x7fff) in d2dactordefsById;
719 if (did !is null && did.classtype == "playerstart") {
720 if ((thing.type&0x7fff) == 1 || (thing.type&0x7fff) == 2) {
721 int pnum = thing.type-1;
722 if (!players[pnum].valid) {
723 auto aid = Actor.alloc;
724 aid.classtype = StrPool.intern("monster");
725 aid.classname = StrPool.intern("Player");
726 aid.plrnum = cast(uint)(pnum+1);
727 aid.state = StrPool.MNST_SLEEP;
728 aid.x = cast(int)thing.x;
729 aid.y = cast(int)thing.y;
730 aid.dir = cast(uint)(thing.right ? 1 : 0);
731 auto adef = findD2DActorDef(thing.type);
732 if (adef is null) assert(0);
733 adef.callInit(aid);
734 if ((aid.fget_flags&AF_NOCOLLISION) == 0) ugActorModify!true(aid);
735 players[pnum] = aid;
736 conwriteln("player #", pnum+1, " aid is ", aid.id);
739 continue;
741 auto adef = findD2DActorDef(thing.type&0x7fff);
742 if (adef is null) {
743 if (did !is null) {
744 conwriteln("ignoring D2D thing '", did.classtype.get, ":", did.classname.get, "'");
745 } else {
746 conwriteln("ignoring unknown D2D thing with mapid ", thing.type);
748 continue;
750 // create actor and initialize it
751 auto aid = Actor.alloc;
752 aid.classtype = StrPool.intern(adef.classtype);
753 aid.classname = StrPool.intern(adef.classname);
754 //conwriteln("found '", aid.classtype.get, ":", aid.classname.get, "'");
755 if (did !is null) {
756 assert(did.classtype == adef.classtype);
757 assert(did.classname == adef.classname);
758 conwriteln("mapid=", thing.type, "; ", adef.classtype, ":", adef.classname, "; id=", aid.id);
759 assert(aid.classtype!string == adef.classtype);
760 assert(aid.classname!string == adef.classname);
761 } else {
762 assert(0);
764 aid.state = StrPool.MNST_SLEEP;
765 aid.x = cast(int)thing.x;
766 aid.y = cast(int)thing.y;
767 aid.dir = cast(uint)(thing.right ? 1 : 0);
768 if (thing.type&0x8000) aid.flags = aid.fget_flags|AF_NOGRAVITY;
769 //if (aid.classtype!string == "item" && aid.x!int < 64) { aid.x = 92; aid.y = aid.y!int-16; conwriteln("!!!"); }
770 adef.callInit(aid);
771 if ((aid.fget_flags&AF_NOCOLLISION) == 0) ugActorModify!true(aid);
774 // create switches
775 foreach (ref sw; map.switches) {
776 if (sw.type == 0) continue; // just in case
777 auto swname = getD2DSwitchClassName(sw.type);
778 if (swname.length == 0) {
779 conwriteln("unknown switch type ", sw.type);
780 continue;
782 if (auto adef = findActorDef("switch", swname)) {
783 auto aid = Actor.alloc;
784 aid.classtype = StrPool.intern("switch");
785 aid.classname = StrPool.intern(swname);
786 // nocollision, 'cause collision checking will be processed in switch thinker, but other actors should not touch switches
787 aid.flags = /*AF_NOCOLLISION|*/AF_NOGRAVITY|/*AF_NOONTOUCH|*/AF_NODRAW|AF_NOLIGHT|AF_NOANIMATE;
788 aid.x = sw.x*TileSize;
789 aid.y = sw.y*TileSize;
790 aid.switchabf = (sw.a<<16)|(sw.b<<8)|sw.flags;
791 // should be enough
792 aid.radius = 16;
793 aid.height = 16;
794 adef.callInit(aid);
795 if ((aid.fget_flags&AF_NOCOLLISION) == 0) ugActorModify!true(aid); // just in case
796 } else {
797 conwriteln("switch definition 'switch:", swname, "' not found");
801 conwriteln("initial snapshot size: ", Actor.snapshotSize, " bytes");
805 // ////////////////////////////////////////////////////////////////////////// //
806 __gshared ActorId[65536] xactorList; // aids
807 __gshared uint xactorListCount, xactorListIndex = uint.max;
810 // dacs API
811 void xactorListRewind () {
812 if (!touchListAllowed) return; // it's ok to reuse it here
813 xactorListCount = xactorListIndex = 0;
814 Actor.forEach((ActorId me) {
815 if (me.fget_classtype != StrPool.X_X) xactorList.ptr[xactorListCount++] = me;
820 // dacs API
821 ActorId xactorListNext () {
822 if (!touchListAllowed) return ActorId(0); // it's ok to reuse it here
823 if (xactorListIndex == uint.max) xactorListRewind();
824 while (xactorListIndex < xactorListCount) {
825 auto aid = xactorList.ptr[xactorListIndex];
826 ++xactorListIndex;
827 if (aid.fget_classtype == StrPool.X_X) continue;
828 return aid;
830 return ActorId(0);
834 // ////////////////////////////////////////////////////////////////////////// //
835 __gshared uint[65536] touchList; // aids
836 __gshared uint[] realTouchList;
837 __gshared uint realTouchListIndex = uint.max;
838 __gshared ActorId curThinkingActor;
839 __gshared bool touchListAllowed = false;
842 // dacs API
843 void rewindTouchList () {
844 if (!touchListAllowed) return;
845 realTouchListIndex = 0;
846 realTouchList = ugActorHitList(curThinkingActor, touchList[]);
850 // dacs API
851 ActorId getNextTouchListItem () {
852 if (!touchListAllowed) return ActorId(0);
853 if (realTouchListIndex == uint.max) rewindTouchList();
854 while (realTouchListIndex < realTouchList.length) {
855 auto aid = ActorId(realTouchList.ptr[realTouchListIndex]);
856 ++realTouchListIndex;
857 if (aid.fget_classtype == StrPool.X_X) continue;
858 if (aid.valid && (aid.fget_flags&AF_NOONTOUCH) == 0) return aid;
860 return ActorId(0);
864 public void doActorsThink () {
865 // we have too much memory!
866 __gshared uint[65536] validActorsList;
867 __gshared ActorId[65536] postponedDeath;
868 __gshared uint pdcount;
870 touchListAllowed = true;
871 scope(exit) touchListAllowed = false;
872 pdcount = 0;
873 // clear flags
874 Actor.forEach((ActorId me) { me.fset_flags(me.fget_flags&~AF_TELEPORT); });
875 mapscripts.runPreThink();
876 foreach (uint xid; Actor.getValidList(validActorsList[])) {
877 auto me = ActorId(xid);
878 if (me.valid) {
879 if (me.fget_classtype == StrPool.X_X) { postponedDeath.ptr[pdcount++] = me; continue; }
880 auto flags = me.fget_flags;
881 if ((flags&AF_NOCOLLISION) == 0) ugActorModify!false(me); // remove from grid
882 if (auto adef = findActorDef(me)) {
883 if ((flags&AF_NOTHINK) == 0) {
884 realTouchListIndex = uint.max;
885 xactorListIndex = uint.max;
886 curThinkingActor = me;
887 adef.callThink(me);
888 //if (me.x!int < 32) conwriteln("actor: ", me.id, "; attLightRGBX=", me.attLightRGBX!uint);
889 if (!me.valid) continue; // we are dead
890 if (me.fget_classtype == StrPool.X_X) { postponedDeath.ptr[pdcount++] = me; continue; }
891 flags = me.fget_flags; // in case script updated flags
893 if ((flags&AF_NOANIMATE) == 0) {
894 int aidx = me.fget_zAnimidx;
895 int nidx = adef.nextAnimIdx(me.fget_zAnimstate, me.fget_dir, aidx);
896 //conwriteln("actor ", me.id, " (", me.classtype!string, me.classname!string, "): state=", me.zAnimstate!string, "; aidx=", aidx, "; nidx=", nidx);
897 me.fset_zAnimidx = nidx;
898 //assert(me.fget_zAnimidx == nidx);
900 // put back to grid
901 if ((flags&AF_NOCOLLISION) == 0) ugActorModify!true(me);
905 mapscripts.runPostThink();
906 // process scheduled death
907 foreach (ActorId aid; postponedDeath[0..pdcount]) {
908 /*if ((flags&AF_NOCOLLISION) == 0)*/ ugActorModify!false(aid); // remove from grid
909 Actor.remove(aid);
911 // reset player keys
912 plrKeysFix(0);
913 //{ import std.stdio : stdout; stdout.writeln("========================================="); }
914 //Actor.dumpActors();