vsync is now default mode
[dd2d.git] / dengapi.d
bloba9123b862c541c9b3c09911866095bcaa25f68e2
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.glbinds;
26 import glutils;
27 import console;
28 import wadarc;
30 import d2dmap;
31 import d2dadefs;
32 import d2dgfx;
33 import dacs;
35 import d2dunigrid;
38 // ////////////////////////////////////////////////////////////////////////// //
39 private extern (C) void _d_print_throwable (Throwable t);
42 // ////////////////////////////////////////////////////////////////////////// //
43 import arsd.color;
44 import arsd.png;
47 // ////////////////////////////////////////////////////////////////////////// //
48 public {
49 enum PLK_UP = (1<<0);
50 enum PLK_DOWN = (1<<1);
51 enum PLK_LEFT = (1<<2);
52 enum PLK_RIGHT = (1<<3);
53 enum PLK_FIRE = (1<<4);
54 enum PLK_JUMP = (1<<5);
55 enum PLK_USE = (1<<6);
58 __gshared uint plrKeysLast;
59 __gshared uint plrKeyState;
62 public void plrKeyDown (uint plidx, uint mask) {
63 if (mask == 0 || plidx > 0) return;
64 plrKeysLast |= mask;
65 plrKeyState |= mask;
69 public void plrKeyUp (uint plidx, uint mask) {
70 if (mask == 0 || plidx > 0) return;
71 plrKeyState &= ~mask;
75 public void plrKeyUpDown (uint plidx, uint mask, bool down) {
76 if (down) plrKeyDown(plidx, mask); else plrKeyUp(plidx, mask);
80 void plrKeysFix (uint plidx) {
81 if (plidx > 0) return;
82 //conwritefln!"plrKeyState=0x%02x; plrKeysLast=0x%02x"(plrKeyState, plrKeysLast);
83 foreach (uint n; 0..12) {
84 if ((plrKeyState&(1<<n)) == 0) plrKeysLast &= ~(1<<n);
89 // ////////////////////////////////////////////////////////////////////////// //
90 void updateActorClasses (int iteration) {
91 foreach (string ctype; dacsClassTypes(iteration)) {
92 foreach (auto mod; dacsClassModules(ctype, iteration)) {
93 auto adef = registerActorDef(mod.rmoduletype.classtype, mod.rmoduletype.classname);
94 conwriteln("found info for actor '", adef.fullname, "'");
96 auto animInitFn = FuncPool.findByFQMG(mod.name~".initializeAnim", ":void");
97 if (animInitFn !is null) adef.setAnimInitFunc(animInitFn);
100 auto initFn = FuncPool.findByFQMG(mod.name~".initialize", ":void:Actor");
101 if (initFn !is null) adef.setInitFunc(initFn);
104 auto thinkFn = FuncPool.findByFQMG(mod.name~".think", ":void:Actor");
105 if (thinkFn !is null) adef.setThinkFunc(thinkFn);
112 // ////////////////////////////////////////////////////////////////////////// //
113 private string modLoader (string modname) {
114 static string getTextFile (string fname) {
115 try {
116 auto res = loadTextFile(fname);
117 conwriteln("loading DACS module file '", fname, "' (", res.length, " bytes)");
118 if (res is null) res = "";
119 return res;
120 } catch (Exception) {}
121 return null;
124 string res;
126 //res = getTextFile(modname~".dacs");
127 //if (res !is null) return res;
129 res = getTextFile("scripts/"~modname~".dacs");
130 if (res !is null) return res;
131 assert(res is null);
133 string[] parts = ["scripts"];
134 string mn = modname;
135 while (mn.length > 0) {
136 int pos = 0;
137 if (mn[0] >= 'A' && mn[0] <= 'Z') ++pos;
138 while (pos < mn.length && (mn[pos] < 'A' || mn[pos] > 'Z')) ++pos;
139 if (mn[0] >= 'A' && mn[0] <= 'Z') {
140 parts ~= cast(char)(mn[0]+32)~mn[1..pos];
141 } else {
142 parts ~= mn[0..pos];
144 mn = mn[pos..$];
147 import std.array : join;
148 string path = parts.join("/")~".dacs";
149 res = getTextFile(path);
150 if (res !is null) return res;
151 assert(res is null);
153 assert(res is null);
154 throw new Exception("module '"~modname~"' not found");
158 __gshared int modIteration = 0;
160 public void loadWadScripts () {
161 if (moduleLoader is null) moduleLoader = (string modname) => modLoader(modname);
162 string[] mainmods;
163 try {
164 import std.array : split;
165 auto t = loadTextFile("scripts/dacsmain.txt");
166 foreach (string line; t.split('\n')) {
167 while (line.length && line[0] <= ' ') line = line[1..$];
168 if (line.length == 0 || line[0] == ';') continue;
169 while (line.length && line[$-1] <= ' ') line = line[0..$-1];
170 mainmods ~= line;
172 } catch (Exception e) { return; }
173 try {
174 //conwriteln("loading main DACS module '", mainmod, "'");
175 auto iter = modIteration++;
176 conwriteln("iteration=", iter);
177 foreach (string mod; mainmods) {
178 parseModule(mod, iter);
180 conwriteln("calling parseComplete; iteration=", iter);
181 parseComplete();
182 updateActorClasses(iter);
183 } catch (CompilerException e) {
184 _d_print_throwable(e);
185 conwriteln("PARSE ERROR: ", e.file, " at ", e.line);
186 conwriteln(e.toString);
187 assert(0);
192 public void scriptLoadingComplete () {
193 setupDAPI();
194 dacsFinalizeCompiler();
198 // ////////////////////////////////////////////////////////////////////////// //
199 public __gshared LevelMap map;
200 //public __gshared DACSVM dvm;
201 public __gshared uint prngSyncSeed = 0x29a;
202 public __gshared uint prngSeed = 0x29a; // unsynced
205 // ////////////////////////////////////////////////////////////////////////// //
206 // Park-Miller-Carta Pseudo-Random Number Generator, based on David G. Carta paper
207 // 31 bit of randomness
208 // seed is previous result, as usual
209 public uint prngR31next (uint seed) {
210 if (seed == 0) seed = 0x29a;
211 uint lo = 16807*(seed&0xffff);
212 uint hi = 16807*(seed>>16);
213 lo += (hi&0x7fff)<<16;
214 lo += hi>>15;
215 //if (lo > 0x7fffffff) lo -= 0x7fffffff; // should be >=, actually
216 lo = (lo&0x7FFFFFFF)+(lo>>31); // same as previous code, but branch-less
217 return lo;
221 // "synced" random, using common seed for all players
222 public uint syncrandu31 () {
223 pragma(inline, true);
224 return (prngSyncSeed = prngR31next(prngSyncSeed));
228 // ////////////////////////////////////////////////////////////////////////// //
229 version(dacs_use_vm) {
230 // this is VAT function, argument order is reversed
231 void doWrite(bool donl) (FuncPool.FuncInfo fi, DACSVM vm) {
232 import std.stdio;
233 auto count = vm.popInt();
234 while (count-- > 0) {
235 auto type = vm.popInt();
236 switch (cast(VATArgType)type) {
237 case VATArgType.Int: write(vm.popInt()); break;
238 case VATArgType.Uint: write(vm.popUint()); break;
239 case VATArgType.Float: write(vm.popFloat()); break;
240 case VATArgType.StrId: write(StrId(vm.popUint()).get); break;
241 case VATArgType.ActorId: write("<actor>"); vm.popUint(); break; //TODO
242 default: write("<invalid-type>"); vm.popUint(); break; //TODO
245 static if (donl) writeln();
246 // push dummy return value
247 vm.pushInt(0);
249 } else {
250 extern(C) void doWrite(bool donl) (uint argc, ...) {
251 import core.vararg;
252 import std.stdio;
253 mainloop: while (argc >= 2) {
254 argc -= 2;
255 int tp = va_arg!int(_argptr);
256 switch (tp) {
257 case VATArgType.Int:
258 auto v = va_arg!int(_argptr);
259 write(v);
260 break;
261 case VATArgType.Uint:
262 auto v = va_arg!uint(_argptr);
263 write(v);
264 break;
265 case VATArgType.Float:
266 auto v = va_arg!float(_argptr);
267 write(v);
268 break;
269 case VATArgType.StrId:
270 auto v = StrId(va_arg!uint(_argptr)).get;
271 write(v);
272 break;
273 case VATArgType.ActorId:
274 auto v = ActorId(va_arg!uint(_argptr));
275 write("<actor:", v.valid, ":", v.id, ">");
276 //write("<actor>");
277 break;
278 default: write("<invalid-type>"); break mainloop;
281 static if (donl) writeln();
286 // ////////////////////////////////////////////////////////////////////////// //
287 void animClearFrames (StrId classtype, StrId classname, StrId state) {
288 auto adef = findActorDef(classtype.get, classname.get);
289 if (adef is null) throw new Exception("animClearFrames: actor '"~classtype.get~":"~classname.get~"' not found!");
290 adef.clearFrames(state);
294 void animAddFrame (StrId classtype, StrId classname, StrId state, uint dir, StrId sprname) {
295 auto adef = findActorDef(classtype.get, classname.get);
296 if (adef is null) throw new Exception("animAddFrame: actor '"~classtype.get~":"~classname.get~"' not found!");
297 if (dir != 0) dir = 1; //TODO: process mirror flag here
298 adef.addFrame(state, dir, sprname);
302 // ////////////////////////////////////////////////////////////////////////// //
303 void setupDAPI () {
304 conwriteln("setting up D API");
306 FuncPool["write"] = &doWrite!false;
307 FuncPool["writeln"] = &doWrite!true;
309 FuncPool["syncrandu31"] = &syncrandu31;
311 FuncPool["animClearFrames"] = &animClearFrames;
312 FuncPool["animAddFrame"] = &animAddFrame;
314 FuncPool["actorSetAnimation"] = function void (ActorId me, StrId state) {
315 if (!me.valid) return;
316 if (auto adef = findActorDef(me.classtype!string, me.classname!string)) {
317 me.zAnimstate = state;
318 me.zAnimidx = 0;
322 //FuncPool["getPlayer"] = function ActorId () { assert(0); };
323 //FuncPool["isPlayer"] = function int (ActorId me) { return (me == players.ptr[0] ? 1 : 0); };
325 FuncPool["getPlayerButtons"] = function uint (uint pidx) { return (pidx == 1 ? plrKeysLast : 0); };
327 FuncPool["mapGetTypeTile"] = function int (int x, int y) {
328 return (map !is null && x >= 0 && y >= 0 && x < map.width && y < map.height ? map.tiles.ptr[LevelMap.Type].ptr[y*map.width+x] : 0);
331 FuncPool["mapGetTile"] = function int (uint layer, int x, int y) {
332 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);
335 FuncPool["mapSetTypeTile"] = function void (int x, int y, int tid) {
336 if (map is null || x < 0 || y < 0 || x >= map.width || y >= map.height || tid < 0 || tid > 255) return;
337 auto p = map.tiles.ptr[LevelMap.Type].ptr+y*map.width+x;
338 if (*p != tid) {
339 *p = cast(ubyte)tid;
340 import xmain_d2d : mapDirty;
341 mapDirty();
345 FuncPool["mapSetTile"] = function void (uint layer, int x, int y, int tid) {
346 if (map is null || layer < 0 || layer > 1 || x < 0 || y < 0 || x >= map.width || y >= map.height || tid < 0 || tid > 255) return;
347 auto p = map.tiles.ptr[LevelMap.Front+layer].ptr+y*map.width+x;
348 if (*p != tid) {
349 *p = cast(ubyte)tid;
350 import xmain_d2d : mapDirty;
351 mapDirty();
355 FuncPool["getMapViewHeight"] = function int () {
356 import xmain_d2d : vlWidth, vlHeight, scale;
357 return vlHeight/scale;
360 FuncPool["setMapViewPos"] = function void (int x, int y) {
361 import xmain_d2d : setMapViewPos;
362 setMapViewPos(x, y);
365 FuncPool["actorsOverlap"] = function int (ActorId a, ActorId b) { return (actorsOverlap(a, b) ? 1 : 0); };
367 FuncPool["actorRemove"] = function void (ActorId me) { Actor.remove(me); };
369 FuncPool["rewindTouchList"] = &rewindTouchList;
370 FuncPool["getNextTouchListItem"] = &getNextTouchListItem;
372 FuncPool["getCheatNoDoors"] = function int () { import xmain_d2d : cheatNoDoors; return (cheatNoDoors ? 1 : 0); };
377 public void registerAPI () {
378 //Actor.actorSize += 256;
379 conwriteln("actor size is ", Actor.actorSize, " bytes");
381 version(dacs_use_vm) FuncPool.vm = new DACSVM();
382 //dvm = new DACSVM();
384 // initialize actor animation
385 foreach (ActorDef adef; actordefs.byValue) {
386 adef.callAnimInit();
389 conwriteln("API registered");
393 // ////////////////////////////////////////////////////////////////////////// //
394 mixin(Actor.FieldGetMixin!("classtype", StrId)); // fget_classtype
395 mixin(Actor.FieldGetMixin!("classname", StrId)); // fget_classname
396 mixin(Actor.FieldGetMixin!("x", int));
397 mixin(Actor.FieldGetMixin!("y", int));
398 mixin(Actor.FieldGetMixin!("dir", uint));
399 mixin(Actor.FieldGetMixin!("height", int));
400 mixin(Actor.FieldGetMixin!("radius", int));
401 mixin(Actor.FieldGetMixin!("flags", uint));
402 mixin(Actor.FieldGetMixin!("zAnimstate", StrId));
403 mixin(Actor.FieldGetMixin!("zAnimidx", int));
405 mixin(Actor.FieldSetMixin!("zAnimidx", int));
408 public bool actorsOverlap (ActorId a, ActorId b) {
409 if (!a.valid || !b.valid) return false;
410 if (a.id == b.id) return false; // no self-overlap
412 int ax = a.fget_x;
413 int bx = b.fget_x;
414 int ar = a.fget_radius;
415 int br = b.fget_radius;
417 if (ax-ar > bx+br || ax+ar < bx-br) return false;
419 int ay = a.fget_y;
420 int by = b.fget_y;
421 int ah = a.fget_height;
422 int bh = b.fget_height;
424 //return (ay > by-bh && ay-ah < by);
425 return (by-bh <= ay && by >= ay-ah);
429 // ////////////////////////////////////////////////////////////////////////// //
430 __gshared ActorId[2] players;
432 public void loadMapMonsters () {
433 assert(map !is null);
434 ugClear();
435 players[] = ActorId(0);
436 //conwriteln(players[0].valid, "; ", players[0].id);
437 foreach (ref thing; map.things) {
438 if (thing.dmonly) continue;
439 if (auto did = thing.type in d2dactordefsById) {
440 //if (did.classtype == "item") continue;
441 if (did.classtype == "playerstart") {
442 if (thing.type == 1 || thing.type == 2) {
443 int pnum = thing.type-1;
444 if (!players[pnum].valid) {
445 auto aid = Actor.alloc;
446 aid.classtype = StrPool.intern("monster");
447 aid.classname = StrPool.intern("Player");
448 aid.plrnum = cast(uint)(pnum+1);
449 aid.state = StrPool.MNST_SLEEP;
450 aid.x = cast(int)thing.x;
451 aid.y = cast(int)thing.y;
452 aid.dir = cast(uint)(thing.right ? 1 : 0);
453 auto adef = findD2DActorDef(thing.type);
454 if (adef is null) assert(0);
455 adef.callInit(aid);
456 if ((aid.flags!uint&AF_NOCOLLISION) == 0) ugActorModify!true(aid);
457 players[pnum] = aid;
458 conwriteln("player #", pnum+1, " aid is ", aid.id);
459 //FIXME: HACK!
461 if (pnum == 0) {
462 import xmain_d2d : setMapViewPos;
463 setMapViewPos(aid.x!int, aid.y!int);
468 continue;
471 auto adef = findD2DActorDef(thing.type);
472 if (adef is null) {
473 if (auto did = thing.type in d2dactordefsById) {
474 conwriteln("ignoring D2D thing '", did.fullname, "'");
475 } else {
476 conwriteln("ignoring unknown D2D thing with mapid ", thing.type);
478 continue;
480 // load graphics (we'll load all graphics)
481 //adef.loadGraphics();
482 // create actor and initialize it
483 auto aid = Actor.alloc;
484 aid.classtype = StrPool.intern(adef.classtype);
485 aid.classname = StrPool.intern(adef.classname);
486 //conwriteln("found '", aid.classtype.get, ":", aid.classname.get, "'");
487 if (auto did = thing.type in d2dactordefsById) {
488 assert(did.classtype == adef.classtype);
489 assert(did.classname == adef.classname);
490 conwriteln("mapid=", thing.type, "; ", adef.classtype, ":", adef.classname, "; id=", aid.id);
491 assert(aid.classtype!string == adef.classtype);
492 assert(aid.classname!string == adef.classname);
493 } else {
494 assert(0);
496 aid.state = StrPool.MNST_SLEEP;
497 aid.x = cast(int)thing.x;
498 aid.y = cast(int)thing.y;
499 aid.dir = cast(uint)(thing.right ? 1 : 0);
500 //if (aid.classtype!string == "item" && aid.x!int < 64) { aid.x = 92; aid.y = aid.y!int-16; conwriteln("!!!"); }
501 adef.callInit(aid);
502 if ((aid.flags!uint&AF_NOCOLLISION) == 0) ugActorModify!true(aid);
505 foreach (ActorDef adef; actordefs.byValue) {
506 conwriteln("loading graphics for '", adef.fullname, "'");
507 adef.loadGraphics();
509 //Actor.dumpActors();
511 conwriteln("initial snapshot size: ", Actor.snapshotSize, " bytes");
515 // ////////////////////////////////////////////////////////////////////////// //
516 __gshared uint[65536] touchList; // aids
517 __gshared uint[] realTouchList;
518 __gshared uint realTouchListIndex = uint.max;
519 __gshared ActorId curThinkingActor;
520 __gshared bool touchListAllowed = false;
523 // dacs API
524 void rewindTouchList () {
525 if (!touchListAllowed) return;
526 realTouchListIndex = 0;
527 realTouchList = ugActorHitList(curThinkingActor, touchList[]);
531 // dacs API
532 ActorId getNextTouchListItem () {
533 if (!touchListAllowed) return ActorId(0);
534 if (realTouchListIndex == uint.max) rewindTouchList();
535 while (realTouchListIndex < realTouchList.length) {
536 auto aid = ActorId(realTouchList.ptr[realTouchListIndex]);
537 ++realTouchListIndex;
538 if (aid.valid && (aid.flags!uint&AF_NOONTOUCH) == 0) return aid;
540 return ActorId(0);
544 public void doActorsThink () {
545 //FIXME
546 //static uint fflags = uint.max;
547 //if (fflags == uint.max) fflags = Actor.fields["flags"].ofs;
549 // we have too much memory!
550 __gshared uint[65536] validActorsList;
552 touchListAllowed = true;
553 scope(exit) touchListAllowed = false;
554 foreach (uint xid; Actor.getValidList(validActorsList[])) {
555 auto me = ActorId(xid);
556 if (me.valid) {
557 auto flags = me.fget_flags;
558 if ((flags&AF_NOCOLLISION) == 0) ugActorModify!false(me); // remove from grid
559 if (auto adef = findActorDef(me)) {
560 if ((flags&AF_NOTHINK) == 0) {
561 realTouchListIndex = uint.max;
562 curThinkingActor = me;
563 adef.callThink(me);
564 //if (me.x!int < 32) conwriteln("actor: ", me.id, "; attLightRGBX=", me.attLightRGBX!uint);
565 if (!me.valid) continue; // we are dead
566 flags = me.fget_flags; // in case script updated flags
568 if ((flags&AF_NOANIMATE) == 0) {
569 int aidx = me.fget_zAnimidx;
570 int nidx = adef.nextAnimIdx(me.fget_zAnimstate, me.fget_dir, aidx);
571 //conwriteln("actor ", me.id, " (", me.classtype!string, me.classname!string, "): state=", me.zAnimstate!string, "; aidx=", aidx, "; nidx=", nidx);
572 me.fset_zAnimidx = nidx;
573 //assert(me.fget_zAnimidx == nidx);
575 // put back to grid
576 if ((flags&AF_NOCOLLISION) == 0) ugActorModify!true(me);
580 plrKeysFix(0);
581 //{ import std.stdio : stdout; stdout.writeln("========================================="); }
582 //Actor.dumpActors();