1 module game.terminal; 2 3 import spasm.types; 4 import spasm.spa; 5 import spasm.rt.memory; 6 import spasm.rt.array : text; 7 import game.math; 8 import game.audio; 9 10 import std.range : only; 11 import std.range : empty, popFront, front; 12 13 nothrow: 14 @safe: 15 16 struct Bold { 17 mixin Node!"b"; 18 @prop string innerText = "█"; 19 } 20 struct Line { 21 nothrow: 22 mixin Node!"div"; 23 @prop string innerText; 24 @child Bold bold; 25 @visible!"bold" bool isLast = false; 26 void setLast(bool l) { 27 this.update!isLast(l); 28 } 29 this(string line) { 30 this.innerText = line; 31 } 32 } 33 34 alias TerminalCallback = void delegate() nothrow @safe; 35 struct Terminal { 36 nothrow: 37 mixin Node!"div"; 38 @style!"transparent" bool transparent = false; 39 @style!"hide" bool hidden; 40 @prop id = "a"; 41 @child List!(Line, "code") terminal_text_buffer; 42 @property bool indent = true; 43 @property int wait = 100; 44 @property int timeoutId; 45 @property int hideTimeoutId; 46 void cancel() { 47 clearTimeout(timeoutId); 48 } 49 void show() { 50 clearTimeout(hideTimeoutId); 51 spasm.dom.update!transparent(this,false); 52 spasm.dom.update!hidden(this,false); 53 } 54 void hide() { 55 spasm.dom.update!transparent(this,true); 56 hideTimeoutId = setTimeout(&setHidden, 1000); 57 } 58 void setHidden() { 59 spasm.dom.update!hidden(this,true); 60 } 61 void add(string line) @trusted { 62 add(allocator.make!(Line)(line)); 63 } 64 void add(Line* line) @trusted { 65 gSoundPlayer.playTerminal(); 66 if (terminal_text_buffer.items.length > 0) 67 terminal_text_buffer.items[$-1].setLast(false); 68 line.isLast = true; 69 if (terminal_text_buffer.items[].length > 20) 70 terminal_text_buffer.remove(0); 71 terminal_text_buffer.put(line); 72 } 73 void clear() { 74 terminal_text_buffer.shrinkTo(0); 75 } 76 } 77 78 enum terminal_text_ident = "> "; 79 80 struct TextItem { 81 int idx; 82 string text; 83 } 84 85 auto l(int t, string line) { 86 return TextItem(t,line); 87 } 88 89 enum terminal_text_garbage = "´A1e{∏éI9·NQ≥ÀΩ¸94CîyîR›kÈ¡˙ßT-;ûÅf^˛,¬›A∫S〫ÕÕ1f@çX8ÎRjßf•ò√ã0êÃcÄ]Î≤moDÇ’ñ‰\\ˇ≠n=(s7É;"; 90 91 enum terminal_text_title = only(l(0,"> UNDERRUN"), 92 l(20,"> "), 93 l(21,"> CONCEPT, GRAPHICS & PROGRAMMING:"), 94 l(22,"> DOMINIC SZABLEWSKI // PHOBOSLAB.ORG"), 95 l(42,"> "), 96 l(43,"> MUSIC:"), 97 l(44,"> ANDREAS LÖSCH // NO-FATE.NET"), 98 l(74,"> "), 99 l(75,"> SYSTEM VERSION: 13.20.18"), 100 l(76,"> CPU: PL(R) Q-COATL 7240 @ 12.6 THZ"), 101 l(77,"> MEMORY: 108086391056891900 BYTES"), 102 l(78,"> "), 103 l(79,"> CONNECTING...") 104 ); 105 106 enum terminal_text_story = only(l(0,"> DATE: SEP. 13, 2718 - 13:32"), 107 l(1,"> CRITICAL SOFTWARE FAILURE DETECTED"), 108 l(2,"> ANALYZING..."), 109 l(41,"> "), 110 l(42,"> ERROR CODE: JS13K2018"), 111 l(43,"> STATUS: SYSTEMS OFFLINE"), 112 l(44,"> DESCRIPTION: BUFFER UNDERRUN DUE TO SATCOM R.U.D."), 113 l(45,"> AFFECTED SYSTEM: FACILITY AUTOMATION"), 114 l(46,"> AFFECTED SUBSYSTEMS: AI, RADIATION SHIELDS, POWER MANAGEMENT"), 115 l(47,"> "), 116 l(48,"> INITIATING RESCUE SYSTEM..."), 117 l(78,"> FAILED"), 118 l(79,"> "), 119 l(80,"> ATTEMPTING AUTOMATED REBOOT..."), 120 l(110,"> FAILED"), 121 l(120,"> "), 122 l(121,"> "), 123 l(122,"> MANUAL REBOOT OF ALL SYSTEMS REQUIRED"), 124 l(132,"> "), 125 l(133,"> USE WASD OR CURSOR KEYS TO MOVE, MOUSE TO SHOOT"), 126 l(134,"> CLICK TO INITIATE YOUR DEPLOYMENT"), 127 l(135,"> ") 128 ); 129 130 enum terminal_text_outro = only(l(0,"> ALL SATELLITE LINKS ONLINE"), 131 l(1,"> CONNECTING..."), 132 l(31,"> CONNECTION ESTABLISHED"), 133 l(32,"> RECEIVING TRANSMISSION..."), 134 l(62,"> "), 135 l(63,"> SENT: SEP. 13, 2018"), 136 l(64,"> RCVD: SEP. 13, 2718"), 137 l(65,"> "), 138 l(66,"> THANKS FOR PLAYING ❤"), 139 l(76,"> "), 140 l(77,"> I HAVE PREVIOUSLY BEEN A PROUD SPONSOR OF THE JS13K"), 141 l(78,"> COMPETITION SINCE THE VERY FIRST ONE BACK IN 2012."), 142 l(79,"> HOWEVER, THIS YEAR\"S COMPETITION WAS MY FIRST ONE"), 143 l(80,"> AS A PARTICIPANT AND IT HAS BEEN TREMENDOUS FUN!"), 144 l(81,"> "), 145 l(82,"> I WANT TO THANK MY DEAR FRIEND ANDREAS LÖSCH OF"), 146 l(83,"> NO-FATE.NET FOR COMPOSING SOME AWESOME MUSIC ON"), 147 l(84,"> SUCH SHORT NOTICE."), 148 l(85,"> "), 149 l(86,"> FURTHER THANKS GO OUT TO THE JS13K STAFF, THE"), 150 l(87,"> SONANT-X DEVELOPERS AND ALL OTHER PARTICIPANTS"), 151 l(88,"> IN THIS YEAR\"S JS13K. SEE YOU NEXT YEAR!"), 152 l(89,"> "), 153 l(90,"> DOMINIC"), 154 l(110,"> END OF TRANSMISSION") 155 ); 156 157 struct TerminalWriter(LineRange) { 158 nothrow: 159 Terminal* terminal; 160 LineRange lines; 161 TerminalCallback callback; 162 int idx = 0; 163 this(Terminal* terminal, ref LineRange lines, TerminalCallback callback) { 164 this.terminal = terminal; 165 this.lines = lines; 166 this.callback = callback; 167 } 168 void writeLine() { 169 if (lines.empty) { 170 if (callback) callback(); 171 return; 172 } 173 if (lines.front().idx == idx) { 174 // TODO: 175 // audio_play(audio_sfx_terminal); 176 terminal.add(lines.front().text); 177 lines.popFront(); 178 } 179 idx++; 180 terminal.timeoutId = setTimeout(&writeLine, terminal.wait); 181 } 182 } 183 184 void terminal_write_text(LineRange)(ref Terminal terminal, LineRange lines, TerminalCallback callback = null) @trusted { 185 auto writer = allocator.make!(TerminalWriter!(LineRange))(&terminal, lines, callback); 186 writer.writeLine(); 187 } 188 189 auto terminal_show_notice(LineRange)(ref Terminal terminal, LineRange notice, TerminalCallback callback = null) @trusted { 190 static struct Handler { 191 Terminal* terminal; 192 TerminalCallback callback; 193 void trigger() { 194 terminal.timeoutId = setTimeout(&end,2000); 195 } 196 void end() { 197 terminal.hide(); 198 if (callback) callback(); 199 } 200 } 201 terminal.clear(); 202 terminal.cancel(); 203 terminal.show(); 204 auto handler = allocator.make!(Handler)(&terminal, callback); 205 terminal.terminal_write_text(notice, &handler.trigger); 206 } 207 208 extern(C) int setTimeout(int ctx, int ptr, int ms); 209 extern(C) void clearTimeout(int id); 210 211 int setTimeout(Delegate)(Delegate del, int ms) { 212 return setTimeout(del.toTuple.expand,ms); 213 } 214 215 auto terminal_run_intro(ref Terminal terminal) @trusted { 216 terminal.clear(); 217 static struct Handler { 218 nothrow: 219 @safe: 220 Terminal* terminal; 221 this(Terminal* t) { 222 this.terminal = t; 223 } 224 void trigger() { 225 setTimeout(&end, 4000); 226 } 227 void end() { 228 (*terminal).terminal_run_garbage(); 229 } 230 } 231 auto handler = allocator.make!(Handler)(&terminal); 232 terminal.terminal_write_text(terminal_text_title, &handler.trigger); 233 } 234 235 struct TerminalGarbage { 236 nothrow: 237 int idx, s, e; 238 this(int i) { 239 idx = i; 240 popFront(); 241 } 242 void popFront() { 243 s = cast(int)(random()*terminal_text_garbage.length); 244 e = cast(int)(random()*(terminal_text_garbage.length - s)); 245 idx++; 246 } 247 auto empty() { return idx > 64; } 248 auto front() { 249 return l(idx,terminal_text_garbage[s .. s+e]); 250 } 251 } 252 253 auto terminal_run_garbage(ref Terminal terminal) @trusted { 254 terminal.indent = false; 255 terminal.wait = 16; 256 static struct Handler { 257 nothrow: 258 @safe: 259 Terminal* terminal; 260 this(Terminal* t) { 261 this.terminal = t; 262 } 263 void end() { 264 (*terminal).terminal_run_story(); 265 } 266 } 267 auto handler = allocator.make!(Handler)(&terminal); 268 terminal.terminal_write_text(TerminalGarbage(0), &handler.end); 269 } 270 271 auto terminal_run_story(ref Terminal terminal) { 272 terminal.indent = true; 273 terminal.wait = 100; 274 terminal.terminal_write_text(terminal_text_story); 275 } 276 277 auto terminal_run_outro(ref Terminal terminal) { 278 // TODO 279 // c.style.opacity = 0.3; 280 terminal.clear(); 281 282 terminal.cancel(); 283 terminal.show(); 284 terminal.terminal_write_text(terminal_text_outro); 285 }