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 }