├── .editorconfig
├── .gitignore
├── .lvimrc
├── .prettierrc.js
├── LICENSE
├── README.md
├── assets
├── all-fedora.txt
├── clh-logo-white.svg
├── clh-thumb.png
├── cmds
│ ├── README.md
│ ├── bash.js
│ ├── from-path-fedora.txt
│ ├── from-path-ubuntu.txt
│ ├── generateBashCmds.js
│ ├── html.js
│ ├── js.js
│ └── python.js
├── fonts
│ ├── _overpass.scss
│ ├── example.html
│ ├── fonts.css
│ ├── overpass-bold-italic.eot
│ ├── overpass-bold-italic.ttf
│ ├── overpass-bold-italic.woff
│ ├── overpass-bold-italic.woff2
│ ├── overpass-bold.eot
│ ├── overpass-bold.ttf
│ ├── overpass-bold.woff
│ ├── overpass-bold.woff2
│ ├── overpass-extrabold-italic.eot
│ ├── overpass-extrabold-italic.ttf
│ ├── overpass-extrabold-italic.woff
│ ├── overpass-extrabold-italic.woff2
│ ├── overpass-extrabold.eot
│ ├── overpass-extrabold.ttf
│ ├── overpass-extrabold.woff
│ ├── overpass-extrabold.woff2
│ ├── overpass-extralight-italic.eot
│ ├── overpass-extralight-italic.ttf
│ ├── overpass-extralight-italic.woff
│ ├── overpass-extralight-italic.woff2
│ ├── overpass-extralight.eot
│ ├── overpass-extralight.ttf
│ ├── overpass-extralight.woff
│ ├── overpass-extralight.woff2
│ ├── overpass-heavy-italic.eot
│ ├── overpass-heavy-italic.ttf
│ ├── overpass-heavy-italic.woff
│ ├── overpass-heavy-italic.woff2
│ ├── overpass-heavy.eot
│ ├── overpass-heavy.ttf
│ ├── overpass-heavy.woff
│ ├── overpass-heavy.woff2
│ ├── overpass-italic.eot
│ ├── overpass-italic.ttf
│ ├── overpass-italic.woff
│ ├── overpass-italic.woff2
│ ├── overpass-light-italic.eot
│ ├── overpass-light-italic.ttf
│ ├── overpass-light-italic.woff
│ ├── overpass-light-italic.woff2
│ ├── overpass-light.eot
│ ├── overpass-light.ttf
│ ├── overpass-light.woff
│ ├── overpass-light.woff2
│ ├── overpass-mono-bold.eot
│ ├── overpass-mono-bold.otf
│ ├── overpass-mono-bold.ttf
│ ├── overpass-mono-bold.woff
│ ├── overpass-mono-bold.woff2
│ ├── overpass-mono-light.eot
│ ├── overpass-mono-light.otf
│ ├── overpass-mono-light.ttf
│ ├── overpass-mono-light.woff
│ ├── overpass-mono-light.woff2
│ ├── overpass-mono-regular.eot
│ ├── overpass-mono-regular.otf
│ ├── overpass-mono-regular.ttf
│ ├── overpass-mono-regular.woff
│ ├── overpass-mono-regular.woff2
│ ├── overpass-mono-semibold.eot
│ ├── overpass-mono-semibold.otf
│ ├── overpass-mono-semibold.ttf
│ ├── overpass-mono-semibold.woff
│ ├── overpass-mono-semibold.woff2
│ ├── overpass-regular.eot
│ ├── overpass-regular.ttf
│ ├── overpass-regular.woff
│ ├── overpass-regular.woff2
│ ├── overpass-semibold-italic.eot
│ ├── overpass-semibold-italic.ttf
│ ├── overpass-semibold-italic.woff
│ ├── overpass-semibold-italic.woff2
│ ├── overpass-semibold.eot
│ ├── overpass-semibold.ttf
│ ├── overpass-semibold.woff
│ ├── overpass-semibold.woff2
│ ├── overpass-thin-italic.eot
│ ├── overpass-thin-italic.ttf
│ ├── overpass-thin-italic.woff
│ ├── overpass-thin-italic.woff2
│ ├── overpass-thin.eot
│ ├── overpass-thin.ttf
│ ├── overpass-thin.woff
│ ├── overpass-thin.woff2
│ └── overpass.css
├── images
│ ├── monitor_bezel_outline.png
│ └── monitor_fire_inner.png
├── models
│ ├── CLH_Computer.mtl.gz
│ ├── CLH_Computer.obj.gz
│ ├── CLH_Shuttle.mtl.gz
│ ├── CLH_Shuttle.obj.gz
│ ├── CLH_ep2_computer_high_poly.mtl.gz
│ ├── CLH_ep2_computer_high_poly.obj.gz
│ ├── CLH_ep2_cyc_wall.mtl.gz
│ └── CLH_ep2_cyc_wall.obj.gz
├── sfx
│ ├── Waveshaper - 66 MHz.mp3
│ ├── boot.mp3
│ ├── cmd-bad.mp3
│ ├── cmd-gold.mp3
│ ├── cmd-good.mp3
│ ├── keypress.mp3
│ ├── leaderboard.mp3
│ ├── menu-music.mp3
│ ├── play.mp3
│ ├── timer-relaxed.mp3
│ └── timer-urgent.mp3
├── styles.css
└── textures
│ ├── clh_test_pattern.jpg
│ ├── equirectangular.png
│ └── wall.png
├── index.html
├── package.json
├── src
├── Fire.js
├── MTLLoaderPhysical.js
├── app.js
├── cmds.js
├── config.js
├── console-canvas.js
├── keycodes.js
├── leaderboard.js
├── main.js
├── palette.js
├── sfx.js
├── sleep.js
├── states.js
├── three-utils.js
├── tween-camera.js
└── unzip.js
└── start.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig: http://EditorConfig.org
2 |
3 | # Top-most EditorConfig file
4 | root = true
5 |
6 | # Rules for JavaScript files:
7 |
8 | [*.{js,py,json,sh,html}]
9 | # 4 space indentation
10 | indent_style = space
11 | indent_size = 4
12 | # No trailing spaces
13 | trim_trailing_whitespace = true
14 | # Unix-style newlines
15 | end_of_line = lf
16 | # Newline ending every file
17 | insert_final_newline = true
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /tags
3 | .idea
4 | npm-debug.log
5 | dist
6 | package-lock.json
7 | .DS_Store
8 | *~
9 |
10 | assets/models/*.mtl
11 | assets/models/*.obj
12 |
--------------------------------------------------------------------------------
/.lvimrc:
--------------------------------------------------------------------------------
1 | let g:ale_fixers = {}
2 | let g:ale_fixers['javascript'] = ['prettier']
3 | let g:ale_fixers['json'] = ['prettier']
4 | let g:ale_fixers['css'] = ['prettier']
5 |
6 | let g:ale_fix_on_save = 1
7 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/.prettierrc.js
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # clh-bash
2 |
3 | 
4 |
5 | ## Development
6 |
7 | Install dependencies.
8 |
9 | npm install
10 |
11 | Extract the compressed MTL and OBJ files.
12 |
13 | npm run extract
14 |
15 |
16 |
17 | Extract the compressed MTL and OBJ files for Windows OS.
18 |
19 | npm run unzip
20 |
21 | Start the dev server.
22 |
23 | npm start
24 |
25 | If you need to make changes to MTL/OBJ files and want to preserve them, run this to compress them. Only gzipped MTL/OBJ files are saved in the repo.
26 |
27 | npm run compress
28 |
29 | ## Leaderboard selection
30 |
31 | Bash supports multiple options for leaderboard storage. The default is in-browser `localStorage`. A networked leaderboard is also supported, through sending leaderboard entries to a Parse server.
32 |
33 | ### Networked leaderboards with Parse
34 |
35 | If you want a networked leaderboard, you must have a Parse instance up and running. Then, open `src/config.js` and change `PARSE_URL` to point to the URL of your parse server.
36 |
37 | Finally, when you launch Bash, add `&storage=parse` to the end.
38 |
39 | ### Selecting a leaderboard namespace
40 |
41 | Both `localStorage` and Parse leaderboard support namespacing. In other words, you can give the leaderboard a name. This is especially useful if you need to maintain multiple leaderboards, for tournament rounds, timed events at conferences, etc. Switching between leaderboards is as easy as changing the namespace.
42 |
43 | Then, when you launch Bash, add `&name=NAMESPACE` to the end of Bash's URL. Note that you can change the word `NAMESPACE` to be anything you want.
44 |
45 |
46 | ### How to get Help
47 |
48 | 1. Post a question in the repo [issues](https://github.com/CommandLineHeroes/clh-bash/issues)
49 | 2. Ask a question in real-time in our [public Discord server](https://discord.gg/rpnmpVj)
50 | 3. Send a tweet to one of the twitter links below [social](#social)
51 |
52 | ## Community
53 |
54 | Join our [public Discord server](https://discord.gg/rpnmpVj)!
55 |
56 | ## Social
57 |
58 | - Jared Sprague [@caramelcode](https://twitter.com/caramelcode)
59 | - Michael Clayton [@mwcz](https://twitter.com/mwcz)
60 | - [Command Line Heroes](https://www.redhat.com/en/command-line-heroes)
61 | - [#CommandLinePod](https://twitter.com/hashtag/CommandLinePod?src=hash)
62 |
--------------------------------------------------------------------------------
/assets/all-fedora.txt:
--------------------------------------------------------------------------------
1 | Fedora 29 - x86_64 37 kB/s | 10 MB 04:41
2 |
--------------------------------------------------------------------------------
/assets/clh-logo-white.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/assets/clh-thumb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/clh-thumb.png
--------------------------------------------------------------------------------
/assets/cmds/README.md:
--------------------------------------------------------------------------------
1 | # Building a shell command database
2 |
3 | We pull from several sources to populate the command database.
4 |
5 | _Note: all commands to be run from this directory, `assets/commands/`._
6 |
7 | ## From \$PATH (Fedora)
8 |
9 | Get all executables from the \$PATH and all built-in bash functions.
10 |
11 | compgen -bc > from-path-fedora.txt
12 |
13 | Get all DNF packages (this does not get executable names).
14 |
15 | dnf list all
16 |
17 | ## JavaScript
18 |
19 | A few types of JS code are accepted.
20 |
21 | - all [keywords](https://tc39.github.io/ecma262/#sec-keywords)
22 | - some [literals](https://tc39.github.io/ecma262/#sec-ecmascript-language-lexical-grammar-literals)
23 | - literals to include: `null`, `true`, `false`
24 | - all [properties of the global object](https://tc39.github.io/ecma262/#sec-global-object)
25 | - all objects listed in [chapters 19-26](https://tc39.github.io/ecma262/#sec-fundamental-objects)
26 | - for example, the subchapters of chapter 19 are "Object Objects", "Function Objects", "Boolean Objects", "Symbol Objects", and "Error Objects", so `object`, `function`, `boolean`, `symbol`, and `error` should be used.
27 | - additional [built-in properties of the global object](https://tc39.github.io/ecma262/#sec-additional-properties-of-the-global-object)
28 | - `async` should be added. it won't appear in the above lists because it is not a proper keyword (it's only a keyword in certain contexts but can still be used as a variable name, etc).
29 | - `window`, `document`, `navigator` I can't find in any spec but should be included
30 |
--------------------------------------------------------------------------------
/assets/cmds/generateBashCmds.js:
--------------------------------------------------------------------------------
1 | // This file will generate and write the bash.js file.
2 | // It this takes a set of text files that are generated from "compgen -bc > file.txt" as input
3 | // Sorts, removes duplicates, and unions the files together then writes bash.js
4 | // This makes it possible to combine different linux distros together e.g. Fedora, Ubuntu, RHEL
5 | // Example Usage:
6 | // node generateBashCmds.js from-path-fedora.txt from-path-ubuntu.txt
7 |
8 | "use strict";
9 |
10 | let fs = require("fs");
11 |
12 | let allCmds = [];
13 |
14 | // First load the files from command line arguments
15 | for (let i = 2; i < process.argv.length; i++) {
16 | let fileName = process.argv[i];
17 | console.log("Loading file: " + fileName);
18 |
19 | let fileContents = fs.readFileSync(fileName, "ascii");
20 | let linesArray = fileContents.split("\n");
21 |
22 | allCmds = allCmds.concat(linesArray);
23 |
24 | console.log("command count: ", linesArray.length);
25 | }
26 |
27 | console.log("all commands count: ", allCmds.length);
28 |
29 | // Sort
30 | allCmds.sort();
31 |
32 | // Remove duplicates
33 | let uniqueCmds = allCmds.filter(function(elem, index, self) {
34 | return index === self.indexOf(elem);
35 | });
36 |
37 | console.log("unique commands count: ", uniqueCmds.length);
38 |
39 | // Write to file
40 | console.log("Writing to: bash.js");
41 | let outFileContent = "/** Generated from generateBashCmds.js **/\n";
42 | outFileContent += "export default [\n";
43 | uniqueCmds.forEach(value => {
44 | outFileContent += '"' + value + '",\n';
45 | });
46 | outFileContent += "];\n";
47 |
48 | try {
49 | fs.writeFileSync("./bash.js", outFileContent);
50 | } catch (err) {
51 | console.error(err);
52 | }
53 |
--------------------------------------------------------------------------------
/assets/cmds/html.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: "HTML5",
3 | commonCmds: [
4 | "html",
5 | "head",
6 | "title",
7 | "link",
8 | "meta",
9 | "style",
10 | "body",
11 | "a",
12 | "nav",
13 | "h1",
14 | "h2",
15 | "h3",
16 | "p",
17 | "hr",
18 | "pre",
19 | "blockquote",
20 | "ol",
21 | "ul",
22 | "li",
23 | "div",
24 | "br",
25 | "table",
26 | "tr",
27 | "td"
28 | ],
29 | cmds: [
30 | "html",
31 | "head",
32 | "title",
33 | "base",
34 | "link",
35 | "meta",
36 | "style",
37 | "body",
38 | "article",
39 | "section",
40 | "nav",
41 | "aside",
42 | "h1",
43 | "h2",
44 | "h3",
45 | "h4",
46 | "h5",
47 | "h6",
48 | "header",
49 | "footer",
50 | "p",
51 | "address",
52 | "hr",
53 | "pre",
54 | "blockquote",
55 | "ol",
56 | "ul",
57 | "li",
58 | "dl",
59 | "dt",
60 | "dd",
61 | "figure",
62 | "figcaption",
63 | "main",
64 | "div",
65 | "a",
66 | "em",
67 | "strong",
68 | "small",
69 | "s",
70 | "cite",
71 | "q",
72 | "dfn",
73 | "abbr",
74 | "ruby",
75 | "rb",
76 | "rt",
77 | "rtc",
78 | "rp",
79 | "data",
80 | "time",
81 | "code",
82 | "var",
83 | "samp",
84 | "kbd",
85 | "sub",
86 | "sup",
87 | "i",
88 | "b",
89 | "u",
90 | "mark",
91 | "bdi",
92 | "bdo",
93 | "span",
94 | "br",
95 | "wbr",
96 | "ins",
97 | "del",
98 | "picture",
99 | "source",
100 | "img",
101 | "iframe",
102 | "embed",
103 | "object",
104 | "param",
105 | "video",
106 | "audio",
107 | "track",
108 | "map",
109 | "area",
110 | "table",
111 | "caption",
112 | "colgroup",
113 | "col",
114 | "tbody",
115 | "thead",
116 | "tfoot",
117 | "tr",
118 | "td",
119 | "th",
120 | "form",
121 | "label",
122 | "input",
123 | "button",
124 | "select",
125 | "datalist",
126 | "optgroup",
127 | "option",
128 | "textarea",
129 | "output",
130 | "progress",
131 | "meter",
132 | "fieldset",
133 | "legend",
134 | "details",
135 | "summary",
136 | "dialog",
137 | "script",
138 | "noscript",
139 | "template",
140 | "canvas",
141 | "slot",
142 | "hr",
143 | "fieldset",
144 | "legend",
145 | "button",
146 | "details",
147 | "summary",
148 | "marquee",
149 | "meter",
150 | "progress",
151 | "select",
152 | "textarea",
153 | "marquee"
154 | ]
155 | };
156 |
--------------------------------------------------------------------------------
/assets/cmds/js.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This is a list of JavaScript keywords, "standard library" objects, etc.
3 | *
4 | * See the README in this directory for more on how this list is assembled.
5 | *
6 | * There are duplicates, and that's okay. But if you are removing items, be sure to look for multiple entries!
7 | */
8 | export default {
9 | name: "JavaScript",
10 | commonCmds: [
11 | "if",
12 | "else",
13 | "for",
14 | "function",
15 | "let",
16 | "var",
17 | "const",
18 | "JSON",
19 | "Date",
20 | "window"
21 | ],
22 | cmds: [
23 | // keywords
24 | "await",
25 | "break",
26 | "case",
27 | "catch",
28 | "class",
29 | "const",
30 | "continue",
31 | "debugger",
32 | "default",
33 | "delete",
34 | "do",
35 | "else",
36 | "export",
37 | "extends",
38 | "finally",
39 | "for",
40 | "function",
41 | "if",
42 | "import",
43 | "in",
44 | "instanceof",
45 | "new",
46 | "return",
47 | "super",
48 | "switch",
49 | "this",
50 | "throw",
51 | "try",
52 | "typeof",
53 | "var",
54 | "void",
55 | "while",
56 | "with",
57 | "yield",
58 | // some literals
59 | "null",
60 | "true",
61 | "false",
62 | // global object properties
63 | "Infinity",
64 | "NaN",
65 | "undefined",
66 | "eval",
67 | "isFinite",
68 | "isNaN",
69 | "parseFloat",
70 | "parseInt",
71 | "decodeURI",
72 | "decodeURIComponent",
73 | "encodeURI",
74 | "encodeURIComponent",
75 | "Array",
76 | "ArrayBuffer",
77 | "Boolean",
78 | "DataView",
79 | "Date",
80 | "Error",
81 | "EvalError",
82 | "Float32Array",
83 | "Float64Array",
84 | "Function",
85 | "Int8Array",
86 | "Int16Array",
87 | "Int32Array",
88 | "Map",
89 | "Number",
90 | "Object",
91 | "Promise",
92 | "Proxy",
93 | "RangeError",
94 | "ReferenceError",
95 | "RegExp",
96 | "Set",
97 | "SharedArrayBuffer",
98 | "String",
99 | "Symbol",
100 | "SyntaxError",
101 | "TypeError",
102 | "Uint8Array",
103 | "Uint8ClampedArray",
104 | "Uint16Array",
105 | "Uint32Array",
106 | "URIError",
107 | "WeakMap",
108 | "WeakSet",
109 |
110 | // fundamental objects (ch 19)
111 | "Object",
112 | "Function",
113 | "Boolean",
114 | "Symbol",
115 | "Error",
116 |
117 | // numbers and dates (ch 20)
118 | "Number",
119 | "Math",
120 | "Date",
121 |
122 | // text processing (ch 21)
123 | "String",
124 | "RegExp",
125 |
126 | // indexed collections (ch 22)
127 | "Array",
128 |
129 | // keyed collections (ch 23)
130 | "Map",
131 | "Set",
132 | "WeakMap",
133 | "WeakSet",
134 |
135 | // structured data (ch 24)
136 | "ArrayBuffer",
137 | "SharedArrayBuffer",
138 | "DataView",
139 | "Atomics",
140 | "JSON",
141 |
142 | // control abstraction objects (ch 25)
143 | "Generator",
144 | "AsyncGenerator",
145 | "Promise",
146 |
147 | // reflection (ch 26)
148 | "Reflect",
149 | "Proxy",
150 |
151 | // some curiously hard to find ones in the spec
152 | "async",
153 | "let",
154 | "static",
155 | "else",
156 | "document",
157 | "window",
158 | "navigator",
159 | "then",
160 | "set",
161 | "get",
162 | "of",
163 |
164 | // Object.keys(window) in chrome
165 | "postMessage",
166 | "blur",
167 | "focus",
168 | "close",
169 | "parent",
170 | "opener",
171 | "top",
172 | "length",
173 | "frames",
174 | "closed",
175 | "location",
176 | "self",
177 | "window",
178 | "document",
179 | "name",
180 | "customElements",
181 | "history",
182 | "locationbar",
183 | "menubar",
184 | "personalbar",
185 | "scrollbars",
186 | "statusbar",
187 | "toolbar",
188 | "status",
189 | "frameElement",
190 | "navigator",
191 | "origin",
192 | "external",
193 | "screen",
194 | "innerWidth",
195 | "innerHeight",
196 | "scrollX",
197 | "pageXOffset",
198 | "scrollY",
199 | "pageYOffset",
200 | "visualViewport",
201 | "screenX",
202 | "screenY",
203 | "outerWidth",
204 | "outerHeight",
205 | "devicePixelRatio",
206 | "clientInformation",
207 | "screenLeft",
208 | "screenTop",
209 | "defaultStatus",
210 | "defaultstatus",
211 | "styleMedia",
212 | "onanimationend",
213 | "onanimationiteration",
214 | "onanimationstart",
215 | "onsearch",
216 | "ontransitionend",
217 | "onwebkitanimationend",
218 | "onwebkitanimationiteration",
219 | "onwebkitanimationstart",
220 | "onwebkittransitionend",
221 | "isSecureContext",
222 | "onabort",
223 | "onblur",
224 | "oncancel",
225 | "oncanplay",
226 | "oncanplaythrough",
227 | "onchange",
228 | "onclick",
229 | "onclose",
230 | "oncontextmenu",
231 | "oncuechange",
232 | "ondblclick",
233 | "ondrag",
234 | "ondragend",
235 | "ondragenter",
236 | "ondragleave",
237 | "ondragover",
238 | "ondragstart",
239 | "ondrop",
240 | "ondurationchange",
241 | "onemptied",
242 | "onended",
243 | "onerror",
244 | "onfocus",
245 | "oninput",
246 | "oninvalid",
247 | "onkeydown",
248 | "onkeypress",
249 | "onkeyup",
250 | "onload",
251 | "onloadeddata",
252 | "onloadedmetadata",
253 | "onloadstart",
254 | "onmousedown",
255 | "onmouseenter",
256 | "onmouseleave",
257 | "onmousemove",
258 | "onmouseout",
259 | "onmouseover",
260 | "onmouseup",
261 | "onmousewheel",
262 | "onpause",
263 | "onplay",
264 | "onplaying",
265 | "onprogress",
266 | "onratechange",
267 | "onreset",
268 | "onresize",
269 | "onscroll",
270 | "onseeked",
271 | "onseeking",
272 | "onselect",
273 | "onstalled",
274 | "onsubmit",
275 | "onsuspend",
276 | "ontimeupdate",
277 | "ontoggle",
278 | "onvolumechange",
279 | "onwaiting",
280 | "onwheel",
281 | "onauxclick",
282 | "ongotpointercapture",
283 | "onlostpointercapture",
284 | "onpointerdown",
285 | "onpointermove",
286 | "onpointerup",
287 | "onpointercancel",
288 | "onpointerover",
289 | "onpointerout",
290 | "onpointerenter",
291 | "onpointerleave",
292 | "onselectstart",
293 | "onselectionchange",
294 | "onafterprint",
295 | "onbeforeprint",
296 | "onbeforeunload",
297 | "onhashchange",
298 | "onlanguagechange",
299 | "onmessage",
300 | "onmessageerror",
301 | "onoffline",
302 | "ononline",
303 | "onpagehide",
304 | "onpageshow",
305 | "onpopstate",
306 | "onrejectionhandled",
307 | "onstorage",
308 | "onunhandledrejection",
309 | "onunload",
310 | "performance",
311 | "stop",
312 | "open",
313 | "alert",
314 | "confirm",
315 | "prompt",
316 | "print",
317 | "queueMicrotask",
318 | "requestAnimationFrame",
319 | "cancelAnimationFrame",
320 | "captureEvents",
321 | "releaseEvents",
322 | "requestIdleCallback",
323 | "cancelIdleCallback",
324 | "getComputedStyle",
325 | "matchMedia",
326 | "moveTo",
327 | "moveBy",
328 | "resizeTo",
329 | "resizeBy",
330 | "getSelection",
331 | "find",
332 | "webkitRequestAnimationFrame",
333 | "webkitCancelAnimationFrame",
334 | "fetch",
335 | "btoa",
336 | "atob",
337 | "setTimeout",
338 | "clearTimeout",
339 | "setInterval",
340 | "clearInterval",
341 | "createImageBitmap",
342 | "scroll",
343 | "scrollTo",
344 | "scrollBy",
345 | "onappinstalled",
346 | "onbeforeinstallprompt",
347 | "crypto",
348 | "ondevicemotion",
349 | "ondeviceorientation",
350 | "ondeviceorientationabsolute",
351 | "indexedDB",
352 | "webkitStorageInfo",
353 | "sessionStorage",
354 | "localStorage",
355 | "chrome",
356 | "speechSynthesis",
357 | "webkitRequestFileSystem",
358 | "webkitResolveLocalFileSystemURL",
359 | "openDatabase"
360 | ]
361 | };
362 |
--------------------------------------------------------------------------------
/assets/cmds/python.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This is a list of Python3+ keywords and built in functions
3 | *
4 | * See the README in this directory for more on how this list is assembled.
5 | *
6 | * There are duplicates, and that's okay. But if you are removing items, be sure to look for multiple entries!
7 | */
8 | export default {
9 | name: "Python",
10 | commonCmds: [
11 | "def",
12 | "len",
13 | "lambda",
14 | "tuple",
15 | "elif",
16 | "if",
17 | "or",
18 | "return",
19 | "finally",
20 | "class",
21 | "and",
22 | "del",
23 | "not",
24 | "while",
25 | "break",
26 | "with",
27 | "True",
28 | "False",
29 | "max",
30 | "round",
31 | "globals"
32 | ],
33 | cmds: [
34 | // keywords
35 | "False",
36 | "class",
37 | "finally",
38 | "is",
39 | "return",
40 | "None",
41 | "continue",
42 | "for",
43 | "lambda",
44 | "try",
45 | "True",
46 | "def",
47 | "from",
48 | "nonlocal",
49 | "while",
50 | "and",
51 | "del",
52 | "global",
53 | "not",
54 | "with",
55 | "as",
56 | "elif",
57 | "if",
58 | "or",
59 | "yield",
60 | "assert",
61 | "else",
62 | "import",
63 | "pass",
64 | "break",
65 | "except",
66 | "in",
67 | "raise",
68 | // funtions
69 | "abs()",
70 | "delattr()",
71 | "hash()",
72 | "memoryview()",
73 | "set()",
74 | "all()",
75 | "dict()",
76 | "help()",
77 | "min()",
78 | "setattr()",
79 | "any()",
80 | "dir()",
81 | "hex()",
82 | "next()",
83 | "slice()",
84 | "ascii()",
85 | "divmod()",
86 | "id()",
87 | "object()",
88 | "sorted()",
89 | "bin()",
90 | "enumerate()",
91 | "input()",
92 | "oct()",
93 | "staticmethod()",
94 | "bool()",
95 | "eval()",
96 | "int()",
97 | "open()",
98 | "str()",
99 | "breakpoint()",
100 | "exec()",
101 | "isinstance()",
102 | "ord()",
103 | "sum()",
104 | "bytearray()",
105 | "filter()",
106 | "issubclass()",
107 | "pow()",
108 | "super()",
109 | "bytes()",
110 | "float()",
111 | "iter()",
112 | "print()",
113 | "tuple()",
114 | "callable()",
115 | "format()",
116 | "len()",
117 | "property()",
118 | "type()",
119 | "chr()",
120 | "frozenset()",
121 | "list()",
122 | "range()",
123 | "vars()",
124 | "classmethod()",
125 | "getattr()",
126 | "locals()",
127 | "repr()",
128 | "zip()",
129 | "compile()",
130 | "globals()",
131 | "map()",
132 | "reversed()",
133 | "__import__()",
134 | "complex()",
135 | "hasattr()",
136 | "max()",
137 | "round()",
138 | // functions without parens
139 | "abs",
140 | "delattr",
141 | "hash",
142 | "memoryview",
143 | "set",
144 | "all",
145 | "dict",
146 | "help",
147 | "min",
148 | "setattr",
149 | "any",
150 | "dir",
151 | "hex",
152 | "next",
153 | "slice",
154 | "ascii",
155 | "divmod",
156 | "id",
157 | "object",
158 | "sorted",
159 | "bin",
160 | "enumerate",
161 | "input",
162 | "oct",
163 | "staticmethod",
164 | "bool",
165 | "eval",
166 | "int",
167 | "open",
168 | "str",
169 | "breakpoint",
170 | "exec",
171 | "isinstance",
172 | "ord",
173 | "sum",
174 | "bytearray",
175 | "filter",
176 | "issubclass",
177 | "pow",
178 | "super",
179 | "bytes",
180 | "float",
181 | "iter",
182 | "print",
183 | "tuple",
184 | "callable",
185 | "format",
186 | "len",
187 | "property",
188 | "type",
189 | "chr",
190 | "frozenset",
191 | "list",
192 | "range",
193 | "vars",
194 | "classmethod",
195 | "getattr",
196 | "locals",
197 | "repr",
198 | "zip",
199 | "compile",
200 | "globals",
201 | "map",
202 | "reversed",
203 | "__import__",
204 | "complex",
205 | "hasattr",
206 | "max",
207 | "round"
208 | ]
209 | };
210 |
--------------------------------------------------------------------------------
/assets/fonts/_overpass.scss:
--------------------------------------------------------------------------------
1 | /* Overpass helper mixins */
2 |
3 | //-- This is a map of the current font weights, keyed on their file names
4 | $supported-weights: (
5 | thin: 200,
6 | extralight: 300,
7 | light: 400,
8 | regular: 500,
9 | semibold: 600,
10 | bold: 700,
11 | extrabold: 800,
12 | heavy: 900
13 | );
14 |
15 | //-- This is a list of the currently supported font styles
16 | $supported-styles: (normal italic);
17 |
18 | //-- This simple mixin adds the overpass basic styles to a typography element
19 | @mixin _overpass-styles {
20 | font-family: 'overpass';
21 | -webkit-font-smoothing: antialiased;
22 | -moz-osx-font-smoothing: grayscale;
23 | }
24 |
25 | //-- This mixins will dynamically print the font-face declarations based on your levels of support
26 | // * Weight and style lists can be limited to those your wish to incorporate in your styles,
27 | // if left with defaults, it will add support for all overpass variations
28 | // * The weight map can be customized if you wish to set your own values
29 | // Example: To use `font-weight: bolder` instead of `font-weight: 800`, we create a custom map
30 | // that maps the key extrabold to the value bolder -> `extrabold: bolder`
31 | // * The path to dist represents the location of the overpass font files relative to your architecture
32 | @mixin print-overpass-font-face(
33 | $weights: map-keys($supported-weights),
34 | $styles: $supported-styles,
35 | $weight-map: $supported-weights,
36 | $path_to_dist: './'
37 | ) {
38 | @each $weight in $weights {
39 | @each $style in $styles {
40 | @font-face {
41 | font-family: 'overpass';
42 | src: url("#{$path_to_dist}overpass-#{$weight}#{if($style != normal, #{-$style}, "")}.eot?");
43 | src: url("#{$path_to_dist}overpass-#{$weight}#{if($style != normal, #{-$style}, "")}.eot?#iefix") format("embedded-opentype"),
44 | url("#{$path_to_dist}overpass-#{$weight}#{if($style != normal, #{-$style}, "")}.woff?") format("woff"),
45 | url("#{$path_to_dist}overpass-#{$weight}#{if($style != normal, #{-$style}, "")}.ttf?") format("truetype");
46 | font-weight: map-get($weight-map, $weight);
47 | font-style: $style;
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/assets/fonts/example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 |
9 | Overpass Overpass
10 | Overpass Overpass
11 | Overpass Overpass
12 | Overpass Overpass
13 | Overpass Overpass
14 | Overpass Overpass
15 | Overpass Overpass
16 | Overpass Overpass
17 |
18 |
19 |
--------------------------------------------------------------------------------
/assets/fonts/fonts.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: "overpass-mono";
3 | src: url("overpass-mono-light.eot");
4 | src: url("overpass-mono-light.eot?#iefix") format("embedded-opentype"),
5 | url("overpass-mono-light.woff2") format("woff2"),
6 | url("overpass-mono-light.woff") format("woff"),
7 | url("overpass-mono-light.ttf") format("truetype");
8 | font-weight: 300;
9 | font-style: normal;
10 | }
11 |
12 | @font-face {
13 | font-family: "overpass-mono";
14 | src: url("overpass-mono-regular.eot");
15 | src: url("overpass-mono-regular.eot?#iefix") format("embedded-opentype"),
16 | url("overpass-mono-regular.woff2") format("woff2"),
17 | url("overpass-mono-regular.woff") format("woff"),
18 | url("overpass-mono-regular.ttf") format("truetype");
19 | font-weight: 400;
20 | font-style: normal;
21 | }
22 |
23 | @font-face {
24 | font-family: "overpass-mono";
25 | src: url("overpass-mono-semibold.eot");
26 | src: url("overpass-mono-semibold.eot?#iefix") format("embedded-opentype"),
27 | url("overpass-mono-semibold.woff2") format("woff2"),
28 | url("overpass-mono-semibold.woff") format("woff"),
29 | url("overpass-mono-semibold.ttf") format("truetype");
30 | font-weight: 500;
31 | font-style: normal;
32 | }
33 |
34 | @font-face {
35 | font-family: "overpass-mono";
36 | src: url("overpass-mono-bold.eot");
37 | src: url("overpass-mono-bold.eot?#iefix") format("embedded-opentype"),
38 | url("overpass-mono-bold.woff2") format("woff2"),
39 | url("overpass-mono-bold.woff") format("woff"),
40 | url("overpass-mono-bold.ttf") format("truetype");
41 | font-weight: 600;
42 | font-style: normal;
43 | }
44 |
45 | @font-face {
46 | font-family: "overpass";
47 | src: url("overpass-thin.eot"); /* IE9 Compat Modes */
48 | src: url("overpass-thin.eot?#iefix") format("embedded-opentype"),
49 | /* IE6-IE8 */ url("overpass-thin.woff2") format("woff2"),
50 | /* Super Modern Browsers */ url("overpass-thin.woff") format("woff"),
51 | /* Pretty Modern Browsers */ url("overpass-thin.ttf") format("truetype"); /* Safari, Android, iOS */
52 | font-weight: 200;
53 | font-style: normal;
54 | }
55 |
56 | @font-face {
57 | font-family: "overpass";
58 | src: url("overpass-thin-italic.eot");
59 | src: url("overpass-thin-italic.eot?#iefix") format("embedded-opentype"),
60 | url("overpass-thin-italic.woff2") format("woff2"),
61 | url("overpass-thin-italic.woff") format("woff"),
62 | url("overpass-thin-italic.ttf") format("truetype");
63 | font-weight: 200;
64 | font-style: italic;
65 | }
66 |
67 | @font-face {
68 | font-family: "overpass";
69 | src: url("overpass-extralight.eot");
70 | src: url("overpass-extralight.eot?#iefix") format("embedded-opentype"),
71 | url("overpass-extralight.woff2") format("woff2"),
72 | url("overpass-extralight.woff") format("woff"),
73 | url("overpass-extralight.ttf") format("truetype");
74 | font-weight: 300;
75 | font-style: normal;
76 | }
77 |
78 | @font-face {
79 | font-family: "overpass";
80 | src: url("overpass-extralight-italic.eot");
81 | src: url("overpass-extralight-italic.eot?#iefix") format("embedded-opentype"),
82 | url("overpass-extralight-italic.woff2") format("woff2"),
83 | url("overpass-extralight-italic.woff") format("woff"),
84 | url("overpass-extralight-italic.ttf") format("truetype");
85 | font-weight: 300;
86 | font-style: italic;
87 | }
88 |
89 | @font-face {
90 | font-family: "overpass";
91 | src: url("overpass-light.eot");
92 | src: url("overpass-light.eot?#iefix") format("embedded-opentype"),
93 | url("overpass-light.woff2") format("woff2"),
94 | url("overpass-light.woff") format("woff"),
95 | url("overpass-light.ttf") format("truetype");
96 | font-weight: 400;
97 | font-style: normal;
98 | }
99 |
100 | @font-face {
101 | font-family: "overpass";
102 | src: url("overpass-light-italic.eot");
103 | src: url("overpass-light-italic.eot?#iefix") format("embedded-opentype"),
104 | url("overpass-light-italic.woff2") format("woff2"),
105 | url("overpass-light-italic.woff") format("woff"),
106 | url("overpass-light-italic.ttf") format("truetype");
107 | font-weight: 400;
108 | font-style: italic;
109 | }
110 |
111 | @font-face {
112 | font-family: "overpass";
113 | src: url("overpass-regular.eot");
114 | src: url("overpass-regular.eot?#iefix") format("embedded-opentype"),
115 | url("overpass-regular.woff2") format("woff2"),
116 | url("overpass-regular.woff") format("woff"),
117 | url("overpass-regular.ttf") format("truetype");
118 | font-weight: 500;
119 | font-style: normal;
120 | }
121 |
122 | @font-face {
123 | font-family: "overpass";
124 | src: url("overpass-italic.eot");
125 | src: url("overpass-italic.eot?#iefix") format("embedded-opentype"),
126 | url("overpass-italic.woff2") format("woff2"),
127 | url("overpass-italic.woff") format("woff"),
128 | url("overpass-italic.ttf") format("truetype");
129 | font-weight: 500;
130 | font-style: italic;
131 | }
132 |
133 | @font-face {
134 | font-family: "overpass";
135 | src: url("overpass-semibold.eot");
136 | src: url("overpass-semibold.eot?#iefix") format("embedded-opentype"),
137 | url("overpass-semibold.woff2") format("woff2"),
138 | url("overpass-semibold.woff") format("woff"),
139 | url("overpass-semibold.ttf") format("truetype");
140 | font-weight: 600;
141 | font-style: normal;
142 | }
143 |
144 | @font-face {
145 | font-family: "overpass";
146 | src: url("overpass-semibold-italic.eot");
147 | src: url("overpass-semibold-italic.eot?#iefix") format("embedded-opentype"),
148 | url("overpass-semibold-italic.woff2") format("woff2"),
149 | url("overpass-semibold-italic.woff") format("woff"),
150 | url("overpass-semibold-italic.ttf") format("truetype");
151 | font-weight: 600;
152 | font-style: italic;
153 | }
154 |
155 | @font-face {
156 | font-family: "overpass";
157 | src: url("overpass-bold.eot");
158 | src: url("overpass-bold.eot?#iefix") format("embedded-opentype"),
159 | url("overpass-bold.woff2") format("woff2"),
160 | url("overpass-bold.woff") format("woff"),
161 | url("overpass-bold.ttf") format("truetype");
162 | font-weight: 700;
163 | font-style: normal;
164 | }
165 |
166 | @font-face {
167 | font-family: "overpass";
168 | src: url("overpass-bold-italic.eot");
169 | src: url("overpass-bold-italic.eot?#iefix") format("embedded-opentype"),
170 | url("overpass-bold-italic.woff2") format("woff2"),
171 | url("overpass-bold-italic.woff") format("woff"),
172 | url("overpass-bold-italic.ttf") format("truetype");
173 | font-weight: 700;
174 | font-style: italic;
175 | }
176 |
177 | @font-face {
178 | font-family: "overpass";
179 | src: url("overpass-extrabold.eot");
180 | src: url("overpass-extrabold.eot?#iefix") format("embedded-opentype"),
181 | url("overpass-extrabold.woff2") format("woff2"),
182 | url("overpass-extrabold.woff") format("woff"),
183 | url("overpass-extrabold.ttf") format("truetype");
184 | font-weight: 800;
185 | font-style: normal;
186 | }
187 |
188 | @font-face {
189 | font-family: "overpass";
190 | src: url("overpass-extrabold-italic.eot");
191 | src: url("overpass-extrabold-italic.eot?#iefix") format("embedded-opentype"),
192 | url("overpass-extrabold-italic.woff2") format("woff2"),
193 | url("overpass-extrabold-italic.woff") format("woff"),
194 | url("overpass-extrabold-italic.ttf") format("truetype");
195 | font-weight: 800;
196 | font-style: italic;
197 | }
198 |
199 | @font-face {
200 | font-family: "overpass";
201 | src: url("overpass-heavy.eot");
202 | src: url("overpass-heavy.eot?#iefix") format("embedded-opentype"),
203 | url("overpass-heavy.woff2") format("woff2"),
204 | url("overpass-heavy.woff") format("woff"),
205 | url("overpass-heavy.ttf") format("truetype");
206 | font-weight: 900;
207 | font-style: normal;
208 | }
209 |
210 | @font-face {
211 | font-family: "overpass";
212 | src: url("overpass-heavy-italic.eot");
213 | src: url("overpass-heavy-italic.eot?#iefix") format("embedded-opentype"),
214 | url("overpass-heavy-italic.woff2") format("woff2"),
215 | url("overpass-heavy-italic.woff") format("woff"),
216 | url("overpass-heavy-italic.ttf") format("truetype");
217 | font-weight: 900;
218 | font-style: italic;
219 | }
220 |
--------------------------------------------------------------------------------
/assets/fonts/overpass-bold-italic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-bold-italic.eot
--------------------------------------------------------------------------------
/assets/fonts/overpass-bold-italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-bold-italic.ttf
--------------------------------------------------------------------------------
/assets/fonts/overpass-bold-italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-bold-italic.woff
--------------------------------------------------------------------------------
/assets/fonts/overpass-bold-italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-bold-italic.woff2
--------------------------------------------------------------------------------
/assets/fonts/overpass-bold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-bold.eot
--------------------------------------------------------------------------------
/assets/fonts/overpass-bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-bold.ttf
--------------------------------------------------------------------------------
/assets/fonts/overpass-bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-bold.woff
--------------------------------------------------------------------------------
/assets/fonts/overpass-bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-bold.woff2
--------------------------------------------------------------------------------
/assets/fonts/overpass-extrabold-italic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extrabold-italic.eot
--------------------------------------------------------------------------------
/assets/fonts/overpass-extrabold-italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extrabold-italic.ttf
--------------------------------------------------------------------------------
/assets/fonts/overpass-extrabold-italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extrabold-italic.woff
--------------------------------------------------------------------------------
/assets/fonts/overpass-extrabold-italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extrabold-italic.woff2
--------------------------------------------------------------------------------
/assets/fonts/overpass-extrabold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extrabold.eot
--------------------------------------------------------------------------------
/assets/fonts/overpass-extrabold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extrabold.ttf
--------------------------------------------------------------------------------
/assets/fonts/overpass-extrabold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extrabold.woff
--------------------------------------------------------------------------------
/assets/fonts/overpass-extrabold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extrabold.woff2
--------------------------------------------------------------------------------
/assets/fonts/overpass-extralight-italic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extralight-italic.eot
--------------------------------------------------------------------------------
/assets/fonts/overpass-extralight-italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extralight-italic.ttf
--------------------------------------------------------------------------------
/assets/fonts/overpass-extralight-italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extralight-italic.woff
--------------------------------------------------------------------------------
/assets/fonts/overpass-extralight-italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extralight-italic.woff2
--------------------------------------------------------------------------------
/assets/fonts/overpass-extralight.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extralight.eot
--------------------------------------------------------------------------------
/assets/fonts/overpass-extralight.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extralight.ttf
--------------------------------------------------------------------------------
/assets/fonts/overpass-extralight.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extralight.woff
--------------------------------------------------------------------------------
/assets/fonts/overpass-extralight.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extralight.woff2
--------------------------------------------------------------------------------
/assets/fonts/overpass-heavy-italic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-heavy-italic.eot
--------------------------------------------------------------------------------
/assets/fonts/overpass-heavy-italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-heavy-italic.ttf
--------------------------------------------------------------------------------
/assets/fonts/overpass-heavy-italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-heavy-italic.woff
--------------------------------------------------------------------------------
/assets/fonts/overpass-heavy-italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-heavy-italic.woff2
--------------------------------------------------------------------------------
/assets/fonts/overpass-heavy.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-heavy.eot
--------------------------------------------------------------------------------
/assets/fonts/overpass-heavy.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-heavy.ttf
--------------------------------------------------------------------------------
/assets/fonts/overpass-heavy.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-heavy.woff
--------------------------------------------------------------------------------
/assets/fonts/overpass-heavy.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-heavy.woff2
--------------------------------------------------------------------------------
/assets/fonts/overpass-italic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-italic.eot
--------------------------------------------------------------------------------
/assets/fonts/overpass-italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-italic.ttf
--------------------------------------------------------------------------------
/assets/fonts/overpass-italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-italic.woff
--------------------------------------------------------------------------------
/assets/fonts/overpass-italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-italic.woff2
--------------------------------------------------------------------------------
/assets/fonts/overpass-light-italic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-light-italic.eot
--------------------------------------------------------------------------------
/assets/fonts/overpass-light-italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-light-italic.ttf
--------------------------------------------------------------------------------
/assets/fonts/overpass-light-italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-light-italic.woff
--------------------------------------------------------------------------------
/assets/fonts/overpass-light-italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-light-italic.woff2
--------------------------------------------------------------------------------
/assets/fonts/overpass-light.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-light.eot
--------------------------------------------------------------------------------
/assets/fonts/overpass-light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-light.ttf
--------------------------------------------------------------------------------
/assets/fonts/overpass-light.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-light.woff
--------------------------------------------------------------------------------
/assets/fonts/overpass-light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-light.woff2
--------------------------------------------------------------------------------
/assets/fonts/overpass-mono-bold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-bold.eot
--------------------------------------------------------------------------------
/assets/fonts/overpass-mono-bold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-bold.otf
--------------------------------------------------------------------------------
/assets/fonts/overpass-mono-bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-bold.ttf
--------------------------------------------------------------------------------
/assets/fonts/overpass-mono-bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-bold.woff
--------------------------------------------------------------------------------
/assets/fonts/overpass-mono-bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-bold.woff2
--------------------------------------------------------------------------------
/assets/fonts/overpass-mono-light.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-light.eot
--------------------------------------------------------------------------------
/assets/fonts/overpass-mono-light.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-light.otf
--------------------------------------------------------------------------------
/assets/fonts/overpass-mono-light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-light.ttf
--------------------------------------------------------------------------------
/assets/fonts/overpass-mono-light.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-light.woff
--------------------------------------------------------------------------------
/assets/fonts/overpass-mono-light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-light.woff2
--------------------------------------------------------------------------------
/assets/fonts/overpass-mono-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-regular.eot
--------------------------------------------------------------------------------
/assets/fonts/overpass-mono-regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-regular.otf
--------------------------------------------------------------------------------
/assets/fonts/overpass-mono-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-regular.ttf
--------------------------------------------------------------------------------
/assets/fonts/overpass-mono-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-regular.woff
--------------------------------------------------------------------------------
/assets/fonts/overpass-mono-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-regular.woff2
--------------------------------------------------------------------------------
/assets/fonts/overpass-mono-semibold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-semibold.eot
--------------------------------------------------------------------------------
/assets/fonts/overpass-mono-semibold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-semibold.otf
--------------------------------------------------------------------------------
/assets/fonts/overpass-mono-semibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-semibold.ttf
--------------------------------------------------------------------------------
/assets/fonts/overpass-mono-semibold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-semibold.woff
--------------------------------------------------------------------------------
/assets/fonts/overpass-mono-semibold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-semibold.woff2
--------------------------------------------------------------------------------
/assets/fonts/overpass-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-regular.eot
--------------------------------------------------------------------------------
/assets/fonts/overpass-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-regular.ttf
--------------------------------------------------------------------------------
/assets/fonts/overpass-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-regular.woff
--------------------------------------------------------------------------------
/assets/fonts/overpass-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-regular.woff2
--------------------------------------------------------------------------------
/assets/fonts/overpass-semibold-italic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-semibold-italic.eot
--------------------------------------------------------------------------------
/assets/fonts/overpass-semibold-italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-semibold-italic.ttf
--------------------------------------------------------------------------------
/assets/fonts/overpass-semibold-italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-semibold-italic.woff
--------------------------------------------------------------------------------
/assets/fonts/overpass-semibold-italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-semibold-italic.woff2
--------------------------------------------------------------------------------
/assets/fonts/overpass-semibold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-semibold.eot
--------------------------------------------------------------------------------
/assets/fonts/overpass-semibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-semibold.ttf
--------------------------------------------------------------------------------
/assets/fonts/overpass-semibold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-semibold.woff
--------------------------------------------------------------------------------
/assets/fonts/overpass-semibold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-semibold.woff2
--------------------------------------------------------------------------------
/assets/fonts/overpass-thin-italic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-thin-italic.eot
--------------------------------------------------------------------------------
/assets/fonts/overpass-thin-italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-thin-italic.ttf
--------------------------------------------------------------------------------
/assets/fonts/overpass-thin-italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-thin-italic.woff
--------------------------------------------------------------------------------
/assets/fonts/overpass-thin-italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-thin-italic.woff2
--------------------------------------------------------------------------------
/assets/fonts/overpass-thin.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-thin.eot
--------------------------------------------------------------------------------
/assets/fonts/overpass-thin.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-thin.ttf
--------------------------------------------------------------------------------
/assets/fonts/overpass-thin.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-thin.woff
--------------------------------------------------------------------------------
/assets/fonts/overpass-thin.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-thin.woff2
--------------------------------------------------------------------------------
/assets/fonts/overpass.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'overpass';
3 | src: url('overpass-thin.eot'); /* IE9 Compat Modes */
4 | src: url('overpass-thin.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
5 | url('overpass-thin.woff2') format('woff2'), /* Super Modern Browsers */
6 | url('overpass-thin.woff') format('woff'), /* Pretty Modern Browsers */
7 | url('overpass-thin.ttf') format('truetype'); /* Safari, Android, iOS */
8 | font-weight: 200;
9 | font-style: normal;
10 | }
11 |
12 | @font-face {
13 | font-family: 'overpass';
14 | src: url('overpass-thin-italic.eot');
15 | src: url('overpass-thin-italic.eot?#iefix') format('embedded-opentype'),
16 | url('overpass-thin-italic.woff2') format('woff2'),
17 | url('overpass-thin-italic.woff') format('woff'),
18 | url('overpass-thin-italic.ttf') format('truetype');
19 | font-weight: 200;
20 | font-style: italic;
21 | }
22 |
23 |
24 | @font-face {
25 | font-family: 'overpass';
26 | src: url('overpass-extralight.eot');
27 | src: url('overpass-extralight.eot?#iefix') format('embedded-opentype'),
28 | url('overpass-extralight.woff2') format('woff2'),
29 | url('overpass-extralight.woff') format('woff'),
30 | url('overpass-extralight.ttf') format('truetype');
31 | font-weight: 300;
32 | font-style: normal;
33 | }
34 |
35 | @font-face {
36 | font-family: 'overpass';
37 | src: url('overpass-extralight-italic.eot');
38 | src: url('overpass-extralight-italic.eot?#iefix') format('embedded-opentype'),
39 | url('overpass-extralight-italic.woff2') format('woff2'),
40 | url('overpass-extralight-italic.woff') format('woff'),
41 | url('overpass-extralight-italic.ttf') format('truetype');
42 | font-weight: 300;
43 | font-style: italic;
44 | }
45 |
46 |
47 |
48 | @font-face {
49 | font-family: 'overpass';
50 | src: url('overpass-light.eot');
51 | src: url('overpass-light.eot?#iefix') format('embedded-opentype'),
52 | url('overpass-light.woff2') format('woff2'),
53 | url('overpass-light.woff') format('woff'),
54 | url('overpass-light.ttf') format('truetype');
55 | font-weight: 400;
56 | font-style: normal;
57 | }
58 |
59 | @font-face {
60 | font-family: 'overpass';
61 | src: url('overpass-light-italic.eot');
62 | src: url('overpass-light-italic.eot?#iefix') format('embedded-opentype'),
63 | url('overpass-light-italic.woff2') format('woff2'),
64 | url('overpass-light-italic.woff') format('woff'),
65 | url('overpass-light-italic.ttf') format('truetype');
66 | font-weight: 400;
67 | font-style: italic;
68 | }
69 |
70 |
71 |
72 | @font-face {
73 | font-family: 'overpass';
74 | src: url('overpass-regular.eot');
75 | src: url('overpass-regular.eot?#iefix') format('embedded-opentype'),
76 | url('overpass-regular.woff2') format('woff2'),
77 | url('overpass-regular.woff') format('woff'),
78 | url('overpass-regular.ttf') format('truetype');
79 | font-weight: 500;
80 | font-style: normal;
81 | }
82 |
83 | @font-face {
84 | font-family: 'overpass';
85 | src: url('overpass-italic.eot');
86 | src: url('overpass-italic.eot?#iefix') format('embedded-opentype'),
87 | url('overpass-italic.woff2') format('woff2'),
88 | url('overpass-italic.woff') format('woff'),
89 | url('overpass-italic.ttf') format('truetype');
90 | font-weight: 500;
91 | font-style: italic;
92 | }
93 |
94 |
95 |
96 |
97 | @font-face {
98 | font-family: 'overpass';
99 | src: url('overpass-semibold.eot');
100 | src: url('overpass-semibold.eot?#iefix') format('embedded-opentype'),
101 | url('overpass-semibold.woff2') format('woff2'),
102 | url('overpass-semibold.woff') format('woff'),
103 | url('overpass-semibold.ttf') format('truetype');
104 | font-weight: 600;
105 | font-style: normal;
106 | }
107 |
108 | @font-face {
109 | font-family: 'overpass';
110 | src: url('overpass-semibold-italic.eot');
111 | src: url('overpass-semibold-italic.eot?#iefix') format('embedded-opentype'),
112 | url('overpass-semibold-italic.woff2') format('woff2'),
113 | url('overpass-semibold-italic.woff') format('woff'),
114 | url('overpass-semibold-italic.ttf') format('truetype');
115 | font-weight: 600;
116 | font-style: italic;
117 | }
118 |
119 |
120 |
121 |
122 | @font-face {
123 | font-family: 'overpass';
124 | src: url('overpass-bold.eot');
125 | src: url('overpass-bold.eot?#iefix') format('embedded-opentype'),
126 | url('overpass-bold.woff2') format('woff2'),
127 | url('overpass-bold.woff') format('woff'),
128 | url('overpass-bold.ttf') format('truetype');
129 | font-weight: 700;
130 | font-style: normal;
131 | }
132 |
133 | @font-face {
134 | font-family: 'overpass';
135 | src: url('overpass-bold-italic.eot');
136 | src: url('overpass-bold-italic.eot?#iefix') format('embedded-opentype'),
137 | url('overpass-bold-italic.woff2') format('woff2'),
138 | url('overpass-bold-italic.woff') format('woff'),
139 | url('overpass-bold-italic.ttf') format('truetype');
140 | font-weight: 700;
141 | font-style: italic;
142 | }
143 |
144 |
145 |
146 | @font-face {
147 | font-family: 'overpass';
148 | src: url('overpass-extrabold.eot');
149 | src: url('overpass-extrabold.eot?#iefix') format('embedded-opentype'),
150 | url('overpass-extrabold.woff2') format('woff2'),
151 | url('overpass-extrabold.woff') format('woff'),
152 | url('overpass-extrabold.ttf') format('truetype');
153 | font-weight: 800;
154 | font-style: normal;
155 | }
156 |
157 | @font-face {
158 | font-family: 'overpass';
159 | src: url('overpass-extrabold-italic.eot');
160 | src: url('overpass-extrabold-italic.eot?#iefix') format('embedded-opentype'),
161 | url('overpass-extrabold-italic.woff2') format('woff2'),
162 | url('overpass-extrabold-italic.woff') format('woff'),
163 | url('overpass-extrabold-italic.ttf') format('truetype');
164 | font-weight: 800;
165 | font-style: italic;
166 | }
167 |
168 |
169 | @font-face {
170 | font-family: 'overpass';
171 | src: url('overpass-heavy.eot');
172 | src: url('overpass-heavy.eot?#iefix') format('embedded-opentype'),
173 | url('overpass-heavy.woff2') format('woff2'),
174 | url('overpass-heavy.woff') format('woff'),
175 | url('overpass-heavy.ttf') format('truetype');
176 | font-weight: 900;
177 | font-style: normal;
178 | }
179 |
180 | @font-face {
181 | font-family: 'overpass';
182 | src: url('overpass-heavy-italic.eot');
183 | src: url('overpass-heavy-italic.eot?#iefix') format('embedded-opentype'),
184 | url('overpass-heavy-italic.woff2') format('woff2'),
185 | url('overpass-heavy-italic.woff') format('woff'),
186 | url('overpass-heavy-italic.ttf') format('truetype');
187 | font-weight: 900;
188 | font-style: italic;
189 | }
190 |
--------------------------------------------------------------------------------
/assets/images/monitor_bezel_outline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/images/monitor_bezel_outline.png
--------------------------------------------------------------------------------
/assets/images/monitor_fire_inner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/images/monitor_fire_inner.png
--------------------------------------------------------------------------------
/assets/models/CLH_Computer.mtl.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/models/CLH_Computer.mtl.gz
--------------------------------------------------------------------------------
/assets/models/CLH_Computer.obj.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/models/CLH_Computer.obj.gz
--------------------------------------------------------------------------------
/assets/models/CLH_Shuttle.mtl.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/models/CLH_Shuttle.mtl.gz
--------------------------------------------------------------------------------
/assets/models/CLH_Shuttle.obj.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/models/CLH_Shuttle.obj.gz
--------------------------------------------------------------------------------
/assets/models/CLH_ep2_computer_high_poly.mtl.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/models/CLH_ep2_computer_high_poly.mtl.gz
--------------------------------------------------------------------------------
/assets/models/CLH_ep2_computer_high_poly.obj.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/models/CLH_ep2_computer_high_poly.obj.gz
--------------------------------------------------------------------------------
/assets/models/CLH_ep2_cyc_wall.mtl.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/models/CLH_ep2_cyc_wall.mtl.gz
--------------------------------------------------------------------------------
/assets/models/CLH_ep2_cyc_wall.obj.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/models/CLH_ep2_cyc_wall.obj.gz
--------------------------------------------------------------------------------
/assets/sfx/Waveshaper - 66 MHz.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/sfx/Waveshaper - 66 MHz.mp3
--------------------------------------------------------------------------------
/assets/sfx/boot.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/sfx/boot.mp3
--------------------------------------------------------------------------------
/assets/sfx/cmd-bad.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/sfx/cmd-bad.mp3
--------------------------------------------------------------------------------
/assets/sfx/cmd-gold.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/sfx/cmd-gold.mp3
--------------------------------------------------------------------------------
/assets/sfx/cmd-good.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/sfx/cmd-good.mp3
--------------------------------------------------------------------------------
/assets/sfx/keypress.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/sfx/keypress.mp3
--------------------------------------------------------------------------------
/assets/sfx/leaderboard.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/sfx/leaderboard.mp3
--------------------------------------------------------------------------------
/assets/sfx/menu-music.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/sfx/menu-music.mp3
--------------------------------------------------------------------------------
/assets/sfx/play.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/sfx/play.mp3
--------------------------------------------------------------------------------
/assets/sfx/timer-relaxed.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/sfx/timer-relaxed.mp3
--------------------------------------------------------------------------------
/assets/sfx/timer-urgent.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/sfx/timer-urgent.mp3
--------------------------------------------------------------------------------
/assets/styles.css:
--------------------------------------------------------------------------------
1 | @import "./fonts/fonts.css";
2 |
3 | html,
4 | body {
5 | height: 100%;
6 | padding: 0;
7 | margin: 0;
8 | overflow: hidden;
9 | }
10 |
11 | body {
12 | background: var(--clh-black);
13 | color: var(--clh-white);
14 |
15 | display: flex;
16 | align-items: center;
17 | justify-items: center;
18 | justify-content: center;
19 |
20 | font-family: overpass;
21 |
22 | --clh-white: #dbdbdb;
23 | --clh-black: #101114;
24 | --clh-purple: #200a5f;
25 | --clh-purple-light: #6b45a0;
26 | --clh-yellow: #f4be4a;
27 | --clh-yellow-light: #f7cf78;
28 | --clh-orange: #ef702b;
29 | --clh-orange-light: #f7a060;
30 | --clh-blue: #00a9e0;
31 | --clh-blue-light: #68cceb;
32 |
33 | --title-reveal-interval: 600ms;
34 | }
35 |
36 | a {
37 | color: var(--clh-blue);
38 | }
39 | a:active {
40 | color: var(--clh-white);
41 | }
42 | a:visited {
43 | color: var(--clh-purple-light);
44 | }
45 |
46 | textarea#cmd {
47 | background: rgba(0, 0, 0, 0.6);
48 | color: var(--clh-blue-light);
49 |
50 | border: 3px solid var(--clh-blue);
51 | outline: none;
52 | font-family: monospace;
53 | padding: 8px;
54 | font-size: 32px;
55 | text-transform: lowercase;
56 | text-align: left;
57 | position: absolute;
58 | bottom: 20px;
59 | right: 20px;
60 | width: 20vw;
61 | height: 30vw;
62 |
63 | opacity: 0;
64 | }
65 |
66 | #game-canvas {
67 | position: absolute;
68 | z-index: -1;
69 | top: 0;
70 | left: 0;
71 | right: 0;
72 | bottom: 0;
73 | }
74 |
75 | #console-canvas {
76 | position: absolute;
77 | width: calc(1920px / 7);
78 | bottom: 20px;
79 | right: 20px;
80 | border: 3px solid var(--clh-blue);
81 | user-select: none; /* disable ctrl+a to select all text */
82 |
83 | /* letter-spacing: -0.5em; */
84 |
85 | opacity: 0;
86 | }
87 |
88 | #test-pattern {
89 | display: none;
90 | }
91 |
92 | /* Title screen */
93 | #title-screen > * {
94 | opacity: 0;
95 | transition: opacity 100ms linear;
96 | width: 100%;
97 | }
98 | #title-screen > *.show {
99 | opacity: 1;
100 | transition: opacity 1800ms linear;
101 | }
102 |
103 | #title-screen {
104 | opacity: 0;
105 | transition: opacity 100ms linear;
106 | position: absolute;
107 | top: 20vh;
108 | left: 10vw;
109 | width: 40vw;
110 | height: 70vh;
111 | display: grid;
112 |
113 | font-size: 1.8rem;
114 | font-weight: bold;
115 | }
116 | #title-screen.show {
117 | opacity: 1;
118 | }
119 |
120 | #credits {
121 | font-size: 0.7em;
122 | color: var(--clh-white);
123 | display: none;
124 | }
125 |
126 | /* delay the reveal of each title screen element so the reveals cacade down the screen */
127 | #title-screen > *:nth-child(1) {
128 | transition-delay: calc(0 * var(--title-reveal-interval));
129 | }
130 | #title-screen > *:nth-child(2) {
131 | transition-delay: calc(1 * var(--title-reveal-interval));
132 | }
133 | #title-screen > *:nth-child(3) {
134 | transition-delay: calc(2 * var(--title-reveal-interval));
135 | }
136 | #title-screen > *:nth-child(4) {
137 | transition-delay: calc(3 * var(--title-reveal-interval));
138 | }
139 | #title-screen > *:nth-child(5) {
140 | transition-delay: calc(4 * var(--title-reveal-interval));
141 | }
142 | #title-screen > *:nth-child(6) {
143 | transition-delay: calc(5 * var(--title-reveal-interval));
144 | }
145 | #title-screen > *:nth-child(7) {
146 | transition-delay: calc(6 * var(--title-reveal-interval));
147 | }
148 | #title-screen > *:nth-child(8) {
149 | transition-delay: calc(6 * var(--title-reveal-interval));
150 | }
151 | #title-screen > *:nth-child(9) {
152 | transition-delay: calc(6 * var(--title-reveal-interval));
153 | }
154 | #title-screen > *:nth-child(10) {
155 | transition-delay: calc(6 * var(--title-reveal-interval));
156 | }
157 |
158 | /* individual elements of the title screen */
159 | #langs {
160 | text-align: center;
161 | font-size: 0.8em;
162 | color: var(--clh-yellow-light);
163 | font-family: overpass-mono;
164 | }
165 | #langs .sep {
166 | color: var(--clh-white);
167 | }
168 | #listen {
169 | font-size: 0.7em;
170 | color: var(--clh-white);
171 | }
172 |
173 | #intro {
174 | opacity: 0;
175 | transition: opacity linear 300ms;
176 | position: absolute;
177 | top: 0;
178 | right: 0;
179 | bottom: 0;
180 | left: 0;
181 | background: var(--clh-black);
182 | color: var(--clh-white);
183 | font-size: 3rem;
184 | font-weight: bold;
185 | display: flex;
186 | flex-direction: column;
187 | justify-content: center;
188 | align-items: center;
189 | user-select: none;
190 | padding-right: 30px;
191 | padding-left: 30px;
192 | }
193 | #intro .cursor {
194 | animation: blink 666ms steps(2, start) infinite;
195 | }
196 | #intro.show {
197 | opacity: 1;
198 | }
199 | #intro .logo {
200 | display: block;
201 | clear: both;
202 | max-width: 100%;
203 | width: 600px;
204 | margin-bottom: 10vh;
205 | }
206 |
207 | @keyframes blink {
208 | to {
209 | visibility: hidden;
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/assets/textures/clh_test_pattern.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/textures/clh_test_pattern.jpg
--------------------------------------------------------------------------------
/assets/textures/equirectangular.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/textures/equirectangular.png
--------------------------------------------------------------------------------
/assets/textures/wall.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/textures/wall.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
13 |
14 |
15 | The Command Line Heroes BASH!
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |

24 |
25 | Apologies, mobile is not yet supported, but PRs are welcome!
26 | DOWNLOADING
27 | █
28 |
29 |
30 |
56 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "clh-bash",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node start.js",
9 | "extract": "gunzip -k assets/models/*.gz",
10 | "compress": "gzip -fk assets/models/*.{mtl,obj}",
11 | "prettier": "node ./node_modules/.bin/prettier --write src/*.js assets/cmds/*.js",
12 | "unzip": "node src/unzip.js"
13 | },
14 | "keywords": [],
15 | "author": "",
16 | "license": "ISC",
17 | "devDependencies": {
18 | "browser-sync": "^2.26.14",
19 | "compression": "^1.7.3",
20 | "prettier": "^1.17.0"
21 | },
22 | "dependencies": {
23 | "@tweenjs/tween.js": "^17.2.0",
24 | "howler": "^2.1.1",
25 | "ismobilejs": "^0.5.1",
26 | "lodash": "^4.17.11",
27 | "three": "^0.126.1",
28 | "vue": "^2.5.22",
29 | "vue-autofocus-directive": "0.0.5"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Fire.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Mike Piecuch / https://github.com/mikepiecuch
3 | *
4 | * Based on research paper "Real-Time Fluid Dynamics for Games" by Jos Stam
5 | * http://www.dgp.toronto.edu/people/stam/reality/Research/pdf/GDC03.pdf
6 | *
7 | */
8 |
9 | THREE.Fire = function ( geometry, options ) {
10 |
11 | THREE.Mesh.call( this, geometry );
12 |
13 | this.type = 'Fire';
14 |
15 | this.clock = new THREE.Clock();
16 |
17 | options = options || {};
18 |
19 | var textureWidth = options.textureWidth || 512;
20 | var textureHeight = options.textureHeight || 512;
21 | var oneOverWidth = 1.0 / textureWidth;
22 | var oneOverHeight = 1.0 / textureHeight;
23 |
24 | var debug = ( options.debug === undefined ) ? false : options.debug;
25 | this.color1 = options.color1 || new THREE.Color( 0xffffff );
26 | this.color2 = options.color2 || new THREE.Color( 0xffa000 );
27 | this.color3 = options.color3 || new THREE.Color( 0x000000 );
28 | this.colorBias = ( options.colorBias === undefined ) ? 0.8 : options.colorBias;
29 | this.diffuse = ( options.diffuse === undefined ) ? 1.33 : options.diffuse;
30 | this.viscosity = ( options.viscosity === undefined ) ? 0.25 : options.viscosity;
31 | this.expansion = ( options.expansion === undefined ) ? - 0.25 : options.expansion;
32 | this.swirl = ( options.swirl === undefined ) ? 50.0 : options.swirl;
33 | this.burnRate = ( options.burnRate === undefined ) ? 0.3 : options.burnRate;
34 | this.drag = ( options.drag === undefined ) ? 0.35 : options.drag;
35 | this.airSpeed = ( options.airSpeed === undefined ) ? 6.0 : options.airSpeed;
36 | this.windVector = options.windVector || new THREE.Vector2( 0.0, 0.75 );
37 | this.speed = ( options.speed === undefined ) ? 500.0 : options.speed;
38 | this.massConservation = ( options.massConservation === undefined ) ? false : options.massConservation;
39 |
40 | var size = textureWidth * textureHeight;
41 | this.sourceData = new Uint8Array( 4 * size );
42 |
43 | this.clearSources = function () {
44 |
45 | for ( var y = 0; y < textureHeight; y ++ ) {
46 |
47 | for ( var x = 0; x < textureWidth; x ++ ) {
48 |
49 | var i = y * textureWidth + x;
50 | var stride = i * 4;
51 |
52 | this.sourceData[ stride ] = 0;
53 | this.sourceData[ stride + 1 ] = 0;
54 | this.sourceData[ stride + 2 ] = 0;
55 | this.sourceData[ stride + 3 ] = 0;
56 |
57 | }
58 |
59 | }
60 |
61 | this.sourceMaterial.uniforms.sourceMap.value = this.internalSource;
62 | this.sourceMaterial.needsUpdate = true;
63 |
64 | return this.sourceData;
65 |
66 | };
67 |
68 | this.addSource = function ( u, v, radius, density = null, windX = null, windY = null ) {
69 |
70 | var startX = Math.max( Math.floor( ( u - radius ) * textureWidth ), 0 );
71 | var startY = Math.max( Math.floor( ( v - radius ) * textureHeight ), 0 );
72 | var endX = Math.min( Math.floor( ( u + radius ) * textureWidth ), textureWidth );
73 | var endY = Math.min( Math.floor( ( v + radius ) * textureHeight ), textureHeight );
74 |
75 | for ( var y = startY; y < endY; y ++ ) {
76 |
77 | for ( var x = startX; x < endX; x ++ ) {
78 |
79 | var diffX = x * oneOverWidth - u;
80 | var diffY = y * oneOverHeight - v;
81 |
82 | if ( diffX * diffX + diffY * diffY < radius * radius ) {
83 |
84 | var i = y * textureWidth + x;
85 | var stride = i * 4;
86 |
87 | if ( density != null ) {
88 |
89 | this.sourceData[ stride ] = Math.min( Math.max( density, 0.0 ), 1.0 ) * 255;
90 |
91 | }
92 | if ( windX != null ) {
93 |
94 | var wind = Math.min( Math.max( windX, - 1.0 ), 1.0 );
95 | wind = ( wind < 0.0 ) ? Math.floor( wind * 127 ) + 255 : Math.floor( wind * 127 );
96 | this.sourceData[ stride + 1 ] = wind;
97 |
98 | }
99 | if ( windY != null ) {
100 |
101 | var wind = Math.min( Math.max( windY, - 1.0 ), 1.0 );
102 | wind = ( wind < 0.0 ) ? Math.floor( wind * 127 ) + 255 : Math.floor( wind * 127 );
103 | this.sourceData[ stride + 2 ] = wind;
104 |
105 | }
106 |
107 | }
108 |
109 | }
110 |
111 | }
112 |
113 | this.internalSource.needsUpdate = true;
114 |
115 | return this.sourceData;
116 |
117 | };
118 |
119 | // When setting source map, red channel is density. Green and blue channels
120 | // encode x and y velocity respectively as signed chars:
121 | // (0 -> 127 = 0.0 -> 1.0, 128 -> 255 = -1.0 -> 0.0 )
122 | this.setSourceMap = function ( texture ) {
123 |
124 | this.sourceMaterial.uniforms.sourceMap.value = texture;
125 |
126 | };
127 |
128 | var parameters = {
129 | minFilter: THREE.NearestFilter,
130 | magFilter: THREE.NearestFilter,
131 | depthBuffer: false,
132 | stencilBuffer: false
133 | };
134 |
135 |
136 | this.field0 = new THREE.WebGLRenderTarget( textureWidth, textureHeight, parameters );
137 |
138 | this.field0.background = new THREE.Color( 0x000000 );
139 |
140 | this.field1 = new THREE.WebGLRenderTarget( textureWidth, textureHeight, parameters );
141 |
142 | this.field0.background = new THREE.Color( 0x000000 );
143 |
144 | this.fieldProj = new THREE.WebGLRenderTarget( textureWidth, textureHeight, parameters );
145 |
146 | this.field0.background = new THREE.Color( 0x000000 );
147 |
148 | if ( ! THREE.Math.isPowerOfTwo( textureWidth ) ||
149 | ! THREE.Math.isPowerOfTwo( textureHeight ) ) {
150 |
151 | this.field0.texture.generateMipmaps = false;
152 | this.field1.texture.generateMipmaps = false;
153 | this.fieldProj.texture.generateMipmaps = false;
154 |
155 | }
156 |
157 |
158 | this.fieldScene = new THREE.Scene();
159 | this.fieldScene.background = new THREE.Color( 0x000000 );
160 |
161 | this.orthoCamera = new THREE.OrthographicCamera( textureWidth / - 2, textureWidth / 2, textureHeight / 2, textureHeight / - 2, 1, 2 );
162 | this.orthoCamera.position.z = 1;
163 |
164 | this.fieldGeometry = new THREE.PlaneBufferGeometry( textureWidth, textureHeight );
165 |
166 | this.internalSource = new THREE.DataTexture( this.sourceData, textureWidth, textureHeight, THREE.RGBAFormat );
167 |
168 | // Source Shader
169 |
170 | var shader = THREE.Fire.SourceShader;
171 | this.sourceMaterial = new THREE.ShaderMaterial( {
172 | uniforms: shader.uniforms,
173 | vertexShader: shader.vertexShader,
174 | fragmentShader: shader.fragmentShader,
175 | transparent: false
176 | } );
177 |
178 | this.clearSources();
179 |
180 | this.sourceMesh = new THREE.Mesh( this.fieldGeometry, this.sourceMaterial );
181 | this.fieldScene.add( this.sourceMesh );
182 |
183 | // Diffuse Shader
184 |
185 | var shader = THREE.Fire.DiffuseShader;
186 | this.diffuseMaterial = new THREE.ShaderMaterial( {
187 | uniforms: shader.uniforms,
188 | vertexShader: shader.vertexShader,
189 | fragmentShader: shader.fragmentShader,
190 | transparent: false
191 | } );
192 |
193 | this.diffuseMaterial.uniforms.oneOverWidth.value = oneOverWidth;
194 | this.diffuseMaterial.uniforms.oneOverHeight.value = oneOverHeight;
195 |
196 | this.diffuseMesh = new THREE.Mesh( this.fieldGeometry, this.diffuseMaterial );
197 | this.fieldScene.add( this.diffuseMesh );
198 |
199 | // Drift Shader
200 |
201 | shader = THREE.Fire.DriftShader;
202 | this.driftMaterial = new THREE.ShaderMaterial( {
203 | uniforms: shader.uniforms,
204 | vertexShader: shader.vertexShader,
205 | fragmentShader: shader.fragmentShader,
206 | transparent: false
207 | } );
208 |
209 | this.driftMaterial.uniforms.oneOverWidth.value = oneOverWidth;
210 | this.driftMaterial.uniforms.oneOverHeight.value = oneOverHeight;
211 |
212 | this.driftMesh = new THREE.Mesh( this.fieldGeometry, this.driftMaterial );
213 | this.fieldScene.add( this.driftMesh );
214 |
215 | // Projection Shader 1
216 |
217 | shader = THREE.Fire.ProjectionShader1;
218 | this.projMaterial1 = new THREE.ShaderMaterial( {
219 | uniforms: shader.uniforms,
220 | vertexShader: shader.vertexShader,
221 | fragmentShader: shader.fragmentShader,
222 | transparent: false
223 | } );
224 |
225 | this.projMaterial1.uniforms.oneOverWidth.value = oneOverWidth;
226 | this.projMaterial1.uniforms.oneOverHeight.value = oneOverHeight;
227 |
228 | this.projMesh1 = new THREE.Mesh( this.fieldGeometry, this.projMaterial1 );
229 | this.fieldScene.add( this.projMesh1 );
230 |
231 | // Projection Shader 2
232 |
233 | shader = THREE.Fire.ProjectionShader2;
234 | this.projMaterial2 = new THREE.ShaderMaterial( {
235 | uniforms: shader.uniforms,
236 | vertexShader: shader.vertexShader,
237 | fragmentShader: shader.fragmentShader,
238 | transparent: false
239 | } );
240 |
241 |
242 | this.projMaterial2.uniforms.oneOverWidth.value = oneOverWidth;
243 | this.projMaterial2.uniforms.oneOverHeight.value = oneOverHeight;
244 |
245 | this.projMesh2 = new THREE.Mesh( this.fieldGeometry, this.projMaterial2 );
246 | this.fieldScene.add( this.projMesh2 );
247 |
248 | // Projection Shader 3
249 |
250 | shader = THREE.Fire.ProjectionShader3;
251 | this.projMaterial3 = new THREE.ShaderMaterial( {
252 | uniforms: shader.uniforms,
253 | vertexShader: shader.vertexShader,
254 | fragmentShader: shader.fragmentShader,
255 | transparent: false
256 | } );
257 |
258 |
259 | this.projMaterial3.uniforms.oneOverWidth.value = oneOverWidth;
260 | this.projMaterial3.uniforms.oneOverHeight.value = oneOverHeight;
261 |
262 | this.projMesh3 = new THREE.Mesh( this.fieldGeometry, this.projMaterial3 );
263 | this.fieldScene.add( this.projMesh3 );
264 |
265 | // Color Shader
266 |
267 | if ( debug ) {
268 |
269 | shader = THREE.Fire.DebugShader;
270 |
271 | } else {
272 |
273 | shader = THREE.Fire.ColorShader;
274 |
275 | }
276 | this.material = new THREE.ShaderMaterial( {
277 | uniforms: shader.uniforms,
278 | vertexShader: shader.vertexShader,
279 | fragmentShader: shader.fragmentShader,
280 | transparent: true
281 | } );
282 |
283 | this.material.uniforms.densityMap.value = this.field1.texture;
284 |
285 | this.configShaders = function ( dt ) {
286 |
287 | this.diffuseMaterial.uniforms.diffuse.value = dt * 0.05 * this.diffuse;
288 | this.diffuseMaterial.uniforms.viscosity.value = dt * 0.05 * this.viscosity;
289 | this.diffuseMaterial.uniforms.expansion.value = Math.exp( this.expansion * - 1.0 );
290 | this.diffuseMaterial.uniforms.swirl.value = Math.exp( this.swirl * - 0.1 );
291 | this.diffuseMaterial.uniforms.drag.value = Math.exp( this.drag * - 0.1 );
292 | this.diffuseMaterial.uniforms.burnRate.value = this.burnRate * dt * 0.01;
293 | this.driftMaterial.uniforms.windVector.value = this.windVector;
294 | this.driftMaterial.uniforms.airSpeed.value = dt * this.airSpeed * 0.001 * textureHeight;
295 | this.material.uniforms.color1.value = this.color1;
296 | this.material.uniforms.color2.value = this.color2;
297 | this.material.uniforms.color3.value = this.color3;
298 | this.material.uniforms.colorBias.value = this.colorBias;
299 |
300 | };
301 |
302 | this.clearDiffuse = function () {
303 |
304 | this.diffuseMaterial.uniforms.expansion.value = 1.0;
305 | this.diffuseMaterial.uniforms.swirl.value = 1.0;
306 | this.diffuseMaterial.uniforms.drag.value = 1.0;
307 | this.diffuseMaterial.uniforms.burnRate.value = 0.0;
308 |
309 | };
310 |
311 | this.swapTextures = function () {
312 |
313 | var swap = this.field0;
314 | this.field0 = this.field1;
315 | this.field1 = swap;
316 |
317 | };
318 |
319 | this.saveRenderState = function ( renderer ) {
320 |
321 | this.savedRenderTarget = renderer.getRenderTarget();
322 | this.savedVrEnabled = renderer.xr.enabled;
323 | this.savedShadowAutoUpdate = renderer.shadowMap.autoUpdate;
324 | this.savedAntialias = renderer.antialias;
325 | this.savedToneMapping = renderer.toneMapping;
326 |
327 | };
328 |
329 | this.restoreRenderState = function ( renderer ) {
330 |
331 | renderer.xr.enabled = this.savedVrEnabled;
332 | renderer.shadowMap.autoUpdate = this.savedShadowAutoUpdate;
333 | renderer.setRenderTarget( this.savedRenderTarget );
334 | renderer.antialias = this.savedAntialias;
335 | renderer.toneMapping = this.savedToneMapping;
336 |
337 | };
338 |
339 | this.renderSource = function ( renderer ) {
340 |
341 | this.sourceMesh.visible = true;
342 |
343 | this.sourceMaterial.uniforms.densityMap.value = this.field0.texture;
344 |
345 | renderer.setRenderTarget(this.field1)
346 | renderer.render( this.fieldScene, this.orthoCamera);
347 |
348 | this.sourceMesh.visible = false;
349 |
350 | this.swapTextures();
351 |
352 | };
353 |
354 | this.renderDiffuse = function ( renderer ) {
355 |
356 | this.diffuseMesh.visible = true;
357 |
358 | this.diffuseMaterial.uniforms.densityMap.value = this.field0.texture;
359 |
360 | renderer.setRenderTarget(this.field1)
361 | renderer.render( this.fieldScene, this.orthoCamera);
362 |
363 | this.diffuseMesh.visible = false;
364 |
365 | this.swapTextures();
366 |
367 | };
368 |
369 | this.renderDrift = function ( renderer ) {
370 |
371 | this.driftMesh.visible = true;
372 |
373 | this.driftMaterial.uniforms.densityMap.value = this.field0.texture;
374 |
375 | renderer.setRenderTarget(this.field1)
376 | renderer.render( this.fieldScene, this.orthoCamera);
377 |
378 | this.driftMesh.visible = false;
379 |
380 | this.swapTextures();
381 |
382 | };
383 |
384 | this.renderProject = function ( renderer ) {
385 |
386 | // Projection pass 1
387 |
388 | this.projMesh1.visible = true;
389 |
390 | this.projMaterial1.uniforms.densityMap.value = this.field0.texture;
391 |
392 | renderer.setRenderTarget(this.field1)
393 | renderer.render( this.fieldScene, this.orthoCamera);
394 |
395 | this.projMesh1.visible = false;
396 |
397 | this.projMaterial2.uniforms.densityMap.value = this.fieldProj.texture;
398 |
399 | // Projection pass 2
400 |
401 | this.projMesh2.visible = true;
402 |
403 | for ( var i = 0; i < 20; i ++ ) {
404 |
405 | renderer.setRenderTarget(this.field1)
406 | renderer.render( this.fieldScene, this.orthoCamera);
407 |
408 | var temp = this.field1;
409 | this.field1 = this.fieldProj;
410 | this.fieldProj = temp;
411 |
412 | this.projMaterial2.uniforms.densityMap.value = this.fieldProj.texture;
413 |
414 | }
415 |
416 | this.projMesh2.visible = false;
417 |
418 | this.projMaterial3.uniforms.densityMap.value = this.field0.texture;
419 | this.projMaterial3.uniforms.projMap.value = this.fieldProj.texture;
420 |
421 | // Projection pass 3
422 |
423 | this.projMesh3.visible = true;
424 |
425 | renderer.setRenderTarget(this.field1)
426 | renderer.render( this.fieldScene, this.orthoCamera);
427 |
428 | this.projMesh3.visible = false;
429 |
430 | this.swapTextures();
431 |
432 | };
433 |
434 | this.onBeforeRender = function ( renderer, scene, camera ) {
435 |
436 | var delta = this.clock.getDelta();
437 | if ( delta > 0.1 ) {
438 |
439 | delta = 0.1;
440 |
441 | }
442 | var dt = delta * ( this.speed * 0.1 );
443 |
444 | this.configShaders( dt );
445 |
446 | this.saveRenderState( renderer );
447 |
448 | renderer.xr.enabled = false; // Avoid camera modification and recursion
449 | renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows
450 | renderer.antialias = false;
451 | renderer.toneMapping = THREE.NoToneMapping;
452 |
453 | this.sourceMesh.visible = false;
454 | this.diffuseMesh.visible = false;
455 | this.driftMesh.visible = false;
456 | this.projMesh1.visible = false;
457 | this.projMesh2.visible = false;
458 | this.projMesh3.visible = false;
459 |
460 | this.renderSource( renderer );
461 |
462 | this.clearDiffuse();
463 | for ( var i = 0; i < 21; i ++ ) {
464 |
465 | this.renderDiffuse( renderer );
466 |
467 | }
468 | this.configShaders( dt );
469 | this.renderDiffuse( renderer );
470 |
471 | this.renderDrift( renderer );
472 |
473 | if ( this.massConservation ) {
474 |
475 | this.renderProject( renderer );
476 | this.renderProject( renderer );
477 |
478 | }
479 |
480 | // Final result out for coloring
481 |
482 | this.material.map = this.field1.texture;
483 | this.material.transparent = true;
484 | this.material.minFilter = THREE.LinearFilter,
485 | this.material.magFilter = THREE.LinearFilter,
486 |
487 | this.restoreRenderState( renderer );
488 |
489 | };
490 |
491 | };
492 |
493 |
494 | THREE.Fire.prototype = Object.create( THREE.Mesh.prototype );
495 | THREE.Fire.prototype.constructor = THREE.Fire;
496 |
497 | THREE.Fire.SourceShader = {
498 |
499 | uniforms: {
500 | 'sourceMap': {
501 | type: 't',
502 | value: null
503 | },
504 | 'densityMap': {
505 | type: 't',
506 | value: null
507 | }
508 | },
509 |
510 | vertexShader: [
511 | 'varying vec2 vUv;',
512 |
513 | 'void main() {',
514 |
515 | ' vUv = uv;',
516 |
517 | ' vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );',
518 | ' gl_Position = projectionMatrix * mvPosition;',
519 |
520 | '}'
521 |
522 | ].join( "\n" ),
523 |
524 | fragmentShader: [
525 | 'uniform sampler2D sourceMap;',
526 | 'uniform sampler2D densityMap;',
527 |
528 | 'varying vec2 vUv;',
529 |
530 | 'void main() {',
531 | ' vec4 source = texture2D( sourceMap, vUv );',
532 | ' vec4 current = texture2D( densityMap, vUv );',
533 |
534 | ' vec2 v0 = (current.gb - step(0.5, current.gb)) * 2.0;',
535 | ' vec2 v1 = (source.gb - step(0.5, source.gb)) * 2.0;',
536 |
537 | ' vec2 newVel = v0 + v1;',
538 |
539 | ' newVel = clamp(newVel, -0.99, 0.99);',
540 | ' newVel = newVel * 0.5 + step(0.0, -newVel);',
541 |
542 | ' float newDensity = source.r + current.a;',
543 | ' float newTemp = source.r + current.r;',
544 |
545 | ' newDensity = clamp(newDensity, 0.0, 1.0);',
546 | ' newTemp = clamp(newTemp, 0.0, 1.0);',
547 |
548 | ' gl_FragColor = vec4(newTemp, newVel.xy, newDensity);',
549 |
550 | '}'
551 |
552 | ].join( "\n" )
553 | };
554 |
555 |
556 | THREE.Fire.DiffuseShader = {
557 |
558 | uniforms: {
559 | 'oneOverWidth': {
560 | type: 'f',
561 | value: null
562 | },
563 | 'oneOverHeight': {
564 | type: 'f',
565 | value: null
566 | },
567 | 'diffuse': {
568 | type: 'f',
569 | value: null
570 | },
571 | 'viscosity': {
572 | type: 'f',
573 | value: null
574 | },
575 | 'expansion': {
576 | type: 'f',
577 | value: null
578 | },
579 | 'swirl': {
580 | type: 'f',
581 | value: null
582 | },
583 | 'drag': {
584 | type: 'f',
585 | value: null
586 | },
587 | 'burnRate': {
588 | type: 'f',
589 | value: null
590 | },
591 | 'densityMap': {
592 | type: 't',
593 | value: null
594 | }
595 | },
596 |
597 | vertexShader: [
598 | 'varying vec2 vUv;',
599 |
600 | 'void main() {',
601 |
602 | ' vUv = uv;',
603 |
604 | ' vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );',
605 | ' gl_Position = projectionMatrix * mvPosition;',
606 |
607 | '}'
608 |
609 | ].join( "\n" ),
610 |
611 | fragmentShader: [
612 | 'uniform float oneOverWidth;',
613 | 'uniform float oneOverHeight;',
614 | 'uniform float diffuse;',
615 | 'uniform float viscosity;',
616 | 'uniform float expansion;',
617 | 'uniform float swirl;',
618 | 'uniform float burnRate;',
619 | 'uniform float drag;',
620 | 'uniform sampler2D densityMap;',
621 |
622 | 'varying vec2 vUv;',
623 |
624 | 'void main() {',
625 |
626 | ' vec4 dC = texture2D( densityMap, vUv );',
627 | ' vec4 dL = texture2D( densityMap, vec2(vUv.x - oneOverWidth, vUv.y) );',
628 | ' vec4 dR = texture2D( densityMap, vec2(vUv.x + oneOverWidth, vUv.y) );',
629 | ' vec4 dU = texture2D( densityMap, vec2(vUv.x, vUv.y - oneOverHeight) );',
630 | ' vec4 dD = texture2D( densityMap, vec2(vUv.x, vUv.y + oneOverHeight) );',
631 | ' vec4 dUL = texture2D( densityMap, vec2(vUv.x - oneOverWidth, vUv.y - oneOverHeight) );',
632 | ' vec4 dUR = texture2D( densityMap, vec2(vUv.x + oneOverWidth, vUv.y - oneOverHeight) );',
633 | ' vec4 dDL = texture2D( densityMap, vec2(vUv.x - oneOverWidth, vUv.y + oneOverHeight) );',
634 | ' vec4 dDR = texture2D( densityMap, vec2(vUv.x + oneOverWidth, vUv.y + oneOverHeight) );',
635 |
636 | ' dC.yz = (dC.yz - step(0.5, dC.yz)) * 2.0;',
637 | ' dL.yz = (dL.yz - step(0.5, dL.yz)) * 2.0;',
638 | ' dR.yz = (dR.yz - step(0.5, dR.yz)) * 2.0;',
639 | ' dU.yz = (dU.yz - step(0.5, dU.yz)) * 2.0;',
640 | ' dD.yz = (dD.yz - step(0.5, dD.yz)) * 2.0;',
641 | ' dUL.yz = (dUL.yz - step(0.5, dUL.yz)) * 2.0;',
642 | ' dUR.yz = (dUR.yz - step(0.5, dUR.yz)) * 2.0;',
643 | ' dDL.yz = (dDL.yz - step(0.5, dDL.yz)) * 2.0;',
644 | ' dDR.yz = (dDR.yz - step(0.5, dDR.yz)) * 2.0;',
645 |
646 | ' vec4 result = (dC + vec4(diffuse, viscosity, viscosity, diffuse) * ( dL + dR + dU + dD + dUL + dUR + dDL + dDR )) / (1.0 + 8.0 * vec4(diffuse, viscosity, viscosity, diffuse)) - vec4(0.0, 0.0, 0.0, 0.001);',
647 |
648 | ' float temperature = result.r;',
649 | ' temperature = clamp(temperature - burnRate, 0.0, 1.0);',
650 |
651 | ' vec2 velocity = result.yz;',
652 |
653 | ' vec2 expansionVec = vec2(dL.w - dR.w, dU.w - dD.w);',
654 |
655 | ' vec2 swirlVec = vec2((dL.z - dR.z) * 0.5, (dU.y - dD.y) * 0.5);',
656 |
657 | ' velocity = velocity + (1.0 - expansion) * expansionVec + (1.0 - swirl) * swirlVec;',
658 |
659 | ' velocity = velocity - (1.0 - drag) * velocity;',
660 |
661 | ' gl_FragColor = vec4(temperature, velocity * 0.5 + step(0.0, -velocity), result.w);',
662 |
663 | ' gl_FragColor = gl_FragColor * step(oneOverWidth, vUv.x);',
664 | ' gl_FragColor = gl_FragColor * step(oneOverHeight, vUv.y);',
665 | ' gl_FragColor = gl_FragColor * step(vUv.x, 1.0 - oneOverWidth);',
666 | ' gl_FragColor = gl_FragColor * step(vUv.y, 1.0 - oneOverHeight);',
667 |
668 | '}'
669 |
670 | ].join( "\n" )
671 | };
672 |
673 | THREE.Fire.DriftShader = {
674 |
675 | uniforms: {
676 | 'oneOverWidth': {
677 | type: 'f',
678 | value: null
679 | },
680 | 'oneOverHeight': {
681 | type: 'f',
682 | value: null
683 | },
684 | 'windVector': {
685 | type: 'v2',
686 | value: new THREE.Vector2( 0.0, 0.0 )
687 | },
688 | 'airSpeed': {
689 | type: 'f',
690 | value: null
691 | },
692 | 'densityMap': {
693 | type: 't',
694 | value: null
695 | }
696 | },
697 |
698 | vertexShader: [
699 | 'varying vec2 vUv;',
700 |
701 | 'void main() {',
702 |
703 | ' vUv = uv;',
704 |
705 | ' vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );',
706 | ' gl_Position = projectionMatrix * mvPosition;',
707 |
708 | '}'
709 |
710 | ].join( "\n" ),
711 |
712 | fragmentShader: [
713 | 'uniform float oneOverWidth;',
714 | 'uniform float oneOverHeight;',
715 | 'uniform vec2 windVector;',
716 | 'uniform float airSpeed;',
717 | 'uniform sampler2D densityMap;',
718 |
719 | 'varying vec2 vUv;',
720 |
721 | 'void main() {',
722 | ' vec2 velocity = texture2D( densityMap, vUv ).gb;',
723 | ' velocity = (velocity - step(0.5, velocity)) * 2.0;',
724 |
725 | ' velocity = velocity + windVector;',
726 |
727 | ' vec2 sourcePos = vUv - airSpeed * vec2(oneOverWidth, oneOverHeight) * velocity;',
728 |
729 | ' vec2 units = sourcePos / vec2(oneOverWidth, oneOverHeight);',
730 |
731 | ' vec2 intPos = floor(units);',
732 | ' vec2 frac = units - intPos;',
733 | ' intPos = intPos * vec2(oneOverWidth, oneOverHeight);',
734 |
735 | ' vec4 dX0Y0 = texture2D( densityMap, intPos + vec2(0.0, -oneOverHeight) );',
736 | ' vec4 dX1Y0 = texture2D( densityMap, intPos + vec2(oneOverWidth, 0.0) );',
737 | ' vec4 dX0Y1 = texture2D( densityMap, intPos + vec2(0.0, oneOverHeight) );',
738 | ' vec4 dX1Y1 = texture2D( densityMap, intPos + vec2(oneOverWidth, oneOverHeight) );',
739 |
740 |
741 | ' dX0Y0.gb = (dX0Y0.gb - step(0.5, dX0Y0.gb)) * 2.0;',
742 | ' dX1Y0.gb = (dX1Y0.gb - step(0.5, dX1Y0.gb)) * 2.0;',
743 | ' dX0Y1.gb = (dX0Y1.gb - step(0.5, dX0Y1.gb)) * 2.0;',
744 | ' dX1Y1.gb = (dX1Y1.gb - step(0.5, dX1Y1.gb)) * 2.0;',
745 |
746 | ' vec4 source = mix(mix(dX0Y0, dX1Y0, frac.x), mix(dX0Y1, dX1Y1, frac.x), frac.y);',
747 |
748 | ' source.gb = source.gb * 0.5 + step(0.0, -source.gb);',
749 |
750 | ' gl_FragColor = source;',
751 |
752 | '}'
753 |
754 | ].join( "\n" )
755 | };
756 |
757 |
758 | THREE.Fire.ProjectionShader1 = {
759 |
760 | uniforms: {
761 | 'oneOverWidth': {
762 | type: 'f',
763 | value: null
764 | },
765 | 'oneOverHeight': {
766 | type: 'f',
767 | value: null
768 | },
769 | 'densityMap': {
770 | type: 't',
771 | value: null
772 | }
773 | },
774 |
775 | vertexShader: [
776 | 'varying vec2 vUv;',
777 |
778 | 'void main() {',
779 |
780 | ' vUv = uv;',
781 |
782 | ' vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );',
783 | ' gl_Position = projectionMatrix * mvPosition;',
784 |
785 | '}'
786 |
787 | ].join( "\n" ),
788 |
789 | fragmentShader: [
790 | 'uniform float oneOverWidth;',
791 | 'uniform float oneOverHeight;',
792 | 'uniform sampler2D densityMap;',
793 |
794 | 'varying vec2 vUv;',
795 |
796 | 'void main() {',
797 | ' float dL = texture2D( densityMap, vec2(vUv.x - oneOverWidth, vUv.y) ).g;',
798 | ' float dR = texture2D( densityMap, vec2(vUv.x + oneOverWidth, vUv.y) ).g;',
799 | ' float dU = texture2D( densityMap, vec2(vUv.x, vUv.y - oneOverHeight) ).b;',
800 | ' float dD = texture2D( densityMap, vec2(vUv.x, vUv.y + oneOverHeight) ).b;',
801 |
802 | ' dL = (dL - step(0.5, dL)) * 2.0;',
803 | ' dR = (dR - step(0.5, dR)) * 2.0;',
804 | ' dU = (dU - step(0.5, dU)) * 2.0;',
805 | ' dD = (dD - step(0.5, dD)) * 2.0;',
806 |
807 | ' float h = (oneOverWidth + oneOverHeight) * 0.5;',
808 | ' float div = -0.5 * h * (dR - dL + dD - dU);',
809 |
810 | ' gl_FragColor = vec4( 0.0, 0.0, div * 0.5 + step(0.0, -div), 0.0);',
811 |
812 | '}'
813 |
814 | ].join( "\n" )
815 | };
816 |
817 |
818 | THREE.Fire.ProjectionShader2 = {
819 |
820 | uniforms: {
821 | 'oneOverWidth': {
822 | type: 'f',
823 | value: null
824 | },
825 | 'oneOverHeight': {
826 | type: 'f',
827 | value: null
828 | },
829 | 'densityMap': {
830 | type: 't',
831 | value: null
832 | }
833 | },
834 |
835 | vertexShader: [
836 | 'varying vec2 vUv;',
837 |
838 | 'void main() {',
839 |
840 | ' vUv = uv;',
841 |
842 | ' vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );',
843 | ' gl_Position = projectionMatrix * mvPosition;',
844 |
845 | '}'
846 |
847 | ].join( "\n" ),
848 |
849 | fragmentShader: [
850 | 'uniform float oneOverWidth;',
851 | 'uniform float oneOverHeight;',
852 | 'uniform sampler2D densityMap;',
853 |
854 | 'varying vec2 vUv;',
855 |
856 | 'void main() {',
857 | ' float div = texture2D( densityMap, vUv ).b;',
858 | ' float pL = texture2D( densityMap, vec2(vUv.x - oneOverWidth, vUv.y) ).g;',
859 | ' float pR = texture2D( densityMap, vec2(vUv.x + oneOverWidth, vUv.y) ).g;',
860 | ' float pU = texture2D( densityMap, vec2(vUv.x, vUv.y - oneOverHeight) ).g;',
861 | ' float pD = texture2D( densityMap, vec2(vUv.x, vUv.y + oneOverHeight) ).g;',
862 |
863 | ' float divNorm = (div - step(0.5, div)) * 2.0;',
864 | ' pL = (pL - step(0.5, pL)) * 2.0;',
865 | ' pR = (pR - step(0.5, pR)) * 2.0;',
866 | ' pU = (pU - step(0.5, pU)) * 2.0;',
867 | ' pD = (pD - step(0.5, pD)) * 2.0;',
868 |
869 | ' float p = (divNorm + pR + pL + pD + pU) * 0.25;',
870 |
871 | ' gl_FragColor = vec4( 0.0, p * 0.5 + step(0.0, -p), div, 0.0);',
872 |
873 | '}'
874 |
875 | ].join( "\n" )
876 | };
877 |
878 |
879 | THREE.Fire.ProjectionShader3 = {
880 |
881 | uniforms: {
882 | 'oneOverWidth': {
883 | type: 'f',
884 | value: null
885 | },
886 | 'oneOverHeight': {
887 | type: 'f',
888 | value: null
889 | },
890 | 'densityMap': {
891 | type: 't',
892 | value: null
893 | },
894 | 'projMap': {
895 | type: 't',
896 | value: null
897 | }
898 | },
899 |
900 | vertexShader: [
901 | 'varying vec2 vUv;',
902 |
903 | 'void main() {',
904 |
905 | ' vUv = uv;',
906 |
907 | ' vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );',
908 | ' gl_Position = projectionMatrix * mvPosition;',
909 |
910 | '}'
911 |
912 | ].join( "\n" ),
913 |
914 | fragmentShader: [
915 | 'uniform float oneOverWidth;',
916 | 'uniform float oneOverHeight;',
917 | 'uniform sampler2D densityMap;',
918 | 'uniform sampler2D projMap;',
919 |
920 | 'varying vec2 vUv;',
921 |
922 | 'void main() {',
923 | ' vec4 orig = texture2D(densityMap, vUv);',
924 |
925 | ' float pL = texture2D( projMap, vec2(vUv.x - oneOverWidth, vUv.y) ).g;',
926 | ' float pR = texture2D( projMap, vec2(vUv.x + oneOverWidth, vUv.y) ).g;',
927 | ' float pU = texture2D( projMap, vec2(vUv.x, vUv.y - oneOverHeight) ).g;',
928 | ' float pD = texture2D( projMap, vec2(vUv.x, vUv.y + oneOverHeight) ).g;',
929 |
930 | ' float uNorm = (orig.g - step(0.5, orig.g)) * 2.0;',
931 | ' float vNorm = (orig.b - step(0.5, orig.b)) * 2.0;',
932 |
933 | ' pL = (pL - step(0.5, pL)) * 2.0;',
934 | ' pR = (pR - step(0.5, pR)) * 2.0;',
935 | ' pU = (pU - step(0.5, pU)) * 2.0;',
936 | ' pD = (pD - step(0.5, pD)) * 2.0;',
937 |
938 | ' float h = (oneOverWidth + oneOverHeight) * 0.5;',
939 | ' float u = uNorm - (0.5 * (pR - pL) / h);',
940 | ' float v = vNorm - (0.5 * (pD - pU) / h);',
941 |
942 | ' gl_FragColor = vec4( orig.r, u * 0.5 + step(0.0, -u), v * 0.5 + step(0.0, -v), orig.a);',
943 |
944 | '}'
945 |
946 | ].join( "\n" )
947 | };
948 |
949 | THREE.Fire.ColorShader = {
950 |
951 | uniforms: {
952 | 'color1': {
953 | type: 'c',
954 | value: null
955 | },
956 | 'color2': {
957 | type: 'c',
958 | value: null
959 | },
960 | 'color3': {
961 | type: 'c',
962 | value: null
963 | },
964 | 'colorBias': {
965 | type: 'f',
966 | value: null
967 | },
968 | 'densityMap': {
969 | type: 't',
970 | value: null
971 | }
972 | },
973 |
974 | vertexShader: [
975 | 'varying vec2 vUv;',
976 |
977 | 'void main() {',
978 |
979 | ' vUv = uv;',
980 |
981 | ' vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );',
982 | ' gl_Position = projectionMatrix * mvPosition;',
983 |
984 | '}'
985 |
986 | ].join( "\n" ),
987 |
988 | fragmentShader: [
989 | 'uniform vec3 color1;',
990 | 'uniform vec3 color2;',
991 | 'uniform vec3 color3;',
992 | 'uniform float colorBias;',
993 | 'uniform sampler2D densityMap;',
994 |
995 | 'varying vec2 vUv;',
996 |
997 | 'void main() {',
998 | ' float density = texture2D( densityMap, vUv ).a;',
999 | ' float temperature = texture2D( densityMap, vUv ).r;',
1000 |
1001 | ' float bias = clamp(colorBias, 0.0001, 0.9999);',
1002 |
1003 | ' vec3 blend1 = mix(color3, color2, temperature / bias) * (1.0 - step(bias, temperature));',
1004 | ' vec3 blend2 = mix(color2, color1, (temperature - bias) / (1.0 - bias) ) * step(bias, temperature);',
1005 |
1006 | ' gl_FragColor = vec4(blend1 + blend2, density);',
1007 | '}'
1008 |
1009 | ].join( "\n" )
1010 | };
1011 |
1012 |
1013 | THREE.Fire.DebugShader = {
1014 |
1015 | uniforms: {
1016 | 'color1': {
1017 | type: 'c',
1018 | value: null
1019 | },
1020 | 'color2': {
1021 | type: 'c',
1022 | value: null
1023 | },
1024 | 'color3': {
1025 | type: 'c',
1026 | value: null
1027 | },
1028 | 'colorBias': {
1029 | type: 'f',
1030 | value: null
1031 | },
1032 | 'densityMap': {
1033 | type: 't',
1034 | value: null
1035 | }
1036 | },
1037 |
1038 | vertexShader: [
1039 | 'varying vec2 vUv;',
1040 |
1041 | 'void main() {',
1042 |
1043 | ' vUv = uv;',
1044 |
1045 | ' vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );',
1046 | ' gl_Position = projectionMatrix * mvPosition;',
1047 |
1048 | '}'
1049 |
1050 | ].join( "\n" ),
1051 |
1052 | fragmentShader: [
1053 | 'uniform sampler2D densityMap;',
1054 |
1055 | 'varying vec2 vUv;',
1056 |
1057 | 'void main() {',
1058 | ' float density;',
1059 | ' density = texture2D( densityMap, vUv ).a;',
1060 |
1061 | ' vec2 vel = texture2D( densityMap, vUv ).gb;',
1062 |
1063 | ' vel = (vel - step(0.5, vel)) * 2.0;',
1064 |
1065 | ' float r = density;',
1066 | ' float g = max(abs(vel.x), density * 0.5);',
1067 | ' float b = max(abs(vel.y), density * 0.5);',
1068 | ' float a = max(density * 0.5, max(abs(vel.x), abs(vel.y)));',
1069 |
1070 | ' gl_FragColor = vec4(r, g, b, a);',
1071 |
1072 | '}'
1073 |
1074 | ].join( "\n" )
1075 | };
1076 |
--------------------------------------------------------------------------------
/src/MTLLoaderPhysical.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Loads a Wavefront .mtl file specifying materials
3 | *
4 | * @author angelxuanchang
5 | */
6 |
7 | THREE.MTLLoader = function(manager) {
8 | this.manager =
9 | manager !== undefined ? manager : THREE.DefaultLoadingManager;
10 | };
11 |
12 | THREE.MTLLoader.prototype = {
13 | constructor: THREE.MTLLoader,
14 |
15 | /**
16 | * Loads and parses a MTL asset from a URL.
17 | *
18 | * @param {String} url - URL to the MTL file.
19 | * @param {Function} [onLoad] - Callback invoked with the loaded object.
20 | * @param {Function} [onProgress] - Callback for download progress.
21 | * @param {Function} [onError] - Callback for download errors.
22 | *
23 | * @see setPath setResourcePath
24 | *
25 | * @note In order for relative texture references to resolve correctly
26 | * you must call setResourcePath() explicitly prior to load.
27 | */
28 | load: function(url, onLoad, onProgress, onError) {
29 | var scope = this;
30 |
31 | var path =
32 | this.path === undefined
33 | ? THREE.LoaderUtils.extractUrlBase(url)
34 | : this.path;
35 |
36 | var loader = new THREE.FileLoader(this.manager);
37 | loader.setPath(this.path);
38 | loader.load(
39 | url,
40 | function(text) {
41 | onLoad(scope.parse(text, path));
42 | },
43 | onProgress,
44 | onError
45 | );
46 | },
47 |
48 | /**
49 | * Set base path for resolving references.
50 | * If set this path will be prepended to each loaded and found reference.
51 | *
52 | * @see setResourcePath
53 | * @param {String} path
54 | * @return {THREE.MTLLoader}
55 | *
56 | * @example
57 | * mtlLoader.setPath( 'assets/obj/' );
58 | * mtlLoader.load( 'my.mtl', ... );
59 | */
60 | setPath: function(path) {
61 | this.path = path;
62 | return this;
63 | },
64 |
65 | /**
66 | * Set base path for additional resources like textures.
67 | *
68 | * @see setPath
69 | * @param {String} path
70 | * @return {THREE.MTLLoader}
71 | *
72 | * @example
73 | * mtlLoader.setPath( 'assets/obj/' );
74 | * mtlLoader.setResourcePath( 'assets/textures/' );
75 | * mtlLoader.load( 'my.mtl', ... );
76 | */
77 | setResourcePath: function(path) {
78 | this.resourcePath = path;
79 | return this;
80 | },
81 |
82 | setTexturePath: function(path) {
83 | console.warn(
84 | "THREE.MTLLoader: .setTexturePath() has been renamed to .setResourcePath()."
85 | );
86 | return this.setResourcePath(path);
87 | },
88 |
89 | setCrossOrigin: function(value) {
90 | this.crossOrigin = value;
91 | return this;
92 | },
93 |
94 | setMaterialOptions: function(value) {
95 | this.materialOptions = value;
96 | return this;
97 | },
98 |
99 | /**
100 | * Parses a MTL file.
101 | *
102 | * @param {String} text - Content of MTL file
103 | * @return {THREE.MTLLoader.MaterialCreator}
104 | *
105 | * @see setPath setResourcePath
106 | *
107 | * @note In order for relative texture references to resolve correctly
108 | * you must call setResourcePath() explicitly prior to parse.
109 | */
110 | parse: function(text, path) {
111 | var lines = text.split("\n");
112 | var info = {};
113 | var delimiter_pattern = /\s+/;
114 | var materialsInfo = {};
115 |
116 | for (var i = 0; i < lines.length; i++) {
117 | var line = lines[i];
118 | line = line.trim();
119 |
120 | if (line.length === 0 || line.charAt(0) === "#") {
121 | // Blank line or comment ignore
122 | continue;
123 | }
124 |
125 | var pos = line.indexOf(" ");
126 |
127 | var key = pos >= 0 ? line.substring(0, pos) : line;
128 | key = key.toLowerCase();
129 |
130 | var value = pos >= 0 ? line.substring(pos + 1) : "";
131 | value = value.trim();
132 |
133 | if (key === "newmtl") {
134 | // New material
135 |
136 | info = { name: value };
137 | materialsInfo[value] = info;
138 | } else {
139 | if (
140 | key === "ka" ||
141 | key === "kd" ||
142 | key === "ks" ||
143 | key === "ke"
144 | ) {
145 | var ss = value.split(delimiter_pattern, 3);
146 | info[key] = [
147 | parseFloat(ss[0]),
148 | parseFloat(ss[1]),
149 | parseFloat(ss[2])
150 | ];
151 | } else {
152 | info[key] = value;
153 | }
154 | }
155 | }
156 |
157 | var materialCreator = new THREE.MTLLoader.MaterialCreator(
158 | this.resourcePath || path,
159 | this.materialOptions
160 | );
161 | materialCreator.setCrossOrigin(this.crossOrigin);
162 | materialCreator.setManager(this.manager);
163 | materialCreator.setMaterials(materialsInfo);
164 | return materialCreator;
165 | }
166 | };
167 |
168 | /**
169 | * Create a new THREE-MTLLoader.MaterialCreator
170 | * @param baseUrl - Url relative to which textures are loaded
171 | * @param options - Set of options on how to construct the materials
172 | * side: Which side to apply the material
173 | * THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide
174 | * wrap: What type of wrapping to apply for textures
175 | * THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping
176 | * normalizeRGB: RGBs need to be normalized to 0-1 from 0-255
177 | * Default: false, assumed to be already normalized
178 | * ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's
179 | * Default: false
180 | * @constructor
181 | */
182 |
183 | THREE.MTLLoader.MaterialCreator = function(baseUrl, options) {
184 | this.baseUrl = baseUrl || "";
185 | this.options = options;
186 | this.materialsInfo = {};
187 | this.materials = {};
188 | this.materialsArray = [];
189 | this.nameLookup = {};
190 |
191 | this.side =
192 | this.options && this.options.side ? this.options.side : THREE.FrontSide;
193 | this.wrap =
194 | this.options && this.options.wrap
195 | ? this.options.wrap
196 | : THREE.RepeatWrapping;
197 | };
198 |
199 | THREE.MTLLoader.MaterialCreator.prototype = {
200 | constructor: THREE.MTLLoader.MaterialCreator,
201 |
202 | crossOrigin: "anonymous",
203 |
204 | setCrossOrigin: function(value) {
205 | this.crossOrigin = value;
206 | return this;
207 | },
208 |
209 | setManager: function(value) {
210 | this.manager = value;
211 | },
212 |
213 | setMaterials: function(materialsInfo) {
214 | this.materialsInfo = this.convert(materialsInfo);
215 | this.materials = {};
216 | this.materialsArray = [];
217 | this.nameLookup = {};
218 | },
219 |
220 | convert: function(materialsInfo) {
221 | if (!this.options) return materialsInfo;
222 |
223 | var converted = {};
224 |
225 | for (var mn in materialsInfo) {
226 | // Convert materials info into normalized form based on options
227 |
228 | var mat = materialsInfo[mn];
229 |
230 | var covmat = {};
231 |
232 | converted[mn] = covmat;
233 |
234 | for (var prop in mat) {
235 | var save = true;
236 | var value = mat[prop];
237 | var lprop = prop.toLowerCase();
238 |
239 | switch (lprop) {
240 | case "kd":
241 | case "ka":
242 | case "ks":
243 | // Diffuse color (color under white light) using RGB values
244 |
245 | if (this.options && this.options.normalizeRGB) {
246 | value = [
247 | value[0] / 255,
248 | value[1] / 255,
249 | value[2] / 255
250 | ];
251 | }
252 |
253 | if (this.options && this.options.ignoreZeroRGBs) {
254 | if (
255 | value[0] === 0 &&
256 | value[1] === 0 &&
257 | value[2] === 0
258 | ) {
259 | // ignore
260 |
261 | save = false;
262 | }
263 | }
264 |
265 | break;
266 |
267 | default:
268 | break;
269 | }
270 |
271 | if (save) {
272 | covmat[lprop] = value;
273 | }
274 | }
275 | }
276 |
277 | return converted;
278 | },
279 |
280 | preload: function() {
281 | for (var mn in this.materialsInfo) {
282 | this.create(mn);
283 | }
284 | },
285 |
286 | getIndex: function(materialName) {
287 | return this.nameLookup[materialName];
288 | },
289 |
290 | getAsArray: function() {
291 | var index = 0;
292 |
293 | for (var mn in this.materialsInfo) {
294 | this.materialsArray[index] = this.create(mn);
295 | this.nameLookup[mn] = index;
296 | index++;
297 | }
298 |
299 | return this.materialsArray;
300 | },
301 |
302 | create: function(materialName) {
303 | if (this.materials[materialName] === undefined) {
304 | this.createMaterial_(materialName);
305 | }
306 |
307 | return this.materials[materialName];
308 | },
309 |
310 | createMaterial_: function(materialName) {
311 | // Create material
312 |
313 | var scope = this;
314 | var mat = this.materialsInfo[materialName];
315 | var params = {
316 | name: materialName,
317 | side: this.side
318 | };
319 |
320 | function resolveURL(baseUrl, url) {
321 | if (typeof url !== "string" || url === "") return "";
322 |
323 | // Absolute URL
324 | if (/^https?:\/\//i.test(url)) return url;
325 |
326 | return baseUrl + url;
327 | }
328 |
329 | function setMapForType(mapType, value) {
330 | if (params[mapType]) return; // Keep the first encountered texture
331 |
332 | var texParams = scope.getTextureParams(value, params);
333 | var map = scope.loadTexture(
334 | resolveURL(scope.baseUrl, texParams.url)
335 | );
336 |
337 | map.repeat.copy(texParams.scale);
338 | map.offset.copy(texParams.offset);
339 |
340 | map.wrapS = scope.wrap;
341 | map.wrapT = scope.wrap;
342 |
343 | params[mapType] = map;
344 | }
345 |
346 | for (var prop in mat) {
347 | var value = mat[prop];
348 | var n;
349 |
350 | if (value === "") continue;
351 |
352 | switch (prop.toLowerCase()) {
353 | // Ns is material specular exponent
354 |
355 | case "kd":
356 | // Diffuse color (color under white light) using RGB values
357 |
358 | params.color = new THREE.Color().fromArray(value);
359 |
360 | break;
361 |
362 | case "ks":
363 | // Specular color (color when light is reflected from shiny surface) using RGB values
364 | // params.specular = new THREE.Color().fromArray(value);
365 |
366 | break;
367 |
368 | case "ke":
369 | // Emissive using RGB values
370 | params.emissive = new THREE.Color().fromArray(value);
371 |
372 | break;
373 |
374 | case "map_kd":
375 | // Diffuse texture map
376 |
377 | setMapForType("map", value);
378 |
379 | break;
380 |
381 | case "map_ks":
382 | // Specular map
383 |
384 | setMapForType("specularMap", value);
385 |
386 | break;
387 |
388 | case "map_ke":
389 | // Emissive map
390 |
391 | setMapForType("emissiveMap", value);
392 |
393 | break;
394 |
395 | case "norm":
396 | setMapForType("normalMap", value);
397 |
398 | break;
399 |
400 | case "map_bump":
401 | case "bump":
402 | // Bump texture map
403 |
404 | setMapForType("bumpMap", value);
405 |
406 | break;
407 |
408 | case "map_d":
409 | // Alpha map
410 |
411 | setMapForType("alphaMap", value);
412 | params.transparent = true;
413 |
414 | break;
415 |
416 | case "ns":
417 | // The specular exponent (defines the focus of the specular highlight)
418 | // A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000.
419 |
420 | params.reflectivity = parseFloat(value) / 100;
421 |
422 | break;
423 |
424 | case "d":
425 | n = parseFloat(value);
426 |
427 | if (n < 1) {
428 | params.opacity = n;
429 | params.transparent = true;
430 | }
431 |
432 | break;
433 |
434 | case "tr":
435 | n = parseFloat(value);
436 |
437 | if (this.options && this.options.invertTrProperty)
438 | n = 1 - n;
439 |
440 | if (n > 0) {
441 | params.opacity = 1 - n;
442 | params.transparent = true;
443 | }
444 |
445 | break;
446 |
447 | default:
448 | break;
449 | }
450 | }
451 |
452 | this.materials[materialName] = new THREE.MeshPhysicalMaterial(params);
453 | return this.materials[materialName];
454 | },
455 |
456 | getTextureParams: function(value, matParams) {
457 | var texParams = {
458 | scale: new THREE.Vector2(1, 1),
459 | offset: new THREE.Vector2(0, 0)
460 | };
461 |
462 | var items = value.split(/\s+/);
463 | var pos;
464 |
465 | pos = items.indexOf("-bm");
466 |
467 | if (pos >= 0) {
468 | matParams.bumpScale = parseFloat(items[pos + 1]);
469 | items.splice(pos, 2);
470 | }
471 |
472 | pos = items.indexOf("-s");
473 |
474 | if (pos >= 0) {
475 | texParams.scale.set(
476 | parseFloat(items[pos + 1]),
477 | parseFloat(items[pos + 2])
478 | );
479 | items.splice(pos, 4); // we expect 3 parameters here!
480 | }
481 |
482 | pos = items.indexOf("-o");
483 |
484 | if (pos >= 0) {
485 | texParams.offset.set(
486 | parseFloat(items[pos + 1]),
487 | parseFloat(items[pos + 2])
488 | );
489 | items.splice(pos, 4); // we expect 3 parameters here!
490 | }
491 |
492 | texParams.url = items.join(" ").trim();
493 | return texParams;
494 | },
495 |
496 | loadTexture: function(url, mapping, onLoad, onProgress, onError) {
497 | var texture;
498 | var loader = THREE.Loader.Handlers.get(url);
499 | var manager =
500 | this.manager !== undefined
501 | ? this.manager
502 | : THREE.DefaultLoadingManager;
503 |
504 | if (loader === null) {
505 | loader = new THREE.TextureLoader(manager);
506 | }
507 |
508 | if (loader.setCrossOrigin) loader.setCrossOrigin(this.crossOrigin);
509 | texture = loader.load(url, onLoad, onProgress, onError);
510 |
511 | if (mapping !== undefined) texture.mapping = mapping;
512 |
513 | return texture;
514 | }
515 | };
516 |
--------------------------------------------------------------------------------
/src/app.js:
--------------------------------------------------------------------------------
1 | import STATES from "./states.js";
2 | import keyCodes from "./keycodes.js";
3 | import consoleCanvas from "./console-canvas.js";
4 | import * as cmds from "./cmds.js";
5 | import config from "./config.js";
6 |
7 | // create some handy aliases for keycodes, for use with Vue's v-on directive.
8 | Vue.config.keyCodes = {
9 | enter: keyCodes.enter
10 | };
11 |
12 | let ctrl_down = false;
13 |
14 | /**
15 | * @param {Number} kc the keyCode of the key pressed
16 | * @param {Array} leftChars the character to the left of the cursor, used to
17 | * determine whether left arrow is valid (left arrow can't cross over a
18 | * newline)
19 | */
20 | function validKeycode(ev, leftChars, state) {
21 | const kc = ev.keyCode;
22 |
23 | // if ctrl is held down, ignore everything
24 | if (kc == keyCodes.ctrl) {
25 | ctrl_down = true;
26 | }
27 |
28 | if (ctrl_down) {
29 | return false;
30 | }
31 |
32 | // valid keys are alpha, numeric, punctuation, underscore, hyphen, enter, and right-arrow.
33 | // left-arrow and backspace areonly accepted when they doesn't cross over a newline
34 | // (ie, would have made the cursor to up one line).
35 | const alphanumeric =
36 | _.inRange(kc, keyCodes.nums.start, keyCodes.nums.end + 1) ||
37 | _.inRange(kc, keyCodes.alpha.start, keyCodes.alpha.end + 1) ||
38 | _.inRange(kc, keyCodes.punct.start, keyCodes.punct.end + 1);
39 |
40 | const valid_other = [keyCodes.enter, keyCodes.right_arrow].includes(kc);
41 |
42 | const on_newline = leftChars[0] === "\n";
43 | const on_prompt = leftChars.reverse().join("") === "\n> ";
44 | const valid_backspace =
45 | kc === keyCodes.backspace && !(on_newline || on_prompt);
46 |
47 | // Allow spaces when people enter their name on high score list
48 | const valid_space = kc === keyCodes.space && state === STATES.highscore;
49 |
50 | return alphanumeric || valid_other || valid_backspace || valid_space;
51 | }
52 |
53 | const app = new Vue({
54 | el: "#game",
55 | data: {
56 | state: STATES.loading,
57 | isMobile: isMobile.any,
58 | showTitle: false,
59 | showScore: false,
60 | cmd: "",
61 | typingPosition: 0,
62 | displayCmd: "",
63 | commands: [],
64 | displayScore: false,
65 | gameDuration: config.GAME_DURATION,
66 | timer: 0,
67 | allowTyping: false,
68 | score: 0,
69 | count: {
70 | js: 0,
71 | bash: 0,
72 | html: 0,
73 | py: 0,
74 | recentValidCharacters: 0,
75 | totalValidCharacters: 0,
76 | totalValidCommands: 0
77 | }
78 | },
79 | watch: {
80 | displayCmd: function(val, oldVal) {
81 | // if receiving user input and on a newline, add a prompt to the main cmd
82 | if (this.allowTyping && val[val.length - 1] === "\n") {
83 | this.cmd += "> ";
84 | }
85 | },
86 | cmd: function(val, oldVal) {
87 | // if typing is enabled, copy this directly into displayCmd and update typingPosition
88 | if (this.allowTyping) {
89 | this.displayCmd = _.clone(this.cmd);
90 | this.typingPosition = this.cmd.length;
91 | }
92 | // if the screen was blanked out, reset typing position
93 | if (!this.cmd.includes(oldVal)) {
94 | this.typingPosition = 0;
95 | }
96 | }
97 | },
98 | methods: {
99 | toState: function(state) {
100 | const change = { from: this.state, to: state };
101 | this.state = state;
102 | this.titleState = state === STATES.title;
103 | this.onStateChange(change);
104 | },
105 | handlePaste: function(ev) {
106 | // disable pasting into the textarea
107 | ev.preventDefault();
108 | },
109 | // this keypress handler can be overridden and changed based on the state of the game.
110 | onKeyPress: _.noop,
111 | // this keypress handler is the primary one which controls interaction with the textarea.
112 | handleKeypress: function(ev) {
113 | // give onKeyPress first crack at this event
114 | this.onKeyPress(ev);
115 |
116 | if (!this.allowTyping) {
117 | ev.preventDefault();
118 | return;
119 | }
120 | // first get the char to the left of the cursor (it's used when
121 | // left arrow is pressed to determine if left arrow is valid; left
122 | // arrow is valid except when it would cross over a newline and
123 | // move the cursor to the line above)
124 | const textarea = this.$el.querySelector("#cmd");
125 | const leftChars = [
126 | this.cmd[textarea.selectionStart - 1],
127 | this.cmd[textarea.selectionStart - 2],
128 | this.cmd[textarea.selectionStart - 3]
129 | ];
130 |
131 | // if it's enter, test the input and return. also, preventDefault
132 | // so enter doesn't add a newline. Instead, add the newline
133 | // ourselves. This prevents Enter from splitting a word in half if
134 | // the cursor is inside a word, like hitting enter on "ca|t" would
135 | // result in "ca\nt".
136 | if (ev.keyCode === Vue.config.keyCodes.enter) {
137 | ev.preventDefault();
138 | const result = this.testCmd(ev);
139 | result.lang.forEach(lang => app.count[lang]++);
140 |
141 | if (result.cmd.length !== 0) {
142 | // scroll to bottom of the textarea
143 | // gameplay, it just makes the textarea look nicer when the
144 | // textarea itself is visible during debugging)
145 | this.$nextTick(() => {
146 | textarea.blur();
147 | textarea.focus();
148 | });
149 | }
150 | return;
151 | }
152 |
153 | // if keycode is invalid, drop the event.
154 | if (!validKeycode(ev, leftChars, this.state)) {
155 | ev.preventDefault();
156 | }
157 | },
158 | handleKeyup: function(ev) {
159 | if (ev.keyCode === keyCodes.ctrl) {
160 | ctrl_down = false;
161 | }
162 | },
163 | testCmd: function(ev) {
164 | const cmd = _(this.cmd)
165 | .split("\n")
166 | .last()
167 | .trim()
168 | .replace(/^\> /, ""); // ignore the prompt
169 | const { cmd: matchedCmd, lang } = cmds.find(cmd);
170 | const result = { cmd, valid: !!matchedCmd, matchedCmd, lang };
171 | this.$nextTick(() => {
172 | this.onResult(result);
173 | });
174 | return result;
175 | },
176 | onResult: _.noop,
177 | onStateChange: function(change) {
178 | console.log(
179 | `state changing from "${change.from}" to "${
180 | change.to
181 | }" but no handler is registered.`
182 | );
183 | },
184 |
185 | /**
186 | * This function returns a json object with the set of golden command for this game
187 | */
188 | pickGoldenCommands: function() {
189 | // General rules for golden commands
190 | // 1. 10 char or less
191 | // 2. don't start with _
192 | // 3. don't end with ()
193 | // 4. Pull from a list of well known commands for each lang
194 | // 5. pick configurable amount of commands from each language type that meet the above rules
195 |
196 | const filterCmds = function(cmds) {
197 | // filter by length
198 | let filteredCmds = cmds.filter(
199 | cmd => cmd.length <= config.GOLDEN_CMDS_MAX_LENGTH
200 | );
201 |
202 | // Filter out starting with underscore
203 | filteredCmds = filteredCmds.filter(cmd => !cmd.startsWith("_"));
204 |
205 | // Filter out ending with parens )
206 | filteredCmds = filteredCmds.filter(cmd => !cmd.endsWith(")"));
207 |
208 | return filteredCmds;
209 | };
210 |
211 | let bashAll = filterCmds(cmds.cmdsByLang.bash.cmds);
212 | let bashCommon = cmds.cmdsByLang.bash.commonCmds;
213 | let jsAll = filterCmds(cmds.cmdsByLang.js.cmds);
214 | let jsCommon = cmds.cmdsByLang.js.commonCmds;
215 | let pyAll = filterCmds(cmds.cmdsByLang.py.cmds);
216 | let pyCommon = cmds.cmdsByLang.py.commonCmds;
217 | let htmlAll = filterCmds(cmds.cmdsByLang.html.cmds);
218 | let htmlCommon = filterCmds(cmds.cmdsByLang.html.commonCmds);
219 |
220 | let cn = config.GOLDEN_CMDS_COMMON_PER_LANG;
221 | let rn = config.GOLDEN_CMDS_RANDOM_PER_LANG;
222 |
223 | let goldenCommands = {
224 | bash: _.sampleSize(bashCommon, cn).concat(
225 | _.sampleSize(_.xor(bashCommon, bashAll), rn)
226 | ),
227 | js: _.sampleSize(jsCommon, cn).concat(
228 | _.sampleSize(_.xor(jsCommon, jsAll), rn)
229 | ),
230 | py: _.sampleSize(pyCommon, cn).concat(
231 | _.sampleSize(_.xor(pyCommon, pyAll), rn)
232 | ),
233 | html: _.sampleSize(htmlCommon, cn).concat(
234 | _.sampleSize(_.xor(htmlCommon, htmlAll), rn)
235 | )
236 | };
237 | goldenCommands.all = goldenCommands.bash.concat(
238 | goldenCommands.js,
239 | goldenCommands.py,
240 | goldenCommands.html
241 | );
242 |
243 | return goldenCommands;
244 | },
245 | /**
246 | * Get the golden commands for the console canvas.
247 | */
248 | printGoldenCommands: function() {
249 | let out = "";
250 |
251 | const halfScreen = Math.floor(
252 | consoleCanvas.conf.PLAY_CHARS_PER_LINE / 2
253 | );
254 | const goldCmds = app.goldenCommands;
255 | const langs = _.keys(goldCmds);
256 |
257 | // title of first and second langs
258 | out += cmds.bash().name.padEnd(halfScreen);
259 | out += cmds.js().name + "\n";
260 |
261 | // interleave commands of first and second langs
262 | out += _.zip(
263 | goldCmds.bash.map(c => ` - ${c}`.padEnd(halfScreen)),
264 | goldCmds.js.map(c => `${` - ${c}`.padEnd(halfScreen)}\n`)
265 | )
266 | .map(cs => cs.join(""))
267 | .join("");
268 |
269 | out += "\n";
270 |
271 | // title of third and fourth langs
272 | out += cmds
273 | .py()
274 | .name.padEnd(
275 | Math.floor(consoleCanvas.conf.PLAY_CHARS_PER_LINE / 2)
276 | );
277 | out += cmds.html().name + "\n";
278 |
279 | // interleave commands of third and fourth langs
280 | out += _.zip(
281 | goldCmds.py.map(c => ` - ${c}`.padEnd(halfScreen)),
282 | goldCmds.html.map(c => `${` - ${c}`.padEnd(halfScreen)}\n`)
283 | )
284 | .map(cs => cs.join(""))
285 | .join("");
286 |
287 | return out;
288 | },
289 | printHighScores: function(leaders) {
290 | if (leaders.isEmpty) {
291 | return "";
292 | }
293 |
294 | let out = "";
295 |
296 | // Only display the top 10 leaders
297 | leaders = leaders.slice(0, 10);
298 |
299 | // inject headings
300 | let leaderContent = _.concat(
301 | { name: "NAME", score: "SCORE", tribe: "TRIBE" },
302 | { name: "----", score: "-----", tribe: "-----" },
303 | leaders
304 | );
305 |
306 | let longestScoreLength = leaders[0].score.toString().length;
307 | let longestTribeLength = _(leaderContent)
308 | .map("tribe")
309 | .maxBy(n => n.length).length;
310 |
311 | leaderContent.forEach(leader => {
312 | // pad for column formatting
313 | let score = leader.score
314 | .toString()
315 | .padEnd(longestScoreLength + 1);
316 | let tribe = leader.tribe.padEnd(longestTribeLength + 1);
317 |
318 | out += `${score} ${tribe} ${leader.name}\n`;
319 | });
320 |
321 | return out;
322 | },
323 | updateConsole: _.noop,
324 | writeToConsole: function() {
325 | this.$nextTick(() => {
326 | let args = [_.clone(this.displayCmd)];
327 | const showCursor =
328 | this.allowTyping && performance.now() % 1200 < 600;
329 | if (showCursor) {
330 | args[0] += "█";
331 | }
332 | if (this.showScore) {
333 | args.push(this.score);
334 | args.push(this.timer);
335 | }
336 | consoleCanvas.write(...args);
337 | });
338 | },
339 | typingLoop: function() {
340 | let delay = this.typingTimeChar(
341 | this.displayCmd[this.displayCmd - 1]
342 | );
343 |
344 | if (!this.allowTyping) {
345 | this.displayCmd = this.cmd.substr(0, this.typingPosition);
346 | }
347 | // increment typing position but don't exceed the length of cmd
348 | this.typingPosition = Math.min(
349 | this.typingPosition + 1,
350 | this.cmd.length
351 | );
352 |
353 | setTimeout(this.typingLoop, delay);
354 | },
355 | // how long will it take to display the given character
356 | typingTimeChar: function(str) {
357 | let delay = config.CHAR_APPEAR_DELAY;
358 |
359 | if (/\s/.test(this.displayCmd[this.displayCmd.length - 1])) {
360 | delay /= 10;
361 | }
362 |
363 | return delay;
364 | },
365 | // how long will it take to display the given string
366 | typingTime: function(str) {
367 | return (
368 | config.CHAR_APPEAR_DELAY * str.replace(/\S/g, "").length +
369 | (config.CHAR_APPEAR_DELAY / 10) * str.replace(/\s/g, "").length
370 | );
371 | },
372 | resetState: function() {
373 | // Reset the score and other stat between games:
374 | this.timer = 0;
375 | this.allowTyping = false;
376 | this.score = 0;
377 | this.count.js = 0;
378 | this.count.bash = 0;
379 | this.count.html = 0;
380 | this.count.py = 0;
381 | this.count.recentValidCharacters = 0;
382 | this.count.totalValidCharacters = 0;
383 | this.count.totalValidCommands = 0;
384 | }
385 | },
386 | mounted: function() {
387 | // after the entire view has rendered
388 | this.$nextTick(function() {
389 | // put focus on the text input
390 | this.$refs.cmd.focus();
391 | // and also refocus on the input if the user clicks anywhere with
392 | // the mouse
393 | document.body.addEventListener("click", () =>
394 | this.$refs.cmd.focus()
395 | );
396 | });
397 | }
398 | });
399 |
400 | window.app = app;
401 |
402 | export default app;
403 |
--------------------------------------------------------------------------------
/src/cmds.js:
--------------------------------------------------------------------------------
1 | // API for interrogating the command "database"
2 |
3 | import bashCmds from "../assets/cmds/bash.js";
4 | import jsCmds from "../assets/cmds/js.js";
5 | import pyCmds from "../assets/cmds/python.js";
6 | import htmlCmds from "../assets/cmds/html.js";
7 |
8 | const allCmds = _.union(
9 | bash().cmds,
10 | js().cmds,
11 | py().cmds,
12 | html().cmds /* and other langs as needed */
13 | );
14 |
15 | export const cmdsByLang = {
16 | bash: bash(),
17 | js: js(),
18 | py: py(),
19 | html: html()
20 | };
21 |
22 | export function all() {
23 | return allCmds;
24 | }
25 |
26 | export function bash() {
27 | return bashCmds;
28 | }
29 |
30 | export function js() {
31 | return jsCmds;
32 | }
33 |
34 | export function py() {
35 | return pyCmds;
36 | }
37 |
38 | export function html() {
39 | return htmlCmds;
40 | }
41 |
42 | export function longest() {
43 | return allCmds.reduce(function(a, b) {
44 | return a.length > b.length ? a : b;
45 | }).length;
46 | }
47 |
48 | export function find(cmd) {
49 | const result = {
50 | lang: []
51 | };
52 | for (let lang in cmdsByLang) {
53 | if (cmdsByLang[lang].cmds.includes(cmd.trim())) {
54 | result.cmd = cmd;
55 | result.lang.push(lang);
56 | }
57 | }
58 | return result;
59 | }
60 |
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | PARSE_URL: "http://localhost:1337/parse",
3 | PARSE_APPID: "CLH",
4 | LEADERBOARD_NAMESPACE_DEFAULT: "leaderboard",
5 |
6 | GOLDEN_CMDS_COMMON_PER_LANG: 2, // Number of very common commands to include
7 | GOLDEN_CMDS_RANDOM_PER_LANG: 1, // Number of totally random commands to include per language
8 | GOLDEN_CMDS_MAX_LENGTH: 7, // Max string length that a golden command can be
9 | GOLDEN_CMDS_PREVIEW_TIME: 21300,
10 | SCORE_PER_COMMAND: 10,
11 | SCORE_OVERALL_MULTIPLIER: 100,
12 | SCORE_GOLDEN_COMMAND_MULTIPLIER: 10,
13 | GAME_DURATION: 60000,
14 | CHAR_APPEAR_DELAY: 60, // ms between characters appearing on screen
15 | MAX_LEADER_NAME_LENGTH: 20, // max length of names on leaderboard
16 |
17 | // General Fire settings
18 | FIRE_DELAY_BEFORE: 5000, // Minimum time the game has be be running before fire can display,
19 | FIRE_CHECK_INTERVAL: 5000, // how often to check if fire should be turned up
20 | FIRE_CPS_THRESHOLD: 1.75, // Number of valid characters per-second a player must average to get fire
21 | FIRE_STAGE_ZERO: 0,
22 | FIRE_STAGE_ONE: 1,
23 | FIRE_STAGE_TWO: 2,
24 | FIRE_STAGE_THREE: 3,
25 | FIRE_STAGE_TWEEN_TIME: 2000, // how long to tween between fire stages
26 |
27 | // Default Fire properties for medium to high
28 | FIRE_COLOR_1: 0xf7cf78,
29 | FIRE_COLOR_2: 0xef702b,
30 | FIRE_COLOR_3: 0xf7a060,
31 | FIRE_WIND_VECTOR_Y: -0.25,
32 | FIRE_COLOR_BIAS: 0.25,
33 | FIRE_BURN_RATE: 2.6,
34 | FIRE_DIFFUSE: 5,
35 | FIRE_VISCOSITY: 0.5,
36 | FIRE_EXPANSION: 0.75,
37 | FIRE_SWIRL: 30,
38 | FIRE_DRAG: 0.0,
39 | FIRE_AIR_SPEED: 40.0,
40 | FIRE_SPEED: 500.0,
41 | FIRE_STAGE_ONE_SCALE: { x: 0.5, y: 0.5, z: 1 },
42 | FIRE_STAGE_TWO_SCALE: { x: 0.7, y: 0.7, z: 1 },
43 | FIRE_STAGE_THREE_SCALE: { x: 1, y: 1, z: 1 },
44 |
45 | // Fire Settings low FPS clients
46 | FIRE_LOW_FPS_COLOR_1: 0xf7cf78,
47 | FIRE_LOW_FPS_COLOR_2: 0xef702b,
48 | FIRE_LOW_FPS_COLOR_3: 0x420059,
49 | FIRE_LOW_FPS_WIND_VECTOR_Y: 0.4,
50 | FIRE_LOW_FPS_COLOR_BIAS: 0.1,
51 | FIRE_LOW_FPS_BURN_RATE: 5,
52 | FIRE_LOW_FPS_DIFFUSE: 5.0,
53 | FIRE_LOW_FPS_VISCOSITY: 0.5,
54 | FIRE_LOW_FPS_EXPANSION: 0.75,
55 | FIRE_LOW_FPS_SWIRL: 30.0,
56 | FIRE_LOW_FPS_DRAG: 0.0,
57 | FIRE_LOW_FPS_AIR_SPEED: 20.0,
58 | FIRE_LOW_FPS_SPEED: 200.0,
59 | FIRE_LOW_FPS_STAGE_ONE_SCALE: { x: 0.8, y: 0.8, z: 1 },
60 | FIRE_LOW_FPS_STAGE_TWO_SCALE: { x: 1.1, y: 1.1, z: 1 },
61 | FIRE_LOW_FPS_STAGE_THREE_SCALE: { x: 1.4, y: 1.3, z: 1 },
62 |
63 | MAX_FRAME_TIME: 1000 / 30, // 30 FPS
64 | MAX_SLOW_FRAMES: 300
65 | };
66 |
--------------------------------------------------------------------------------
/src/console-canvas.js:
--------------------------------------------------------------------------------
1 | import palette from "./palette.js";
2 |
3 | class ConsoleCanvas {
4 | constructor() {
5 | this.conf = {
6 | WIDTH: 2 * 2048,
7 | HEIGHT: 2 * 2048,
8 | ASPECT: 0.7222,
9 | PAD_LEFT: 4 * 54,
10 | PAD_BOTTOM: 4 * 82,
11 | FONT_SIZE: 4 * 64, // px
12 | FONT_FAM: "overpass-mono",
13 | FONT_WEIGHT: "bold",
14 | LINE_SPACING: 4 * 14, // px
15 | PLAY_CHARS_PER_LINE: 44 // this will need to change if the font size in play mode changes
16 | };
17 |
18 | // find the maximum number of lines of text that can be drawn (to avoid
19 | // performance problems if there are hundreds of thousands of lines
20 | this.conf.MAX_LINES = Math.ceil(
21 | this.conf.WIDTH / (this.conf.FONT_SIZE + this.conf.LINE_SPACING)
22 | );
23 |
24 | console.log(`maximum possible display lines: ${this.conf.MAX_LINES}`);
25 |
26 | // text on the screen
27 |
28 | let text = "";
29 |
30 | // set up canvas element
31 |
32 | this.canvas = document.createElement("canvas");
33 | this.canvas.width = this.conf.WIDTH;
34 | this.canvas.height = this.conf.HEIGHT;
35 | this.canvas.id = "console-canvas";
36 |
37 | // set up canvas drawing context
38 | this.ctx = this.canvas.getContext("2d");
39 |
40 | // scale the canvas pixel sizes so that the square canvas (must be sized to
41 | // powers of two) get scaled to the correct ratio for the 3D computer screen's
42 | // size.
43 |
44 | this.ctx.scale(this.conf.ASPECT, 1);
45 |
46 | document.body.appendChild(this.canvas);
47 | }
48 |
49 | /**
50 | * Write text onto the screen. Also draws the score. If you don't want the
51 | * score to appear at the top-right, pass in score `false` (for instance,
52 | * on the title screen or leaderboard screen).
53 | */
54 | write(text, score = false, timer = false) {
55 | this.ctx.font = `${this.conf.FONT_WEIGHT} ${this.conf.FONT_SIZE}px ${
56 | this.conf.FONT_FAM
57 | }`;
58 | this.ctx.fillStyle = palette.black;
59 | this.ctx.fillRect(
60 | 0,
61 | 0,
62 | this.canvas.width / this.conf.ASPECT,
63 | this.canvas.height
64 | );
65 | this.ctx.fillStyle = palette.yellow_light;
66 |
67 | // fillText doesn't do multi-line, so split the text and call fill text
68 | // multiple times
69 | let y_offset = 0;
70 | let line_count = 0;
71 | for (let line of text.split("\n").reverse()) {
72 | this.ctx.fillText(
73 | line,
74 | this.conf.PAD_LEFT,
75 | this.canvas.height - this.conf.PAD_BOTTOM - y_offset
76 | );
77 | y_offset += this.conf.FONT_SIZE + this.conf.LINE_SPACING;
78 |
79 | // break if we've drawn the max number of display lines
80 | line_count += 1;
81 | if (line_count > this.conf.MAX_LINES) break;
82 | }
83 |
84 | // black out the top line whenever score or timer is being displayed
85 | if (score !== false || timer !== false) {
86 | this.ctx.fillStyle = palette.black;
87 | this.ctx.fillRect(
88 | 0,
89 | 0,
90 | this.canvas.width * 2,
91 | this.conf.FONT_SIZE * 2
92 | );
93 | // this.ctx.fillRect(0, 0, 1000, 1000);
94 | this.ctx.fillStyle = palette.yellow_light;
95 | }
96 |
97 | // draw score and time remaining
98 | if (score !== false) {
99 | this.ctx.fillText(
100 | `score: ${score}`,
101 | this.conf.PAD_LEFT,
102 | this.conf.PAD_BOTTOM
103 | );
104 | }
105 | if (timer !== false) {
106 | this.ctx.fillText(
107 | `timer: ${timer}`,
108 | this.canvas.width - this.conf.PAD_LEFT,
109 | this.conf.PAD_BOTTOM
110 | );
111 | }
112 | }
113 | }
114 |
115 | const consoleCanvas = new ConsoleCanvas();
116 | window.consoleCanvas = consoleCanvas;
117 | export default consoleCanvas;
118 |
--------------------------------------------------------------------------------
/src/keycodes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Useful keycodes!
3 | */
4 | export default {
5 | // TODO: number row punctuation is included in one of these ranges.
6 | nums: { start: 48, end: 57 },
7 | alpha: { start: 65, end: 90 },
8 | punct: { start: 186, end: 222 },
9 | enter: 13,
10 | left_arrow: 37,
11 | right_arrow: 39,
12 | backspace: 8,
13 | ctrl: 17,
14 | space: 32
15 | };
16 |
--------------------------------------------------------------------------------
/src/leaderboard.js:
--------------------------------------------------------------------------------
1 | import config from "./config.js";
2 |
3 | const STORAGE_TYPES = {
4 | local: 0,
5 | parse: 1
6 | };
7 |
8 | const state = {
9 | storage: STORAGE_TYPES.local,
10 | name: config.LEADERBOARD_NAMESPACE_DEFAULT
11 | };
12 |
13 | function init() {
14 | const urlParams = new URLSearchParams(window.location.search);
15 |
16 | // handle the leaderboard namespace param
17 | if (urlParams.has("name")) {
18 | const qsName = urlParams.get("name");
19 | console.assert(
20 | qsName.trim().length,
21 | "invalid ?name value: must not be empty"
22 | );
23 | state.name = qsName;
24 | }
25 |
26 | // handle the leaderboard storage param
27 | if (urlParams.has("storage")) {
28 | const qsStorage = urlParams.get("storage");
29 | console.assert(
30 | STORAGE_TYPES.hasOwnProperty(qsStorage),
31 | `invalid ?storage value provided, must be one of: ${Object.keys(
32 | STORAGE_TYPES
33 | )}`
34 | );
35 | state.storage = STORAGE_TYPES[qsStorage];
36 | }
37 | }
38 |
39 | async function record({ name, score, tribe }) {
40 | if (state.storage == STORAGE_TYPES.local) {
41 | return await recordLocal({ name, score, tribe });
42 | } else if (state.storage == STORAGE_TYPES.parse) {
43 | return await recordParse({ name, score, tribe });
44 | }
45 | }
46 |
47 | async function recordLocal({ name, score, tribe }) {
48 | console.log(`recording leaderboard entry in localstorage`, {
49 | name,
50 | score,
51 | tribe
52 | });
53 |
54 | const leaders = JSON.parse(localStorage.getItem(state.name)) || [];
55 | leaders.push({
56 | name: name,
57 | score: app.score,
58 | tribe: tribe
59 | });
60 | localStorage.setItem(state.name, JSON.stringify(leaders));
61 | }
62 |
63 | async function recordParse({ name, score, tribe }) {
64 | console.log(`recording leaderboard entry in parse`, { name, score, tribe });
65 |
66 | const response = await fetch(`${config.PARSE_URL}/classes/${state.name}`, {
67 | method: "POST",
68 | headers: {
69 | "X-Parse-Application-Id": config.PARSE_APPID,
70 | "Content-Type": "application/json"
71 | },
72 | body: JSON.stringify({ name, score, tribe })
73 | });
74 |
75 | const responseJson = await response.json();
76 | }
77 |
78 | async function get() {
79 | if (state.storage == STORAGE_TYPES.local) {
80 | return await getLocal();
81 | } else if (state.storage == STORAGE_TYPES.parse) {
82 | return await getParse();
83 | }
84 | }
85 |
86 | async function getLocal() {
87 | // First get the current scores from localStorage
88 | let leaders = JSON.parse(localStorage.getItem(state.name));
89 | return formatLeaders(leaders);
90 | }
91 |
92 | async function getParse() {
93 | const response = await fetch(
94 | `${config.PARSE_URL}/classes/${state.name}?limit=10000`,
95 | {
96 | method: "GET",
97 | headers: {
98 | "X-Parse-Application-Id": config.PARSE_APPID
99 | }
100 | }
101 | );
102 |
103 | const scores = await response.json();
104 |
105 | return formatLeaders(scores.results);
106 | }
107 |
108 | function formatLeaders(leaders) {
109 | leaders = _.reverse(_.sortBy(leaders, "score"));
110 | const hiScores = _(leaders)
111 | .sortBy("score")
112 | .reverse()
113 | .uniqBy("name")
114 | .take(10)
115 | .map("score")
116 | .value();
117 | const lowestHiScore = _.min(hiScores);
118 | const topHiScore = _.max(hiScores);
119 | const isEmpty = leaders.length === 0;
120 | return {
121 | leaders,
122 | hiScores,
123 | topHiScore,
124 | lowestHiScore,
125 | isEmpty
126 | };
127 | }
128 |
129 | export default {
130 | init,
131 | record,
132 | get
133 | };
134 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import "../node_modules/three/examples/js/loaders/OBJLoader.js";
2 | import "../node_modules/three/examples/js/controls/OrbitControls.js";
3 | import "../node_modules/three/examples/js/controls/TrackballControls.js";
4 | import "./Fire.js";
5 | import "./MTLLoaderPhysical.js";
6 | import app from "./app.js";
7 | import tweenCamera from "./tween-camera.js";
8 | import keyCodes from "./keycodes.js";
9 | import { loadMesh } from "./three-utils.js";
10 | import STATES from "./states.js";
11 | import sleep from "./sleep.js";
12 | import consoleCanvas from "./console-canvas.js";
13 | import config from "./config.js";
14 | import sfx from "./sfx.js";
15 | import leaderboard from "./leaderboard.js";
16 |
17 | let container;
18 | let camera, scene, renderer, controls;
19 | let mouseX = 0,
20 | mouseY = 0;
21 | let windowHalfX = window.innerWidth / 2;
22 | let windowHalfY = window.innerHeight / 2;
23 | let computer;
24 |
25 | // Fire vars
26 | let fire;
27 | let firePlane;
28 | let allowFire = false;
29 |
30 | let leaders;
31 |
32 | // FPS tracking
33 | let stats = new Stats();
34 | // document.body.appendChild(stats.dom);
35 | let t, previousTime;
36 | let slowCount = 0;
37 | let maxSlowFrames = config.MAX_SLOW_FRAMES;
38 | let isLowFPS = false;
39 | window.isLowFPS = isLowFPS;
40 | t = previousTime = performance.now();
41 |
42 | const states = {
43 | [STATES.title]: {
44 | enter: async function() {
45 | app.updateConsole = _.noop;
46 | app.resetState();
47 | app.typingLoop();
48 | app.cmd = "";
49 |
50 | controls.enabled = false;
51 |
52 | slowCount = 0;
53 |
54 | // make font big enough to see from a distance
55 | consoleCanvas.conf.FONT_SIZE = 4 * 114;
56 |
57 | const camTween = tweenCamera(camera, {
58 | rotation: {
59 | x: -0.5832659522477153,
60 | y: 0.4513175431123964,
61 | z: 0.28022041929249414
62 | },
63 | position: {
64 | x: 68.79903504601936,
65 | y: 218.79396932448483,
66 | z: 432.0475129782785
67 | },
68 | duration: 4000
69 | });
70 | // wait a short time so the CLH test pattern can be seen, then start drawing the console
71 | await sleep(300);
72 | app.updateConsole = app.writeToConsole;
73 |
74 | sfx.boot.play();
75 |
76 | // let the camera zoom in for a while before moving on to displaying text on screen
77 | await sleep(1000);
78 |
79 | app.cmd = "LOADING...";
80 | app.cmd += "\n\nTESTING ROUTINE\nINITIATED.";
81 | app.cmd += "\n\nType PLAY";
82 |
83 | await camTween;
84 |
85 | sfx.menuMusic.play();
86 |
87 | app.showTitle = true;
88 |
89 | app.cmd += "\n> ";
90 |
91 | await sleep(app.typingTime(app.cmd));
92 |
93 | app.onResult = async result => {
94 | if (result.cmd.toLowerCase() === "play") {
95 | app.onResult = _.noop();
96 | app.allowTyping = false;
97 | app.showTitle = false;
98 | app.cmd = "";
99 | sfx.boot.fade(sfx.boot.originalVolume, 0, 600);
100 | sfx.menuMusic.fade(sfx.menuMusic.originalVolume, 0, 600);
101 | await sleep(200);
102 | app.toState(STATES.play);
103 | } else if (result.cmd.toLowerCase() === "leaderboard") {
104 | app.onResult = _.noop();
105 | app.allowTyping = false;
106 | app.showTitle = false;
107 | app.cmd = "";
108 | sfx.boot.fade(sfx.boot.originalVolume, 0, 600);
109 | sfx.menuMusic.fade(sfx.menuMusic.originalVolume, 0, 600);
110 | await sleep(200);
111 | app.toState(STATES.leaderboard);
112 | } else {
113 | app.cmd += "\nType PLAY\n";
114 | }
115 | };
116 |
117 | app.allowTyping = true;
118 | }
119 | },
120 | [STATES.play]: {
121 | enter: async function() {
122 | // make font appropriate size for when camera is zoomed in
123 | consoleCanvas.conf.FONT_SIZE = 4 * 48;
124 | controls.enabled = false;
125 |
126 | // log play count
127 | let playCount = localStorage.getItem("clhPlayCount");
128 | playCount++;
129 | localStorage.setItem("clhPlayCount", playCount);
130 |
131 | // wait for Enter to be pressed and then start the countdown
132 | app.onKeyPress = async ev => {
133 | // don't let any other event handlers run
134 | ev.preventDefault();
135 | ev.stopPropagation();
136 |
137 | if (ev.keyCode === keyCodes.enter) {
138 | app.onKeyPress = _.noop;
139 | showGolden();
140 | }
141 | };
142 |
143 | // Keep a record of entered valid commands
144 | let enteredValidCmds = [];
145 |
146 | app.cmd = "\nEntering game...";
147 |
148 | app.goldenCommands = app.pickGoldenCommands();
149 |
150 | // play golden command music
151 | sfx.play.play("golden");
152 |
153 | await tweenCamera(camera, {
154 | rotation: {
155 | x: 0,
156 | y: 0,
157 | z: 0
158 | },
159 | position: {
160 | x: -4.336209717881005,
161 | y: 39.566049707444186,
162 | z: 155.4934617372831
163 | }
164 | });
165 |
166 | app.cmd = `You have ${config.GAME_DURATION /
167 | 1000} seconds to enter ANY
168 | of the following:
169 |
170 | - bash shell commands & linux built-ins
171 | - JavaScript keywords, objects, functions
172 | - Python keywords, objects, functions
173 | - HTML5 tags
174 |
175 | Press Enter to continue.`;
176 |
177 | async function showGolden() {
178 | // wait for Enter to be pressed and then start the countdown
179 | app.onKeyPress = async ev => {
180 | // don't let any other event handlers run
181 | ev.preventDefault();
182 | ev.stopPropagation();
183 |
184 | if (ev.keyCode === keyCodes.enter) {
185 | app.onKeyPress = _.noop;
186 | startPlaying();
187 | }
188 | };
189 |
190 | app.cmd = `\nThese commands are worth ${
191 | config.SCORE_GOLDEN_COMMAND_MULTIPLIER
192 | }x BONUS points:\n\n`;
193 | app.cmd += app.printGoldenCommands();
194 | app.cmd += "\nPress Enter to begin.";
195 | }
196 |
197 | async function startPlaying() {
198 | app.cmd = "\nGet ready to enter commands... ";
199 | await sleep(1000);
200 | let countdown = 3;
201 | while (countdown--) {
202 | app.cmd += `${1 + countdown} `;
203 | sfx.timerRelaxed.play();
204 | await sleep(1000);
205 | }
206 | sfx.timerUrgent.play();
207 |
208 | const blankChars = _.times(
209 | Math.floor(consoleCanvas.conf.PLAY_CHARS_PER_LINE / 2 - 2),
210 | _.constant(" ")
211 | ).join("");
212 | const blankLines = _.times(
213 | Math.floor(consoleCanvas.conf.MAX_LINES / 2),
214 | _.constant("\n")
215 | ).join("");
216 | app.cmd = `${blankChars}TYPE!${blankLines}`;
217 | app.onResult = async result => {
218 | if (
219 | result.valid &&
220 | !enteredValidCmds.includes(result.cmd)
221 | ) {
222 | let cmdScore = config.SCORE_PER_COMMAND;
223 |
224 | app.cmd += ` ✔ [${result.lang.join(" ")}]`;
225 |
226 | // See if the command entered was a golden command
227 | if (app.goldenCommands.all.includes(result.cmd)) {
228 | console.log("GOLDEN COMMAND ENTERED!");
229 | sfx.cmdGold.play();
230 |
231 | // Give BIG bonus for golden commands
232 | cmdScore *= config.SCORE_GOLDEN_COMMAND_MULTIPLIER;
233 | } else {
234 | sfx.cmdGood.play();
235 | }
236 |
237 | // Increase score
238 | app.score +=
239 | (cmdScore + result.cmd.length) *
240 | config.SCORE_OVERALL_MULTIPLIER;
241 |
242 | // Keep log of entered valid commands
243 | enteredValidCmds.push(result.cmd);
244 |
245 | // Valid command increment counters
246 | app.count.totalValidCommands++;
247 | app.count.totalValidCharacters += result.cmd.length;
248 | app.count.recentValidCharacters += result.cmd.length;
249 | } else {
250 | if (
251 | result.valid &&
252 | enteredValidCmds.includes(result.cmd)
253 | ) {
254 | app.cmd += " x [duplicate]";
255 | } else {
256 | app.cmd += " x";
257 | }
258 |
259 | sfx.cmdBad.play();
260 | }
261 |
262 | // if the command submitted is not empty string, add a newline
263 | app.cmd += "\n";
264 |
265 | console.log(
266 | `entered "${result.cmd}"... it's ${
267 | result.valid ? "valid!" : "invalid :("
268 | }`
269 | );
270 | };
271 |
272 | app.allowTyping = true;
273 |
274 | // play gameplay music command music
275 | // sfx.play.fade(1, 0, 600, "golden");
276 | sfx.play.stop();
277 | sfx.play.play("playing");
278 |
279 | // Reset fire delay timer
280 | allowFire = false;
281 | setTimeout(() => (allowFire = true), config.FIRE_DELAY_BEFORE);
282 |
283 | await sleep(1000);
284 |
285 | app.showScore = true;
286 |
287 | app.timer = config.GAME_DURATION / 1000;
288 | const iid = setInterval(() => {
289 | app.timer -= 1;
290 |
291 | // play a sound for the last few seconds of the timer
292 |
293 | if (app.timer <= 10 && app.timer >= 3) {
294 | sfx.timerRelaxed.play();
295 | } else if (app.timer < 3) {
296 | sfx.timerUrgent.play();
297 | }
298 | if (app.timer <= 0) {
299 | clearInterval(iid);
300 | }
301 | }, 1000);
302 |
303 | const fireInterval = setInterval(() => {
304 | // See if we need to turn up the FIRE!
305 | let cps =
306 | app.count.recentValidCharacters /
307 | (config.FIRE_CHECK_INTERVAL / 1000);
308 | console.log(
309 | "CPS:",
310 | cps,
311 | "Recent Valid:",
312 | app.count.recentValidCharacters
313 | );
314 | let stage;
315 |
316 | if (allowFire && cps >= config.FIRE_CPS_THRESHOLD) {
317 | stage = fire.userData.stage + 1;
318 | setFireStage(stage); // go up a stage
319 | } else if (cps < config.FIRE_CPS_THRESHOLD) {
320 | stage = fire.userData.stage - 1;
321 | setFireStage(stage); // go down a stage
322 | }
323 |
324 | console.log("Set fire stage:", stage);
325 |
326 | app.count.recentValidCharacters = 0;
327 |
328 | if (app.timer <= 0) {
329 | clearInterval(fireInterval);
330 | setFireStage(config.FIRE_STAGE_ZERO);
331 | }
332 | }, config.FIRE_CHECK_INTERVAL);
333 |
334 | console.log("starting game timer");
335 | await sleep(app.gameDuration);
336 | console.log("game timer o'er");
337 |
338 | sfx.play.fade(sfx.play.originalVolume, 0, 600);
339 |
340 | app.cmd = "";
341 | app.showScore = false;
342 | app.onResult = _.noop();
343 | app.allowTyping = false;
344 | app.toState(STATES.score);
345 | }
346 | }
347 | },
348 | [STATES.score]: {
349 | enter: async function() {
350 | app.allowTyping = false;
351 |
352 | controls.enabled = true;
353 |
354 | // Make sure fire is off
355 | setFireStage(config.FIRE_STAGE_ZERO);
356 |
357 | // Get current leaders
358 | leaders = await leaderboard.get();
359 |
360 | await tweenCamera(camera, {
361 | rotation: {
362 | x: 0,
363 | y: 0,
364 | z: 0
365 | },
366 | position: {
367 | x: -4.336209717881005,
368 | y: 39.566049707444186,
369 | z: 255.4934617372831
370 | }
371 | });
372 |
373 | await sleep(500);
374 |
375 | // make font appropriate size for when camera is zoomed in
376 | consoleCanvas.conf.FONT_SIZE = 4 * 90;
377 | app.cmd = "THANKS FOR PLAYING!\n\n";
378 | app.cmd += `SCORE ${app.score}\n`;
379 | app.cmd += `BASH ${app.count.bash}\n`;
380 | app.cmd += `PYTHON ${app.count.py}\n`;
381 | app.cmd += `JAVASCRIPT ${app.count.js}\n`;
382 | app.cmd += `HTML5 ${app.count.html}\n`;
383 |
384 | app.cmd += `\nPress Enter to continue.`;
385 |
386 | // when any key is pressed, go back to the title screen
387 | app.onKeyPress = async ev => {
388 | // don't let any other event handlers run
389 | ev.preventDefault();
390 | ev.stopPropagation();
391 |
392 | if (ev.keyCode === keyCodes.enter) {
393 | app.onKeyPress = _.noop;
394 | app.cmd = "";
395 |
396 | if (leaders.isEmpty || app.score > leaders.lowestHiScore) {
397 | app.toState(STATES.highscore);
398 | } else {
399 | app.toState(STATES.leaderboard);
400 | }
401 | }
402 | };
403 | }
404 | },
405 | [STATES.highscore]: {
406 | enter: async function() {
407 | app.allowTyping = false;
408 | app.showTitle = false;
409 |
410 | controls.enabled = true;
411 |
412 | // make font appropriate size
413 | consoleCanvas.conf.FONT_SIZE = 4 * 90;
414 |
415 | // Only tween if the camera is not close enough to screen
416 | if (Math.floor(camera.position.z) !== 255) {
417 | await tweenCamera(camera, {
418 | rotation: {
419 | x: 0,
420 | y: 0,
421 | z: 0
422 | },
423 | position: {
424 | x: -4.336209717881005,
425 | y: 39.566049707444186,
426 | z: 255.4934617372831
427 | }
428 | });
429 | }
430 |
431 | if (app.score > leaders.topHiScore) {
432 | app.cmd = "Top Score!\n";
433 | console.log("New top score!", app.score);
434 | } else {
435 | app.cmd = "New High Score!\n";
436 | console.log("New high score", app.score);
437 | }
438 |
439 | app.cmd += "\nEnter your name";
440 |
441 | await sleep(app.typingTime(app.cmd));
442 |
443 | app.allowTyping = true;
444 | app.cmd += "\n";
445 |
446 | app.onResult = async result => {
447 | if (result.cmd.length > 0 && result.cmd !== ">") {
448 | app.onResult = _.noop();
449 | app.allowTyping = false;
450 | let name = result.cmd;
451 | let tribe = deriveTribe();
452 |
453 | // truncate name to max size
454 | name = name.substring(0, config.MAX_LEADER_NAME_LENGTH);
455 |
456 | // Store score and name pair in localStorage
457 | console.log("leader name: ", result.cmd);
458 |
459 | // this is an async function but we don't `await` it
460 | // because it's totally fine for it to run in the
461 | // background
462 | leaderboard.record({
463 | name: name,
464 | score: app.score,
465 | tribe: tribe
466 | });
467 |
468 | app.cmd = "";
469 |
470 | await sleep(200);
471 | app.toState(STATES.leaderboard);
472 | } else {
473 | app.cmd += "\nEnter your name\n";
474 | }
475 | };
476 | }
477 | },
478 | [STATES.leaderboard]: {
479 | enter: async function() {
480 | app.allowTyping = false;
481 | app.showTitle = false;
482 |
483 | controls.enabled = true;
484 |
485 | // Make smaller font size
486 | consoleCanvas.conf.FONT_SIZE = 4 * 48;
487 |
488 | // Only tween if the camera is not close enough to screen
489 | if (Math.floor(camera.position.z) !== 155) {
490 | await tweenCamera(camera, {
491 | rotation: {
492 | x: 0,
493 | y: 0,
494 | z: 0
495 | },
496 | position: {
497 | x: -4.336209717881005,
498 | y: 39.566049707444186,
499 | z: 155.4934617372831
500 | }
501 | });
502 | }
503 |
504 | // Fetch current leaders
505 | leaders = await leaderboard.get();
506 |
507 | app.cmd = "HIGH SCORES\n\n";
508 |
509 | if (leaders.isEmpty) {
510 | app.cmd += "None found!";
511 | } else {
512 | app.cmd += app.printHighScores(leaders.leaders);
513 | }
514 |
515 | app.cmd += `\nPress Enter to continue.`;
516 |
517 | // when any key is pressed, go back to the title screen
518 | app.onKeyPress = async ev => {
519 | // don't let any other event handlers run
520 | ev.preventDefault();
521 | ev.stopPropagation();
522 |
523 | if (ev.keyCode === keyCodes.enter) {
524 | app.onKeyPress = _.noop;
525 | app.cmd = "";
526 | app.toState(STATES.title);
527 | }
528 | };
529 | }
530 | }
531 | };
532 | window.states = states;
533 |
534 | async function start() {
535 | // Init localStorage
536 | leaderboard.init();
537 |
538 | if (localStorage.getItem("clhPlayCount") === null) {
539 | localStorage.setItem("clhPlayCount", "0");
540 | }
541 |
542 | // set up a state change listener so when the Vue app changes state, we
543 | // also run the 3D world state changes.
544 | app.onStateChange = change => {
545 | console.log(`state change: ${change.from} -> ${change.to}`);
546 | if (states[change.to]) {
547 | states[change.to].enter();
548 | } else {
549 | throw new Error(`tried to enter nonexistant state ${change.to}`);
550 | }
551 | };
552 |
553 | await init();
554 | animate(0);
555 |
556 | app.toState(STATES.title);
557 | }
558 |
559 | async function init() {
560 | container = document.createElement("div");
561 | document.body.appendChild(container);
562 |
563 | // init renderer
564 | renderer = new THREE.WebGLRenderer({
565 | canvas: document.querySelector("#game-canvas"),
566 | antialias: true
567 | });
568 | renderer.setPixelRatio(window.devicePixelRatio);
569 | renderer.setSize(window.innerWidth, window.innerHeight);
570 | renderer.shadowMap.enabled = true;
571 | renderer.shadowMap.type = THREE.PCFShadowMap;
572 | container.appendChild(renderer.domElement);
573 |
574 | // scene
575 |
576 | scene = new THREE.Scene();
577 | scene.background = new THREE.Color(
578 | 0.52164000272751 / 4.3,
579 | 0.08910000324249 / 4.3,
580 | 0.81000000238419 / 4.3
581 | );
582 |
583 | // camera
584 |
585 | camera = new THREE.PerspectiveCamera(
586 | 45,
587 | window.innerWidth / window.innerHeight,
588 | 1,
589 | 20000
590 | );
591 | // camera.position.z = 350;
592 | camera.position.z = 2000;
593 | camera.position.y = 300;
594 | scene.add(camera);
595 |
596 | controls = new THREE.OrbitControls(camera, renderer.domElement);
597 | controls.enabled = false;
598 | // controls = new THREE.TrackballControls(camera);
599 |
600 | // lighting
601 |
602 | let ambientLight = new THREE.AmbientLight(0xffffff, 0.8);
603 | scene.add(ambientLight);
604 |
605 | // spotlights
606 |
607 | const SHADOW_MAP_WIDTH = 1024 * 2,
608 | SHADOW_MAP_HEIGHT = 1024 * 2;
609 | const whiteSpot = new THREE.SpotLight(0xffffff, 1.0);
610 | whiteSpot.position.set(-300, 600, 600);
611 | whiteSpot.angle = Math.PI / 8;
612 | whiteSpot.penumbra = 0.5;
613 | whiteSpot.decay = 2;
614 | whiteSpot.distance = 4000;
615 | scene.add(whiteSpot);
616 |
617 | const purpleSpot = new THREE.SpotLight(0xda8aff, 1.0);
618 | purpleSpot.position.set(200, 200, 200);
619 | purpleSpot.angle = Math.PI / 4;
620 | purpleSpot.penumbra = 0.5;
621 | purpleSpot.decay = 4;
622 | purpleSpot.distance = 2000;
623 | purpleSpot.castShadow = true;
624 | purpleSpot.shadow.mapSize.width = SHADOW_MAP_WIDTH;
625 | purpleSpot.shadow.mapSize.height = SHADOW_MAP_HEIGHT;
626 | purpleSpot.shadow.camera.near = 200;
627 | purpleSpot.shadow.camera.far = 1000;
628 | scene.add(purpleSpot);
629 |
630 | // models
631 |
632 | // load computer
633 |
634 | const comp = await loadMesh(
635 | "assets/models/",
636 | "CLH_ep2_computer_high_poly.mtl",
637 | "CLH_ep2_computer_high_poly.obj"
638 | );
639 | // make the screen reflect a crisp image
640 | comp.materials.materials.screen.roughness = 0.08;
641 | comp.materials.materials.screen.roughness = 0.7;
642 | comp.object.position.y = -300;
643 | comp.object.position.x = 0;
644 |
645 | // enable shadows for each object in the set of computer meshes
646 | comp.object.children.forEach(c => {
647 | c.castShadow = true;
648 | c.receiveShadow = true;
649 | });
650 |
651 | comp.object.castShadow = true;
652 | comp.object.receiveShadow = true;
653 |
654 | // set up a special canvas material for the screen
655 |
656 | const screen = _.find(comp.object.children, {
657 | name: "IBM_5150_Monitor_-_glass"
658 | });
659 | screen.material = new THREE.MeshBasicMaterial();
660 | screen.material.map = new THREE.CanvasTexture(consoleCanvas.canvas);
661 | window.screen = screen;
662 |
663 | computer = comp.object;
664 | window.comp = comp;
665 | scene.add(comp.object);
666 | camera.lookAt(comp.object.position);
667 |
668 | // create a TEMPORARY flat plane to draw the console on.
669 |
670 | // the comp model we have doesnt' have UV coordinates, so we can't draw a
671 | // texture onto it. for now, create a 3d plane, position it just
672 | // in front of the screen, and draw the console onto it.
673 | // (TODO: remove this once we have a comp model with UV coords)
674 |
675 | // get the screen position and dimensions and copy them
676 | screen.geometry.computeBoundingBox();
677 | const screenSize = {
678 | width:
679 | screen.geometry.boundingBox.max.x -
680 | screen.geometry.boundingBox.min.x,
681 | height:
682 | screen.geometry.boundingBox.max.y -
683 | screen.geometry.boundingBox.min.y
684 | };
685 |
686 | const consolePlaneGeo = new THREE.PlaneGeometry(
687 | screenSize.width,
688 | screenSize.height
689 | );
690 | const consolePlane = new THREE.Mesh(consolePlaneGeo, screen.material);
691 | consolePlane.position.set(-5.5, 42.8, 26);
692 | consolePlane.rotation.x = -0.16;
693 |
694 | // Fixes bug where slight sliver was hollow at bottom of console
695 | consolePlane.scale.x = 1.012;
696 | consolePlane.scale.y = 1.012;
697 |
698 | screen.visible = false;
699 | window.consolePlane = consolePlane;
700 |
701 | scene.add(consolePlane);
702 |
703 | // Fire
704 | firePlane = new THREE.PlaneBufferGeometry(
705 | screenSize.width * 3.7,
706 | screenSize.height * 3.7
707 | );
708 | fire = new THREE.Fire(firePlane, {
709 | textureWidth: 512,
710 | textureHeight: 512,
711 | debug: false
712 | });
713 | let texture = new THREE.TextureLoader().load(
714 | "assets/images/monitor_fire_inner.png"
715 | );
716 | texture.needsUpdate = true;
717 | fire.clearSources();
718 | fire.setSourceMap(texture);
719 | fire.position.set(-5.5, 42.8, 25.5);
720 | fire.rotation.x = -0.16;
721 | setFireStage(config.FIRE_STAGE_ZERO);
722 | scene.add(fire);
723 | window.fire = fire;
724 |
725 | // load cyc wall
726 |
727 | const cyc = await loadMesh(
728 | "assets/models/",
729 | "CLH_ep2_cyc_wall.mtl",
730 | "CLH_ep2_cyc_wall.obj"
731 | );
732 | window.cyc = cyc.object;
733 | cyc.object.position.y = 50;
734 | cyc.object.children[0].castShadow = true;
735 | cyc.object.children[0].receiveShadow = true;
736 | cyc.materials.materials.purple.metalness = 0.4;
737 | cyc.materials.materials.purple.roughness = 0.5;
738 | cyc.materials.materials.purple.color.setHex(0x621b9c);
739 | scene.add(cyc.object);
740 |
741 | // tweak the red and purple computer colors
742 | comp.materials.materials.red.color.setHex(0x881111);
743 | comp.materials.materials.red.clearcoat = 0.5
744 | comp.materials.materials.red.clearcoatRoughness = 0.3;
745 |
746 | comp.materials.materials.purple.color.setHex(0x7d2cd0);
747 | comp.materials.materials.purple.metalness = 0.4;
748 | comp.materials.materials.purple.roughness = 0.5;
749 | comp.materials.materials.purple.clearcoat = 0.5;
750 | comp.materials.materials.purple.clearcoatRoughness = 0.3;
751 |
752 | document.addEventListener("mousemove", onDocumentMouseMove, false);
753 |
754 | window.addEventListener("resize", onWindowResize, false);
755 | console.log("init complete");
756 | }
757 |
758 | function deriveTribe() {
759 | let cmdCounts = [
760 | { tribe: "bash", count: app.count.bash },
761 | { tribe: "Python", count: app.count.py },
762 | { tribe: "JavaScript", count: app.count.js },
763 | { tribe: "HTML", count: app.count.html }
764 | ];
765 |
766 | const tribesSorted = _.reverse(_.sortBy(cmdCounts, "count"));
767 |
768 | return tribesSorted[0].tribe;
769 | }
770 |
771 | function getFireScaleByStage(stage) {
772 | let scale = {};
773 | let one = config.FIRE_STAGE_ONE_SCALE;
774 | let two = config.FIRE_STAGE_TWO_SCALE;
775 | let three = config.FIRE_STAGE_THREE_SCALE;
776 |
777 | if (isLowFPS) {
778 | one = config.FIRE_LOW_FPS_STAGE_ONE_SCALE;
779 | two = config.FIRE_LOW_FPS_STAGE_TWO_SCALE;
780 | three = config.FIRE_LOW_FPS_STAGE_THREE_SCALE;
781 | }
782 |
783 | switch (stage) {
784 | case config.FIRE_STAGE_ZERO:
785 | scale.x = 0.1;
786 | scale.y = 0.1;
787 | break;
788 | case config.FIRE_STAGE_ONE:
789 | scale.x = one.x;
790 | scale.y = one.y;
791 | break;
792 | case config.FIRE_STAGE_TWO:
793 | scale.x = two.x;
794 | scale.y = two.y;
795 | break;
796 | case config.FIRE_STAGE_THREE:
797 | scale.x = three.x;
798 | scale.y = three.y;
799 | break;
800 | default:
801 | scale.x = 0.1;
802 | scale.y = 0.1;
803 | }
804 |
805 | if (stage > config.FIRE_STAGE_THREE) {
806 | scale.x = 1;
807 | scale.y = 1;
808 | }
809 |
810 | return scale;
811 | }
812 |
813 | function setFireStage(stage) {
814 | if (stage > config.FIRE_STAGE_THREE) stage = config.FIRE_STAGE_THREE;
815 | else if (stage < 0) stage = 0;
816 |
817 | if (fire.userData.stage === stage) return; // already on this stage
818 |
819 | if (fire.userData.stage === undefined) fire.userData.stage = 0;
820 |
821 | if (!isLowFPS) {
822 | fire.color1.set(config.FIRE_COLOR_1);
823 | fire.color2.set(config.FIRE_COLOR_2);
824 | fire.color3.set(config.FIRE_COLOR_3);
825 | fire.windVector.y = config.FIRE_WIND_VECTOR_Y;
826 | fire.colorBias = config.FIRE_COLOR_BIAS;
827 | fire.burnRate = config.FIRE_BURN_RATE;
828 | fire.diffuse = config.FIRE_DIFFUSE;
829 | fire.viscosity = config.FIRE_VISCOSITY;
830 | fire.expansion = config.FIRE_EXPANSION;
831 | fire.swirl = config.FIRE_SWIRL;
832 | fire.drag = config.FIRE_DRAG;
833 | fire.airSpeed = config.FIRE_AIR_SPEED;
834 | fire.speed = config.FIRE_SPEED;
835 | } else {
836 | fire.color1.set(config.FIRE_LOW_FPS_COLOR_1);
837 | fire.color2.set(config.FIRE_LOW_FPS_COLOR_2);
838 | fire.color3.set(config.FIRE_LOW_FPS_COLOR_3);
839 | fire.windVector.y = config.FIRE_LOW_FPS_WIND_VECTOR_Y;
840 | fire.colorBias = config.FIRE_LOW_FPS_COLOR_BIAS;
841 | fire.burnRate = config.FIRE_LOW_FPS_BURN_RATE;
842 | fire.diffuse = config.FIRE_LOW_FPS_DIFFUSE;
843 | fire.viscosity = config.FIRE_LOW_FPS_VISCOSITY;
844 | fire.expansion = config.FIRE_LOW_FPS_EXPANSION;
845 | fire.swirl = config.FIRE_LOW_FPS_SWIRL;
846 | fire.drag = config.FIRE_LOW_FPS_DRAG;
847 | fire.airSpeed = config.FIRE_LOW_FPS_AIR_SPEED;
848 | fire.speed = config.FIRE_LOW_FPS_SPEED;
849 | }
850 |
851 | if (stage === config.FIRE_STAGE_ZERO) fire.userData.on = false;
852 |
853 | const stageScale = getFireScaleByStage(stage);
854 | let steps = Math.abs(fire.userData.stage - stage);
855 | console.log("stageScale:", stageScale);
856 | console.log("steps:", steps);
857 |
858 | new TWEEN.Tween(fire.scale)
859 | .to(
860 | { x: stageScale.x, y: stageScale.y },
861 | steps * config.FIRE_STAGE_TWEEN_TIME
862 | )
863 | .easing(TWEEN.Easing.Quartic.InOut) // Use an easing function to make the animation smooth.
864 | .start();
865 |
866 | fire.userData.stage = stage;
867 | }
868 | window.setFireStage = setFireStage;
869 |
870 | function onWindowResize() {
871 | windowHalfX = window.innerWidth / 2;
872 | windowHalfY = window.innerHeight / 2;
873 |
874 | camera.aspect = window.innerWidth / window.innerHeight;
875 | camera.updateProjectionMatrix();
876 |
877 | renderer.setSize(window.innerWidth, window.innerHeight);
878 | }
879 |
880 | function onDocumentMouseMove(event) {
881 | mouseX = (event.clientX - windowHalfX) / 2;
882 | mouseY = (event.clientY - windowHalfY) / 2;
883 | }
884 |
885 | function animate(time) {
886 | // FPS tracking
887 | let maximumFrameTime = config.MAX_FRAME_TIME;
888 | t = performance.now();
889 | let elapsed = t - previousTime;
890 | previousTime = t;
891 |
892 | if (elapsed > maximumFrameTime) {
893 | slowCount++;
894 | }
895 |
896 | if (slowCount > maxSlowFrames && !isLowFPS) {
897 | // This client has slow FPS currently
898 | console.log("Low FPS detected: ", 1000 / elapsed, "FPS");
899 | isLowFPS = true;
900 | }
901 |
902 | requestAnimationFrame(animate);
903 | render(time);
904 | }
905 |
906 | function render(time) {
907 | stats.begin();
908 |
909 | TWEEN.update(time);
910 |
911 | // update the canvas-based material
912 | screen.material.map.needsUpdate = true;
913 |
914 | app.updateConsole();
915 |
916 | renderer.render(scene, camera);
917 |
918 | stats.end();
919 | }
920 |
921 | function setInstuctionsDisplay(display) {
922 | let instructions = document.getElementById("instructions");
923 | let langs = document.getElementById("langs");
924 | let tagline = document.getElementById("tagline");
925 | let listen = document.getElementById("listen");
926 |
927 | instructions.style.display = display;
928 | langs.style.display = display;
929 | tagline.style.display = display;
930 | listen.style.display = display;
931 | }
932 | window.setInstuctionsDisplay = setInstuctionsDisplay;
933 |
934 | function setCreditsDisplay(display) {
935 | let credits = document.getElementById("credits");
936 | credits.style.display = display;
937 | }
938 | window.setCreditsDisplay = setCreditsDisplay;
939 |
940 | function showCredits() {
941 | setInstuctionsDisplay("none");
942 | setCreditsDisplay("block");
943 | }
944 | window.showCredits = showCredits;
945 |
946 | function showInstructions() {
947 | setInstuctionsDisplay("block");
948 | setCreditsDisplay("none");
949 | }
950 | window.showInstructions = showInstructions;
951 |
952 | if (isMobile.any) {
953 | console.log("aborting init, can't run on mobile");
954 | } else {
955 | start();
956 | }
957 |
958 | // disables right click in game to prevent the context menu from inturrupting game play
959 | window.addEventListener("contextmenu", event => event.preventDefault());
960 |
--------------------------------------------------------------------------------
/src/palette.js:
--------------------------------------------------------------------------------
1 | const palette = {};
2 |
3 | function cssVar(prop) {
4 | return window
5 | .getComputedStyle(document.body)
6 | .getPropertyValue(prop)
7 | .trim();
8 | }
9 |
10 | palette.white = cssVar("--clh-white");
11 | palette.black = cssVar("--clh-black");
12 | palette.purple = cssVar("--clh-purple");
13 | palette.purple_light = cssVar("--clh-purple-light");
14 | palette.yellow = cssVar("--clh-yellow");
15 | palette.yellow_light = cssVar("--clh-yellow-light");
16 | palette.orange = cssVar("--clh-orange");
17 | palette.orange_light = cssVar("--clh-orange-light");
18 | palette.blue = cssVar("--clh-blue");
19 | palette.blue_light = cssVar("--clh-blue-light");
20 |
21 | export default palette;
22 |
--------------------------------------------------------------------------------
/src/sfx.js:
--------------------------------------------------------------------------------
1 | const sfx = {
2 | cmdGood: new Howl({ src: ["assets/sfx/cmd-good.mp3"], volume: 1 }),
3 | cmdGold: new Howl({ src: ["assets/sfx/cmd-gold.mp3"], volume: 1 }),
4 | cmdBad: new Howl({ src: ["assets/sfx/cmd-bad.mp3"], volume: 2 }),
5 | keypress: new Howl({ src: ["assets/sfx/keypress.mp3"] }),
6 | timerRelaxed: new Howl({
7 | src: ["assets/sfx/timer-relaxed.mp3"],
8 | volume: 1
9 | }),
10 | timerUrgent: new Howl({ src: ["assets/sfx/timer-urgent.mp3"], volume: 1 }),
11 | boot: new Howl({ src: ["assets/sfx/boot.mp3"] }),
12 | menuMusic: new Howl({
13 | src: ["assets/sfx/menu-music.mp3"],
14 | volume: 0.2,
15 | loop: true
16 | }),
17 | play: new Howl({
18 | src: ["assets/sfx/play.mp3"],
19 | volume: 0.2,
20 | sprite: {
21 | golden: [0, 29648, true],
22 | playing: [29649, 60000 + 39456]
23 | }
24 | })
25 | };
26 |
27 | // preserve the original volume setting for each sfx
28 | _.forEach(sfx, s => (s.originalVolume = s.volume()));
29 |
30 | // when these sfx have finished fading out, stop playing and seek back to the beginning
31 | [sfx.boot, sfx.menuMusic, sfx.play].forEach(sound => {
32 | sound.on("fade", () => {
33 | sound.stop();
34 | sound.seek(0);
35 | sound.volume(sound.originalVolume);
36 | });
37 | });
38 |
39 | window.sfx = sfx;
40 |
41 | export default sfx;
42 |
--------------------------------------------------------------------------------
/src/sleep.js:
--------------------------------------------------------------------------------
1 | export default function(ms) {
2 | return new Promise((resolve, reject) => setTimeout(resolve, ms));
3 | }
4 |
--------------------------------------------------------------------------------
/src/states.js:
--------------------------------------------------------------------------------
1 | export default {
2 | loading: "loading",
3 | title: "title",
4 | play: "play",
5 | score: "score",
6 | leaderboard: "leaderboard",
7 | highscore: "highscore"
8 | };
9 |
--------------------------------------------------------------------------------
/src/three-utils.js:
--------------------------------------------------------------------------------
1 | export async function loadMesh(path, mtl, obj) {
2 | let onProgress = function(name) {
3 | return function(xhr) {
4 | if (xhr.lengthComputable) {
5 | let percentComplete = (xhr.loaded / xhr.total) * 100;
6 | console.log(
7 | `${name} ${Math.round(percentComplete, 2)} % downloaded`
8 | );
9 | }
10 | };
11 | };
12 |
13 | return new Promise((resolve, reject) => {
14 | new THREE.MTLLoader().setPath(path).load(
15 | mtl,
16 | function(materials) {
17 | materials.preload();
18 |
19 | new THREE.OBJLoader()
20 | .setMaterials(materials)
21 | .setPath(path)
22 | .load(
23 | obj,
24 | function(object) {
25 | resolve({ materials, object });
26 | },
27 | onProgress(obj),
28 | reject
29 | );
30 | },
31 | onProgress(mtl),
32 | reject
33 | );
34 | });
35 | }
36 |
--------------------------------------------------------------------------------
/src/tween-camera.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Tween the camera to a given position and rotation. Duration and easing are optional.
3 | */
4 | export default function tweenCamera(
5 | camera,
6 | { position, rotation, duration = 2000, easing = TWEEN.Easing.Quartic.Out }
7 | ) {
8 | return new Promise((resolve, reject) => {
9 | // TODO show other title state stuff like text, logo, etc.
10 |
11 | // tween to camera position
12 | new TWEEN.Tween(camera.rotation) // Create a new tween that modifies 'coords'.
13 | .to(rotation, duration) // Move to (300, 200) in 1 second.
14 | .easing(easing) // Use an easing function to make the animation smooth.
15 | .start(); // Start the tween immediately.
16 | new TWEEN.Tween(camera.position) // Create a new tween that modifies 'coords'.
17 | .to(position, duration) // Move to (300, 200) in 1 second.
18 | .easing(easing) // Use an easing function to make the animation smooth.
19 | .onComplete(resolve)
20 | .start(); // Start the tween immediately.
21 | });
22 | }
23 |
--------------------------------------------------------------------------------
/src/unzip.js:
--------------------------------------------------------------------------------
1 | var zlib = require('zlib');
2 | var fs = require('fs');
3 |
4 | function decompress(inFilename, outFilename) {
5 | var unzip = zlib.createUnzip();
6 | var input = fs.createReadStream(inFilename);
7 | var output = fs.createWriteStream(outFilename);
8 |
9 | input.pipe(unzip).pipe(output);
10 | }
11 |
12 | decompress('assets/models/CLH_Computer.mtl.gz', 'assets/models/CLH_Computer.mtl');
13 | decompress('assets/models/CLH_Computer.obj.gz', 'assets/models/CLH_Computer.obj');
14 | decompress('assets/models/CLH_ep2_computer_high_poly.mtl.gz', 'assets/models/CLH_ep2_computer_high_poly.mtl');
15 | decompress('assets/models/CLH_ep2_computer_high_poly.obj.gz', 'assets/models/CLH_ep2_computer_high_poly.obj');
16 | decompress('assets/models/CLH_ep2_cyc_wall.mtl.gz', 'assets/models/CLH_ep2_cyc_wall.mtl');
17 | decompress('assets/models/CLH_ep2_cyc_wall.obj.gz', 'assets/models/CLH_ep2_cyc_wall.obj');
18 | decompress('assets/models/CLH_Shuttle.mtl.gz', 'assets/models/CLH_Shuttle.mtl');
19 | decompress('assets/models/CLH_Shuttle.obj.gz', 'assets/models/CLH_Shuttle.obj');
--------------------------------------------------------------------------------
/start.js:
--------------------------------------------------------------------------------
1 | const compression = require("compression");
2 | const bs = require("browser-sync").create();
3 |
4 | bs.init({
5 | server: "./",
6 | watch: true,
7 | middleware: [compression({filter: () => true })]
8 | });
9 |
--------------------------------------------------------------------------------