switched to GPLv3 ONLY, because i don't trust FSF anymore
[knightmare.git] / x_knightmare.d
blob8dea9bc2923dc76e7d84fe75b86a2289f19dddaa
1 /* coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
4 * Based on the DOS Knightmare source code by Andrew Zabolotny
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, version 3 of the License ONLY.
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 x_knightmare;
20 import arsd.color;
21 import arsd.simpledisplay;
22 import arsd.image;
24 import iv.cmdcon;
25 import iv.cmdcon.gl;
26 import iv.cmdcon.keybinds;
27 import iv.glbinds.util;
28 import iv.prng.pcg32;
29 import iv.prng.seeder;
30 import iv.strex;
31 import iv.vfs;
33 import tatlas;
34 import zmythspr;
35 import keybinds;
36 import levelmap;
37 import mesactor;
38 import mesengine;
39 import monsters;
40 import music;
43 // ////////////////////////////////////////////////////////////////////////// //
44 version(single_binary) {
45 immutable pakdata = import("data/base.pak");
49 // ////////////////////////////////////////////////////////////////////////// //
50 private __gshared VFSDriver dataPathDriver;
51 private __gshared string dataPath;
54 // ////////////////////////////////////////////////////////////////////////// //
55 void setDataPath (const(char)[] path) {
56 if (dataPathDriver !is null) assert(0);
57 if (path.length == 0) {
58 dataPath = "./";
59 } else if (path[$-1] != '/') {
60 dataPath = path.idup~"/";
61 } else {
62 dataPath = path.idup;
64 dataPathDriver = vfsNewDiskDriver(dataPath);
65 vfsRegister!"first"(dataPathDriver);
69 string getDataPath () { pragma(inline, true); return dataPath; }
72 void addPak (const(char)[] fname) {
73 conwriteln("; adding '", fname, "'...");
74 vfsAddPak(fname);
78 void addPak (VFile fl) {
79 vfsAddPak(fl);
83 // ////////////////////////////////////////////////////////////////////////// //
84 int c2i() (in auto ref Color c) nothrow @safe @nogc {
85 pragma(inline, true);
86 return c.r|(c.g<<8)|(c.b<<16);
90 Color i2c() (in int c) nothrow @safe @nogc {
91 pragma(inline, true);
92 return Color(c&0xff, (c>>8)&0xff, (c>>16)&0xff);
96 // ////////////////////////////////////////////////////////////////////////// //
97 void initTitle () {
98 MemoryImage title;
99 scope(exit) delete title;
101 auto fl = VFile("title.png");
102 if (fl.size > 1024*1024*32) assert(0, "title image too big");
103 auto data = new ubyte[](cast(uint)fl.size);
104 scope(exit) delete data;
105 fl.rawReadExact(data);
106 title = loadImageFromMemory(data);
108 uint[] img;
109 scope(exit) delete img;
110 img.length = title.width*title.height;
111 // sky color
112 auto csky = title.getPixel(0, 0);
113 VMIFace["gtitle_csky"].set!int(c2i(csky));
114 // find lightning colors
115 Color[2] clgt;
116 foreach (immutable dx; 1..title.width) if (title.getPixel(dx, 0) != csky) { clgt[0] = title.getPixel(dx, 0); break; }
117 foreach_reverse (immutable dx; 1..title.width) if (title.getPixel(dx, 0) != csky) { clgt[1] = title.getPixel(dx, 0); break; }
118 // create first overlay image with lightning
119 foreach (immutable dy; 0..title.height) {
120 foreach (immutable dx; 0..title.width) {
121 Color c = title.getPixel(dx, dy);
122 if (c != clgt[0]) c = Color.transparent; else c = Color.white;
123 img[dy*title.width+dx] = c.asUint;
126 atlasAdd("titleovl_", 0, img[], title.width, title.height);
127 // create second overlay image with lightning
128 foreach (immutable dy; 0..title.height) {
129 foreach (immutable dx; 0..title.width) {
130 Color c = title.getPixel(dx, dy);
131 if (c != clgt[1]) c = Color.transparent; else c = Color.white;
132 img[dy*title.width+dx] = c.asUint;
135 atlasAdd("titleovl_", 1, img[], title.width, title.height);
136 // create title image without lightnings
137 foreach (immutable dy; 0..title.height) {
138 foreach (immutable dx; 0..title.width) {
139 Color c = title.getPixel(dx, dy);
140 if (c == clgt[0] || c == clgt[1]) c = csky;
141 img[dy*title.width+dx] = c.asUint;
144 atlasAdd("title_", 0, img[], title.width, title.height);
148 // ////////////////////////////////////////////////////////////////////////// //
149 void createGamePhases () {
150 conRegVar!paused("pause", "pause game");
152 conRegFunc!(() {
153 try {
154 VMIFace["conhookAbortGame"]();
155 } catch (Throwable e) {
156 conwriteln("FATAL: ", e.msg);
157 assert(0, e.msg);
159 })("abort_game", "abort current game and return to title");
161 conRegFunc!(() {
162 try {
163 VMIFace["conhookGameOver"]();
164 } catch (Throwable e) {
165 conwriteln("FATAL: ", e.msg);
166 assert(0, e.msg);
168 })("game_over", "run \"game over\" sequence");
170 conRegFunc!(() {
171 try {
172 VMIFace["conhookStageNext"]();
173 } catch (Throwable e) {
174 conwriteln("FATAL: ", e.msg);
175 assert(0, e.msg);
177 })("stage_next", "go to next stage");
179 conRegFunc!(() {
180 try {
181 VMIFace["conhookStagePrev"]();
182 } catch (Throwable e) {
183 conwriteln("FATAL: ", e.msg);
184 assert(0, e.msg);
186 })("stage_prev", "go to previous stage");
190 // ////////////////////////////////////////////////////////////////////////// //
191 __gshared bool optPauseOnDefocus = true;
192 __gshared bool optVSync = true;
195 // ////////////////////////////////////////////////////////////////////////// //
196 void main (string[] args) {
197 import std.functional : toDelegate;
199 glconShowKey = "M-Grave";
201 conRegVar!optFixedSpawn("predictable_spawn", "should spawn PRNG be initialized with fixed seeds? (default: no)");
202 conRegVar!optPauseOnDefocus("defocus_pause", "autopause the game when window loses focus");
203 conRegVar!optVSync("v_vsync", "OpenGL vsync");
204 conRegVar!Scale(1, 16, "v_scale", "game scale (can be set only on startup)");
206 MESDisableDumps = true;
207 conRegVar!MESDisableDumps("dbg_vm_nodump", "don't dump compiler VM code");
209 //glconSetAndSealFPS(18);
210 glconSetAndSealFPS(20);
211 //glconSetAndSealFPS(35);
213 static void setDP () {
214 version(single_binary) {
215 //immutable pakdata = import("data/base.pak");
216 addPak(wrapMemoryRO(pakdata, "data/base.pak"));
217 } else {
218 version(rdmd) {
219 setDataPath("data");
220 } else version(Windows) {
221 setDataPath("data");
222 } else {
223 import std.file : thisExePath;
224 import std.path : dirName;
225 setDataPath(thisExePath.dirName~"/data");
227 try {
228 addPak(getDataPath~"base.pak");
229 } catch (Exception e) {
234 setDP();
236 setupDefaultKeyBindings();
238 conProcessQueue(256*1024); // load config
239 conProcessArgs!true(args);
240 conProcessQueue(256*1024);
242 conSealVar("predictable_spawn");
243 conSealVar("v_vsync");
244 conSealVar("v_scale");
246 mesIntroduceEnum!Key; // arsd.Key
248 loadScripts();
250 mesRegisterBuiltin("paused", delegate () => cast(bool)paused);
251 mesRegisterBuiltin("paused", delegate (bool v) { paused = v; });
253 mesRegisterBuiltin("isAudioFucked", delegate () => cast(bool)isAudioFucked);
255 mesRegisterBuiltin("optPauseOnDefocus", delegate () => cast(bool)optPauseOnDefocus);
257 mesRegisterBuiltin("atlasUpdateTexture", delegate (string pfx, int idx) {
258 if (auto ai = atlasGet(pfx, idx)) ai.updateTexture();
261 mesRegisterBuiltin("atlasSetPixel", delegate (string pfx, int idx, int x, int y, int c) {
262 if (auto ai = atlasGet(pfx, idx)) {
263 ai.setPixel(x, y, i2c(c));
267 mesRegisterBuiltin("atlasGetPixel", delegate (string pfx, int idx, int x, int y) {
268 if (auto ai = atlasGet(pfx, idx)) {
269 auto clr = ai.getPixel(x, y);
270 if (clr.a == 0) return -666;
271 return c2i(clr);
272 } else {
273 return -666;
278 loadStages();
279 loadMonsters();
280 loadMusic();
281 loadSounds();
283 musicStartThread();
285 //paused = true;
287 // first time setup
288 oglSetupDG = delegate () {
289 glconCtlWindow.vsync = optVSync;
291 createGamePhases();
292 initTitle();
293 seedPRNGs();
295 try {
296 VMIFace["onGameLoadGraphics"]();
297 } catch (Throwable e) {
298 conwriteln("FATAL: ", e.msg);
299 assert(0, e.msg);
302 // this will create texture
303 //gxResize(glconCtlWindow.width, glconCtlWindow.height);
305 atlasLoadGL();
307 try {
308 VMIFace["onGameInit"]();
309 } catch (Throwable e) {
310 conwriteln("FATAL: ", e.msg);
311 assert(0, e.msg);
315 // draw main screen
316 redrawFrameDG = delegate () {
317 oglSetup2D(glconCtlWindow.width, glconCtlWindow.height);
320 glClearColor(0, 0, 0, 0);
321 glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_ACCUM_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
324 // scale everything
325 glMatrixMode(GL_MODELVIEW);
326 glScalef(Scale, Scale, 1);
328 try {
329 VMIFace["onGameRender"]();
330 } catch (Throwable e) {
331 conwriteln("FATAL: ", e.msg);
332 assert(0, e.msg);
336 // rebuild main screen (do any calculations we might need)
337 nextFrameDG = delegate () {
338 try {
339 VMIFace["onGameTick"]();
340 } catch (Throwable e) {
341 conwriteln("FATAL: ", e.msg);
342 assert(0, e.msg);
344 actionBindFrameEnd();
347 keyEventDG = delegate (KeyEvent event) {
348 try {
349 VMIFace["eventKeyKey"].set!Key(event.key);
350 VMIFace["eventKeyPressed"].set!bool(event.pressed);
351 VMIFace["eventKeyCtrl"].set!bool((event.modifierState&ModifierState.ctrl) != 0);
352 VMIFace["eventKeyAlt"].set!bool((event.modifierState&ModifierState.alt) != 0);
353 VMIFace["eventKeyShift"].set!bool((event.modifierState&ModifierState.shift) != 0);
354 VMIFace["eventKeyHyper"].set!bool((event.modifierState&ModifierState.windows) != 0);
356 // process bindings
357 ActionKey kk;
358 bool changed = doActionBind(event, &kk);
359 VMIFace["onActionBindChanged"].set!bool(changed);
360 VMIFace["onActionBindWasPress"].set!bool(event.pressed);
361 if (changed) VMIFace["actionBindKK"].set!ActionKey(kk);
363 VMIFace["onGameKeyEvent"]();
364 } catch (Throwable e) {
365 conwriteln("FATAL: ", e.msg);
366 assert(0, e.msg);
371 mouseEventDG = delegate (MouseEvent event) {
374 charEventDG = delegate (dchar ch) {
377 resizeEventDG = delegate (int wdt, int hgt) {
381 focusEventDG = delegate (bool focused) {
382 try {
383 VMIFace["onGameFocusEvent"](focused);
384 } catch (Throwable e) {
385 conwriteln("FATAL: ", e.msg);
386 assert(0, e.msg);
390 glconRunGLWindow/*Resizeable*/(320*Scale, 200*Scale, "Konami's Knightmare", "KNIGHTMARE");
391 musicQuit();