├── .gitignore ├── LICENSE.md ├── README.md ├── build.sh ├── build └── dummy.txt ├── index-debug.html ├── m ├── l1.png ├── l2.png ├── l3.png └── q2.png ├── package.json ├── shrinkit.js └── source ├── audio.js ├── entity-cpu.js ├── entity-explosion.js ├── entity-health.js ├── entity-particle.js ├── entity-plasma.js ├── entity-player.js ├── entity-sentry.js ├── entity-spider.js ├── entity.js ├── game.js ├── html-template.html ├── main.js ├── music-dark-meat-beat.js ├── random.js ├── renderer.js ├── sonantx-reduced.js ├── sound-effects.js └── terminal.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | work 3 | build -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Dominic Szablewski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UNDERRUN - A JS13k GAME 2 | 3 | My entry for the 2018 [js13k](https://js13kgames.com/) competition. 4 | 5 | Play here: https://phoboslab.org/underrun/ 6 | 7 | MIT Licensed 8 | 9 | Please be aware that this projects makes use of the Sonant-X library (albeit heavily modified) which is published under the zlib license. -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | cat \ 2 | source/game.js \ 3 | source/random.js \ 4 | source/renderer.js \ 5 | source/entity.js \ 6 | source/entity-player.js \ 7 | source/entity-cpu.js \ 8 | source/entity-plasma.js \ 9 | source/entity-spider.js \ 10 | source/entity-sentry.js \ 11 | source/entity-particle.js \ 12 | source/entity-health.js \ 13 | source/entity-explosion.js \ 14 | source/sonantx-reduced.js \ 15 | source/music-dark-meat-beat.js \ 16 | source/sound-effects.js \ 17 | source/audio.js \ 18 | source/terminal.js \ 19 | source/main.js \ 20 | > build/underrun.js 21 | 22 | node shrinkit.js build/underrun.js > build/underrun.compact.js 23 | 24 | ./node_modules/uglify-es/bin/uglifyjs build/underrun.compact.js \ 25 | --compress --screw-ie8 --mangle toplevel -c --beautify --mangle-props regex='/^_/;' \ 26 | -o build/underrun.min.beauty.js 27 | 28 | ./node_modules/uglify-es/bin/uglifyjs build/underrun.compact.js \ 29 | --compress --screw-ie8 --mangle toplevel --mangle-props regex='/^_/;' \ 30 | -o build/underrun.min.js 31 | 32 | 33 | rm build/underrun.zip 34 | 35 | sed -e '/GAME_SOURCE/{r build/underrun.min.js' -e 'd}' source/html-template.html > underrun.html 36 | zip -9 build/underrun.zip m/q2.png m/l1.png m/l2.png m/l3.png underrun.html 37 | ls -la build/ 38 | mv underrun.html build/underrun.html 39 | -------------------------------------------------------------------------------- /build/dummy.txt: -------------------------------------------------------------------------------- 1 | dummy -------------------------------------------------------------------------------- /index-debug.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UNDERRUN 6 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /m/l1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/underrun/f933e29152d7fc1ca61d4b3eaa8b29551d7d7a62/m/l1.png -------------------------------------------------------------------------------- /m/l2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/underrun/f933e29152d7fc1ca61d4b3eaa8b29551d7d7a62/m/l2.png -------------------------------------------------------------------------------- /m/l3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/underrun/f933e29152d7fc1ca61d4b3eaa8b29551d7d7a62/m/l3.png -------------------------------------------------------------------------------- /m/q2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/underrun/f933e29152d7fc1ca61d4b3eaa8b29551d7d7a62/m/q2.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "undderun", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "underrun.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "uglify-es": "^3.3.9" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /shrinkit.js: -------------------------------------------------------------------------------- 1 | const webgl_replace = { 2 | ACTIVE_ATTRIBUTES: 35721, 3 | ACTIVE_ATTRIBUTE_MAX_LENGTH: 35722, 4 | ACTIVE_TEXTURE: 34016, 5 | ACTIVE_UNIFORMS: 35718, 6 | ACTIVE_UNIFORM_MAX_LENGTH: 35719, 7 | ALIASED_LINE_WIDTH_RANGE: 33902, 8 | ALIASED_POINT_SIZE_RANGE: 33901, 9 | ALPHA: 6406, 10 | ALPHA_BITS: 3413, 11 | ALWAYS: 519, 12 | ARRAY_BUFFER: 34962, 13 | ARRAY_BUFFER_BINDING: 34964, 14 | ATTACHED_SHADERS: 35717, 15 | BACK: 1029, 16 | BLEND: 3042, 17 | BLEND_COLOR: 32773, 18 | BLEND_DST_ALPHA: 32970, 19 | BLEND_DST_RGB: 32968, 20 | BLEND_EQUATION: 32777, 21 | BLEND_EQUATION_ALPHA: 34877, 22 | BLEND_EQUATION_RGB: 32777, 23 | BLEND_SRC_ALPHA: 32971, 24 | BLEND_SRC_RGB: 32969, 25 | BLUE_BITS: 3412, 26 | BOOL: 35670, 27 | BOOL_VEC2: 35671, 28 | BOOL_VEC3: 35672, 29 | BOOL_VEC4: 35673, 30 | BROWSER_DEFAULT_WEBGL: 37444, 31 | BUFFER_SIZE: 34660, 32 | BUFFER_USAGE: 34661, 33 | BYTE: 5120, 34 | CCW: 2305, 35 | CLAMP_TO_EDGE: 33071, 36 | COLOR_ATTACHMENT0: 36064, 37 | COLOR_BUFFER_BIT: 16384, 38 | COLOR_CLEAR_VALUE: 3106, 39 | COLOR_WRITEMASK: 3107, 40 | COMPILE_STATUS: 35713, 41 | COMPRESSED_TEXTURE_FORMATS: 34467, 42 | CONSTANT_ALPHA: 32771, 43 | CONSTANT_COLOR: 32769, 44 | CONTEXT_LOST_WEBGL: 37442, 45 | CULL_FACE: 2884, 46 | CULL_FACE_MODE: 2885, 47 | CURRENT_PROGRAM: 35725, 48 | CURRENT_VERTEX_ATTRIB: 34342, 49 | CW: 2304, 50 | DECR: 7683, 51 | DECR_WRAP: 34056, 52 | DELETE_STATUS: 35712, 53 | DEPTH_ATTACHMENT: 36096, 54 | DEPTH_BITS: 3414, 55 | DEPTH_BUFFER_BIT: 256, 56 | DEPTH_CLEAR_VALUE: 2931, 57 | DEPTH_COMPONENT: 6402, 58 | DEPTH_COMPONENT16: 33189, 59 | DEPTH_FUNC: 2932, 60 | DEPTH_RANGE: 2928, 61 | DEPTH_STENCIL: 34041, 62 | DEPTH_STENCIL_ATTACHMENT: 33306, 63 | DEPTH_TEST: 2929, 64 | DEPTH_WRITEMASK: 2930, 65 | DITHER: 3024, 66 | DONT_CARE: 4352, 67 | DST_ALPHA: 772, 68 | DST_COLOR: 774, 69 | DYNAMIC_DRAW: 35048, 70 | ELEMENT_ARRAY_BUFFER: 34963, 71 | ELEMENT_ARRAY_BUFFER_BINDING: 34965, 72 | EQUAL: 514, 73 | FASTEST: 4353, 74 | FLOAT: 5126, 75 | FLOAT_MAT2: 35674, 76 | FLOAT_MAT3: 35675, 77 | FLOAT_MAT4: 35676, 78 | FLOAT_VEC2: 35664, 79 | FLOAT_VEC3: 35665, 80 | FLOAT_VEC4: 35666, 81 | FRAGMENT_SHADER: 35632, 82 | FRAMEBUFFER: 36160, 83 | FRAMEBUFFER_ATTACHMENT_OBJECT_NAME: 36049, 84 | FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE: 36048, 85 | FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE: 36051, 86 | FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL: 36050, 87 | FRAMEBUFFER_BINDING: 36006, 88 | FRAMEBUFFER_COMPLETE: 36053, 89 | FRAMEBUFFER_INCOMPLETE_ATTACHMENT: 36054, 90 | FRAMEBUFFER_INCOMPLETE_DIMENSIONS: 36057, 91 | FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: 36055, 92 | FRAMEBUFFER_UNSUPPORTED: 36061, 93 | FRONT: 1028, 94 | FRONT_AND_BACK: 1032, 95 | FRONT_FACE: 2886, 96 | FUNC_ADD: 32774, 97 | FUNC_REVERSE_SUBTRACT: 32779, 98 | FUNC_SUBTRACT: 32778, 99 | GENERATE_MIPMAP_HINT: 33170, 100 | GEQUAL: 518, 101 | GREATER: 516, 102 | GREEN_BITS: 3411, 103 | HIGH_FLOAT: 36338, 104 | HIGH_INT: 36341, 105 | INCR: 7682, 106 | INCR_WRAP: 34055, 107 | INFO_LOG_LENGTH: 35716, 108 | INT: 5124, 109 | INT_VEC2: 35667, 110 | INT_VEC3: 35668, 111 | INT_VEC4: 35669, 112 | INVALID_ENUM: 1280, 113 | INVALID_FRAMEBUFFER_OPERATION: 1286, 114 | INVALID_OPERATION: 1282, 115 | INVALID_VALUE: 1281, 116 | INVERT: 5386, 117 | KEEP: 7680, 118 | LEQUAL: 515, 119 | LESS: 513, 120 | LINEAR: 9729, 121 | LINEAR_MIPMAP_LINEAR: 9987, 122 | LINEAR_MIPMAP_NEAREST: 9985, 123 | LINES: 1, 124 | LINE_LOOP: 2, 125 | LINE_STRIP: 3, 126 | LINE_WIDTH: 2849, 127 | LINK_STATUS: 35714, 128 | LOW_FLOAT: 36336, 129 | LOW_INT: 36339, 130 | LUMINANCE: 6409, 131 | LUMINANCE_ALPHA: 6410, 132 | MAX_COMBINED_TEXTURE_IMAGE_UNITS: 35661, 133 | MAX_CUBE_MAP_TEXTURE_SIZE: 34076, 134 | MAX_FRAGMENT_UNIFORM_VECTORS: 36349, 135 | MAX_RENDERBUFFER_SIZE: 34024, 136 | MAX_TEXTURE_IMAGE_UNITS: 34930, 137 | MAX_TEXTURE_SIZE: 3379, 138 | MAX_VARYING_VECTORS: 36348, 139 | MAX_VERTEX_ATTRIBS: 34921, 140 | MAX_VERTEX_TEXTURE_IMAGE_UNITS: 35660, 141 | MAX_VERTEX_UNIFORM_VECTORS: 36347, 142 | MAX_VIEWPORT_DIMS: 3386, 143 | MEDIUM_FLOAT: 36337, 144 | MEDIUM_INT: 36340, 145 | MIRRORED_REPEAT: 33648, 146 | NEAREST: 9728, 147 | NEAREST_MIPMAP_LINEAR: 9986, 148 | NEAREST_MIPMAP_NEAREST: 9984, 149 | NEVER: 512, 150 | NICEST: 4354, 151 | NONE: 0, 152 | NOTEQUAL: 517, 153 | NO_ERROR: 0, 154 | NUM_COMPRESSED_TEXTURE_FORMATS: 34466, 155 | ONE: 1, 156 | ONE_MINUS_CONSTANT_ALPHA: 32772, 157 | ONE_MINUS_CONSTANT_COLOR: 32770, 158 | ONE_MINUS_DST_ALPHA: 773, 159 | ONE_MINUS_DST_COLOR: 775, 160 | ONE_MINUS_SRC_ALPHA: 771, 161 | ONE_MINUS_SRC_COLOR: 769, 162 | OUT_OF_MEMORY: 1285, 163 | PACK_ALIGNMENT: 3333, 164 | POINTS: 0, 165 | POLYGON_OFFSET_FACTOR: 32824, 166 | POLYGON_OFFSET_FILL: 32823, 167 | POLYGON_OFFSET_UNITS: 10752, 168 | RED_BITS: 3410, 169 | RENDERBUFFER: 36161, 170 | RENDERBUFFER_ALPHA_SIZE: 36179, 171 | RENDERBUFFER_BINDING: 36007, 172 | RENDERBUFFER_BLUE_SIZE: 36178, 173 | RENDERBUFFER_DEPTH_SIZE: 36180, 174 | RENDERBUFFER_GREEN_SIZE: 36177, 175 | RENDERBUFFER_HEIGHT: 36163, 176 | RENDERBUFFER_INTERNAL_FORMAT: 36164, 177 | RENDERBUFFER_RED_SIZE: 36176, 178 | RENDERBUFFER_STENCIL_SIZE: 36181, 179 | RENDERBUFFER_WIDTH: 36162, 180 | RENDERER: 7937, 181 | REPEAT: 10497, 182 | REPLACE: 7681, 183 | RGB: 6407, 184 | RGB5_A1: 32855, 185 | RGB565: 36194, 186 | RGBA: 6408, 187 | RGBA4: 32854, 188 | SAMPLER_2D: 35678, 189 | SAMPLER_CUBE: 35680, 190 | SAMPLES: 32937, 191 | SAMPLE_ALPHA_TO_COVERAGE: 32926, 192 | SAMPLE_BUFFERS: 32936, 193 | SAMPLE_COVERAGE: 32928, 194 | SAMPLE_COVERAGE_INVERT: 32939, 195 | SAMPLE_COVERAGE_VALUE: 32938, 196 | SCISSOR_BOX: 3088, 197 | SCISSOR_TEST: 3089, 198 | SHADER_COMPILER: 36346, 199 | SHADER_SOURCE_LENGTH: 35720, 200 | SHADER_TYPE: 35663, 201 | SHADING_LANGUAGE_VERSION: 35724, 202 | SHORT: 5122, 203 | SRC_ALPHA: 770, 204 | SRC_ALPHA_SATURATE: 776, 205 | SRC_COLOR: 768, 206 | STATIC_DRAW: 35044, 207 | STENCIL_ATTACHMENT: 36128, 208 | STENCIL_BACK_FAIL: 34817, 209 | STENCIL_BACK_FUNC: 34816, 210 | STENCIL_BACK_PASS_DEPTH_FAIL: 34818, 211 | STENCIL_BACK_PASS_DEPTH_PASS: 34819, 212 | STENCIL_BACK_REF: 36003, 213 | STENCIL_BACK_VALUE_MASK: 36004, 214 | STENCIL_BACK_WRITEMASK: 36005, 215 | STENCIL_BITS: 3415, 216 | STENCIL_BUFFER_BIT: 1024, 217 | STENCIL_CLEAR_VALUE: 2961, 218 | STENCIL_FAIL: 2964, 219 | STENCIL_FUNC: 2962, 220 | STENCIL_INDEX: 6401, 221 | STENCIL_INDEX8: 36168, 222 | STENCIL_PASS_DEPTH_FAIL: 2965, 223 | STENCIL_PASS_DEPTH_PASS: 2966, 224 | STENCIL_REF: 2967, 225 | STENCIL_TEST: 2960, 226 | STENCIL_VALUE_MASK: 2963, 227 | STENCIL_WRITEMASK: 2968, 228 | STREAM_DRAW: 35040, 229 | SUBPIXEL_BITS: 3408, 230 | TEXTURE: 5890, 231 | TEXTURE0: 33984, 232 | TEXTURE1: 33985, 233 | TEXTURE2: 33986, 234 | TEXTURE3: 33987, 235 | TEXTURE4: 33988, 236 | TEXTURE5: 33989, 237 | TEXTURE6: 33990, 238 | TEXTURE7: 33991, 239 | TEXTURE8: 33992, 240 | TEXTURE9: 33993, 241 | TEXTURE10: 33994, 242 | TEXTURE11: 33995, 243 | TEXTURE12: 33996, 244 | TEXTURE13: 33997, 245 | TEXTURE14: 33998, 246 | TEXTURE15: 33999, 247 | TEXTURE16: 34000, 248 | TEXTURE17: 34001, 249 | TEXTURE18: 34002, 250 | TEXTURE19: 34003, 251 | TEXTURE20: 34004, 252 | TEXTURE21: 34005, 253 | TEXTURE22: 34006, 254 | TEXTURE23: 34007, 255 | TEXTURE24: 34008, 256 | TEXTURE25: 34009, 257 | TEXTURE26: 34010, 258 | TEXTURE27: 34011, 259 | TEXTURE28: 34012, 260 | TEXTURE29: 34013, 261 | TEXTURE30: 34014, 262 | TEXTURE31: 34015, 263 | TEXTURE_2D: 3553, 264 | TEXTURE_BINDING_2D: 32873, 265 | TEXTURE_BINDING_CUBE_MAP: 34068, 266 | TEXTURE_CUBE_MAP: 34067, 267 | TEXTURE_CUBE_MAP_NEGATIVE_X: 34070, 268 | TEXTURE_CUBE_MAP_NEGATIVE_Y: 34072, 269 | TEXTURE_CUBE_MAP_NEGATIVE_Z: 34074, 270 | TEXTURE_CUBE_MAP_POSITIVE_X: 34069, 271 | TEXTURE_CUBE_MAP_POSITIVE_Y: 34071, 272 | TEXTURE_CUBE_MAP_POSITIVE_Z: 34073, 273 | TEXTURE_MAG_FILTER: 10240, 274 | TEXTURE_MIN_FILTER: 10241, 275 | TEXTURE_WRAP_S: 10242, 276 | TEXTURE_WRAP_T: 10243, 277 | TRIANGLES: 4, 278 | TRIANGLE_FAN: 6, 279 | TRIANGLE_STRIP: 5, 280 | UNPACK_ALIGNMENT: 3317, 281 | UNPACK_COLORSPACE_CONVERSION_WEBGL: 37443, 282 | UNPACK_FLIP_Y_WEBGL: 37440, 283 | UNPACK_PREMULTIPLY_ALPHA_WEBGL: 37441, 284 | UNSIGNED_BYTE: 5121, 285 | UNSIGNED_INT: 5125, 286 | UNSIGNED_SHORT: 5123, 287 | UNSIGNED_SHORT_4_4_4_4: 32819, 288 | UNSIGNED_SHORT_5_5_5_1: 32820, 289 | UNSIGNED_SHORT_5_6_5: 33635, 290 | VALIDATE_STATUS: 35715, 291 | VENDOR: 7936, 292 | VERSION: 7938, 293 | VERTEX_ATTRIB_ARRAY_BUFFER_BINDING: 34975, 294 | VERTEX_ATTRIB_ARRAY_ENABLED: 34338, 295 | VERTEX_ATTRIB_ARRAY_NORMALIZED: 34922, 296 | VERTEX_ATTRIB_ARRAY_POINTER: 34373, 297 | VERTEX_ATTRIB_ARRAY_SIZE: 34339, 298 | VERTEX_ATTRIB_ARRAY_STRIDE: 34340, 299 | VERTEX_ATTRIB_ARRAY_TYPE: 34341, 300 | VERTEX_SHADER: 35633, 301 | VIEWPORT: 2978, 302 | ZERO: 0, 303 | 304 | activeTexture: "gl.acT", 305 | attachShader: "gl.atS", 306 | bindAttribLocation: "gl.biAL", 307 | bindBuffer: "gl.biB", 308 | bindFramebuffer: "gl.biF", 309 | bindRenderbuffer: "gl.biR", 310 | bindTexture: "gl.biT", 311 | blendColor: "gl.blC", 312 | blendEquation: "gl.blE", 313 | blendEquationSeparate: "gl.blES", 314 | blendFunc: "gl.blF", 315 | blendFuncSeparate: "gl.blFS", 316 | bufferData: "gl.buD", 317 | bufferSubData: "gl.buSD", 318 | checkFramebufferStatus: "gl.chFS", 319 | clear: "gl.cl", 320 | clearColor: "gl.clC", 321 | clearDepth: "gl.clD", 322 | clearStencil: "gl.clS", 323 | colorMask: "gl.coM", 324 | compileShader: "gl.coS", 325 | compressedTexImage2D: "gl.coTI2D", 326 | compressedTexSubImage2D: "gl.coTSI2D", 327 | copyTexImage2D: "gl.coTI2D", 328 | copyTexSubImage2D: "gl.coTSI2D", 329 | createBuffer: "gl.crB", 330 | createFramebuffer: "gl.crF", 331 | createProgram: "gl.crP", 332 | createRenderbuffer: "gl.crR", 333 | createShader: "gl.crS", 334 | createTexture: "gl.crT", 335 | cullFace: "gl.cuF", 336 | deleteBuffer: "gl.deB", 337 | deleteFramebuffer: "gl.deF", 338 | deleteProgram: "gl.deP", 339 | deleteRenderbuffer: "gl.deR", 340 | deleteShader: "gl.deS", 341 | deleteTexture: "gl.deT", 342 | depthFunc: "gl.deF", 343 | depthMask: "gl.deM", 344 | depthRange: "gl.deR", 345 | detachShader: "gl.deS", 346 | disable: "gl.di", 347 | disableVertexAttribArray: "gl.diVAA", 348 | drawArrays: "gl.drA", 349 | drawElements: "gl.drE", 350 | enable: "gl.en", 351 | enableVertexAttribArray: "gl.enVAA", 352 | finish: "gl.fi", 353 | flush: "gl.fl", 354 | framebufferRenderbuffer: "gl.frR", 355 | framebufferTexture2D: "gl.frT2D", 356 | frontFace: "gl.frF", 357 | generateMipmap: "gl.geM", 358 | getActiveAttrib: "gl.geAA", 359 | getActiveUniform: "gl.geAU", 360 | getAttachedShaders: "gl.geAS", 361 | getAttribLocation: "gl.geAL", 362 | getBufferParameter: "gl.geBP", 363 | getContextAttributes: "gl.geCA", 364 | getError: "gl.geE", 365 | getExtension: "gl.geE", 366 | getFramebufferAttachmentParameter: "gl.geFAP", 367 | getParameter: "gl.geP", 368 | getProgramParameter: "gl.gePP", 369 | getProgramInfoLog: "gl.gePIL", 370 | getRenderbufferParameter: "gl.geRP", 371 | getShaderParameter: "gl.geSP", 372 | getShaderInfoLog: "gl.geSIL", 373 | getShaderPrecisionFormat: "gl.geSPF", 374 | getShaderSource: "gl.geSS", 375 | getSupportedExtensions: "gl.geSE", 376 | getTexParameter: "gl.geTP", 377 | getUniform: "gl.geU", 378 | getUniformLocation: "gl.geUL", 379 | getVertexAttrib: "gl.geVA", 380 | getVertexAttribOffset: "gl.geVAO", 381 | hint: "gl.hi", 382 | isBuffer: "gl.isB", 383 | isContextLost: "gl.isCL", 384 | isEnabled: "gl.isE", 385 | isFramebuffer: "gl.isF", 386 | isProgram: "gl.isP", 387 | isRenderbuffer: "gl.isR", 388 | isShader: "gl.isS", 389 | isTexture: "gl.isT", 390 | lineWidth: "gl.liW", 391 | linkProgram: "gl.liP", 392 | pixelStorei: "gl.piS", 393 | polygonOffset: "gl.poO", 394 | readPixels: "gl.reP", 395 | renderbufferStorage: "gl.reS", 396 | sampleCoverage: "gl.saC", 397 | scissor: "gl.sc", 398 | shaderSource: "gl.shS", 399 | stencilFunc: "gl.stF", 400 | stencilFuncSeparate: "gl.stFS", 401 | stencilMask: "gl.stM", 402 | stencilMaskSeparate: "gl.stMS", 403 | stencilOp: "gl.stO", 404 | stencilOpSeparate: "gl.stOS", 405 | texParameterf: "gl.teP", 406 | texParameteri: "gl.teP", 407 | texImage2D: "gl.teI2D", 408 | texSubImage2D: "gl.teSI2D", 409 | uniform1f: "gl.un1f", 410 | uniform1fv: "gl.un1fv", 411 | uniform1i: "gl.un1i", 412 | uniform1iv: "gl.un1iv", 413 | uniform2f: "gl.un2f", 414 | uniform2fv: "gl.un2fv", 415 | uniform2i: "gl.un2i", 416 | uniform2iv: "gl.un2iv", 417 | uniform3f: "gl.un3f", 418 | uniform3fv: "gl.un3fv", 419 | uniform3i: "gl.un3i", 420 | uniform3iv: "gl.un3iv", 421 | uniform4f: "gl.un4f", 422 | uniform4fv: "gl.un4fv", 423 | uniform4i: "gl.un4i", 424 | uniform4iv: "gl.un4iv", 425 | uniformMatrix2fv: "gl.unM2fv", 426 | uniformMatrix3fv: "gl.unM3fv", 427 | uniformMatrix4fv: "gl.unM4fv", 428 | useProgram: "gl.usP", 429 | validateProgram: "gl.vaP", 430 | vertexAttrib1f: "gl.veA1f", 431 | vertexAttrib1fv: "gl.veA1fv", 432 | vertexAttrib2f: "gl.veA2f", 433 | vertexAttrib2fv: "gl.veA2fv", 434 | vertexAttrib3f: "gl.veA3f", 435 | vertexAttrib3fv: "gl.veA3fv", 436 | vertexAttrib4f: "gl.veA4f", 437 | vertexAttrib4fv: "gl.veA4fv", 438 | vertexAttribPointer: "gl.veAP", 439 | viewport: "gl.vi" 440 | }; 441 | 442 | const sonantxr_replace = { 443 | rowLen: '_rl', 444 | endPattern: '_ep', 445 | songData: '_sd', 446 | osc1_oct: '_1o', 447 | osc1_det: '_1d', 448 | osc1_detune: '_1t', 449 | osc1_xenv: '_1x', 450 | osc1_vol: '_1v', 451 | osc1_waveform: '_1w', 452 | osc2_oct: '_2o', 453 | osc2_det: '_2d', 454 | osc2_detune: '_2t', 455 | osc2_xenv: '_2x', 456 | osc2_vol: '_2v', 457 | osc2_waveform: '_2w', 458 | noise_fader: '_nf', 459 | env_attack: '_ea', 460 | env_sustain: '_es', 461 | env_release: '_er', 462 | env_master: '_em', 463 | fx_filter: '_ff', 464 | fx_freq: '_fq', 465 | fx_resonance: '_fr', 466 | fx_delay_time: '_ft', 467 | fx_delay_amt: '_fa', 468 | fx_pan_freq: '_fp', 469 | fx_pan_amt: '_fm', 470 | lfo_osc1_freq: '_lf', 471 | lfo_fx_freq: '_lx', 472 | lfo_freq: '_lq', 473 | lfo_amt: '_la', 474 | lfo_waveform: '_lw' 475 | }; 476 | 477 | const fs = require('fs'); 478 | 479 | fs.readFile(process.argv[2], 'utf8', (err, data) => { 480 | for (var name in webgl_replace) { 481 | var d = webgl_replace[name]; 482 | data = data.replace(new RegExp('\\bgl\\.'+name+'\\b', 'g'), d); 483 | } 484 | for (var name in sonantxr_replace) { 485 | var d = sonantxr_replace[name]; 486 | data = data.replace(new RegExp('\\b'+name+'\\b', 'g'), d); 487 | } 488 | console.log(data); 489 | process.exit(0); 490 | }); -------------------------------------------------------------------------------- /source/audio.js: -------------------------------------------------------------------------------- 1 | var audio_ctx = new (window.webkitAudioContext||window.AudioContext)(), 2 | audio_sfx_shoot, 3 | audio_sfx_hit, 4 | audio_sfx_hurt, 5 | audio_sfx_beep, 6 | audio_sfx_pickup, 7 | audio_sfx_terminal, 8 | audio_sfx_explode; 9 | 10 | function audio_init(callback) { 11 | sonantxr_generate_song(audio_ctx, music_dark_meat_beat, function(buffer){ 12 | audio_play(buffer, true); 13 | callback(); 14 | }); 15 | sonantxr_generate_sound(audio_ctx, sound_shoot, 140, function(buffer){ 16 | audio_sfx_shoot = buffer; 17 | }); 18 | sonantxr_generate_sound(audio_ctx, sound_hit, 134, function(buffer){ 19 | audio_sfx_hit = buffer; 20 | }); 21 | sonantxr_generate_sound(audio_ctx, sound_beep, 173, function(buffer){ 22 | audio_sfx_beep = buffer; 23 | }); 24 | sonantxr_generate_sound(audio_ctx, sound_hurt, 144, function(buffer){ 25 | audio_sfx_hurt = buffer; 26 | }); 27 | sonantxr_generate_sound(audio_ctx, sound_pickup, 156, function(buffer){ 28 | audio_sfx_pickup = buffer; 29 | }); 30 | sonantxr_generate_sound(audio_ctx, sound_terminal, 156, function(buffer){ 31 | audio_sfx_terminal = buffer; 32 | }); 33 | sonantxr_generate_sound(audio_ctx, sound_explode, 114, function(buffer){ 34 | audio_sfx_explode = buffer; 35 | }); 36 | }; 37 | 38 | function audio_play(buffer, loop) { 39 | var source = audio_ctx.createBufferSource(); 40 | source.buffer = buffer; 41 | source.loop = loop; 42 | source.connect(audio_ctx.destination); 43 | source.start(); 44 | }; -------------------------------------------------------------------------------- /source/entity-cpu.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_cpu_t extends entity_t { 3 | _init() { 4 | this._animation_time = 0; 5 | } 6 | 7 | _render() { 8 | this._animation_time += time_elapsed; 9 | 10 | push_block(this.x, this.z, 4, 17); 11 | var intensity = this.h == 5 12 | ? 0.02 + _math.sin(this._animation_time*10+_math.random()*2) * 0.01 13 | : 0.01; 14 | push_light(this.x + 4, 4, this.z + 12, 0.2, 0.4, 1.0, intensity); 15 | } 16 | 17 | _check(other) { 18 | 19 | if (this.h == 5 && other instanceof(entity_player_t)) { 20 | this.h = 10; 21 | cpus_rebooted++; 22 | 23 | var reboot_message = 24 | '\n\n\nREBOOTING..._' + 25 | 'SUCCESS\n'; 26 | 27 | if (cpus_total-cpus_rebooted > 0) { 28 | terminal_show_notice( 29 | reboot_message + 30 | (cpus_total-cpus_rebooted)+' SYSTEM(S) STILL OFFLINE' 31 | ); 32 | } 33 | else { 34 | if (current_level != 3) { 35 | terminal_show_notice( 36 | reboot_message + 37 | 'ALL SYSTEMS ONLINE\n' + 38 | 'TRIANGULATING POSITION FOR NEXT HOP...___' + 39 | 'TARGET ACQUIRED\n' + 40 | 'JUMPING...', 41 | next_level 42 | ); 43 | } 44 | else { 45 | terminal_show_notice( 46 | reboot_message + 47 | 'ALL SYSTEMS ONLINE', 48 | next_level 49 | ); 50 | } 51 | } 52 | audio_play(audio_sfx_beep); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /source/entity-explosion.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_explosion_t extends entity_t { 3 | _init() { 4 | this._lifetime = 1; 5 | } 6 | 7 | _update() { 8 | super._update(); 9 | this._lifetime -= time_elapsed; 10 | if (this._lifetime < 0) { 11 | this._kill(); 12 | } 13 | } 14 | 15 | _render() { 16 | push_light(this.x, 4, this.z + 6, 1,0.7,0.3, 0.08*(1-this._lifetime)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /source/entity-health.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_health_t extends entity_t { 3 | _check(other) { 4 | if (other instanceof(entity_player_t)) { 5 | this._kill(); 6 | other.h += other.h < 5 ? 1 : 0; 7 | audio_play(audio_sfx_pickup); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /source/entity-particle.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_particle_t extends entity_t { 3 | _init() { 4 | this._lifetime = 3; 5 | } 6 | 7 | _update() { 8 | this.ay = -320; 9 | 10 | if (this.y < 0) { 11 | this.y = 0; 12 | this.vy = -this.vy * 0.96; 13 | } 14 | super._update(); 15 | this._lifetime -= time_elapsed; 16 | if (this._lifetime < 0) { 17 | this._kill(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /source/entity-plasma.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_plasma_t extends entity_t { 3 | _init(angle) { 4 | var speed = 96; 5 | this.vx = _math.cos(angle) * speed; 6 | this.vz = _math.sin(angle) * speed; 7 | } 8 | 9 | _render() { 10 | super._render(); 11 | push_light(this.x, 4, this.z + 6, 0.9, 0.2, 0.1, 0.04); 12 | } 13 | 14 | _did_collide() { 15 | this._kill(); 16 | } 17 | 18 | _check(other) { 19 | if (other instanceof(entity_spider_t) || other instanceof(entity_sentry_t)) { 20 | audio_play(audio_sfx_hit); 21 | other._receive_damage(this, 1); 22 | this._kill(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /source/entity-player.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_player_t extends entity_t { 3 | _init() { 4 | this._bob = this._last_shot = this._last_damage = this._frame = 0; 5 | } 6 | 7 | _update() { 8 | var t = this, 9 | speed = 128; 10 | 11 | // movement 12 | t.ax = keys[key_left] ? -speed : keys[key_right] ? speed : 0; 13 | t.az = keys[key_up] ? -speed : keys[key_down] ? speed : 0; 14 | 15 | // rotation - select appropriate sprite 16 | var angle = _math.atan2( 17 | mouse_y - (-34 + c.height * 0.8), 18 | mouse_x - (t.x + 6 + camera_x + c.width * 0.5) 19 | ); 20 | t.s = 18 + ((angle / _math.PI * 4 + 10.5) % 8)|0; 21 | 22 | // bobbing 23 | t._bob += time_elapsed * 1.75 * (_math.abs(t.vx) + _math.abs(t.vz)); 24 | t.y = _math.sin(t._bob) * 0.25; 25 | 26 | t._last_damage -= time_elapsed; 27 | t._last_shot -= time_elapsed; 28 | 29 | if (keys[key_shoot] && t._last_shot < 0) { 30 | audio_play(audio_sfx_shoot); 31 | new entity_plasma_t(t.x, 0, t.z, 0, 26, angle + _math.random() * 0.2 - 0.11); 32 | t._last_shot = 0.1; 33 | } 34 | 35 | super._update(); 36 | } 37 | 38 | _render() { 39 | this._frame++; 40 | if (this._last_damage < 0 || this._frame % 6 < 4) { 41 | super._render(); 42 | } 43 | push_light(this.x, 4, this.z + 6, 1,0.5,0, 0.04); 44 | } 45 | 46 | _kill() { 47 | super._kill(); 48 | this.y = 10; 49 | this.z += 5; 50 | terminal_show_notice( 51 | 'DEPLOYMENT FAILED\n' + 52 | 'RESTORING BACKUP...' 53 | ); 54 | setTimeout(reload_level, 3000); 55 | } 56 | 57 | _receive_damage(from, amount) { 58 | if (this._last_damage < 0) { 59 | audio_play(audio_sfx_hurt); 60 | super._receive_damage(from, amount); 61 | this._last_damage = 2; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /source/entity-sentry.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_sentry_t extends entity_t { 3 | _init() { 4 | this._select_target_counter = 0; 5 | this._target_x = this.x; 6 | this._target_z = this.z; 7 | this.h = 20; 8 | } 9 | 10 | _update() { 11 | var t = this, 12 | txd = t.x - t._target_x, 13 | tzd = t.z - t._target_z, 14 | xd = t.x - entity_player.x, 15 | zd = t.z - entity_player.z, 16 | dist = _math.sqrt(xd * xd + zd * zd); 17 | 18 | t._select_target_counter -= time_elapsed; 19 | 20 | // select new target after a while 21 | if (t._select_target_counter < 0) { 22 | if (dist < 64) { 23 | t._select_target_counter = _math.random() * 0.5 + 0.3; 24 | t._target_x = entity_player.x; 25 | t._target_z = entity_player.z; 26 | } 27 | if (dist < 48) { 28 | var angle = _math.atan2( 29 | entity_player.z - this.z, 30 | entity_player.x - this.x 31 | ); 32 | new entity_sentry_plasma_t(t.x, 0, t.z, 0, 26, angle + _math.random() * 0.2 - 0.11); 33 | } 34 | } 35 | 36 | // set velocity towards target 37 | if (dist > 24) { 38 | t.ax = _math.abs(txd) > 2 ? (txd > 0 ? -48 : 48) : 0; 39 | t.az = _math.abs(tzd) > 2 ? (tzd > 0 ? -48 : 48) : 0; 40 | } else { 41 | t.ax = t.az = 0; 42 | } 43 | 44 | super._update(); 45 | } 46 | 47 | _receive_damage(from, amount) { 48 | super._receive_damage(from, amount); 49 | this.vx = from.vx * 0.1; 50 | this.vz = from.vz * 0.1; 51 | this._spawn_particles(3); 52 | } 53 | 54 | _kill() { 55 | super._kill(); 56 | new entity_explosion_t(this.x, 0, this.z, 0, 26); 57 | camera_shake = 3; 58 | audio_play(audio_sfx_explode); 59 | } 60 | } 61 | 62 | class entity_sentry_plasma_t extends entity_t { 63 | _init(angle) { 64 | var speed = 64; 65 | this.vx = _math.cos(angle) * speed; 66 | this.vz = _math.sin(angle) * speed; 67 | } 68 | 69 | _render() { 70 | super._render(); 71 | push_light(this.x, 4, this.z + 6, 1.5, 0.2, 0.1, 0.04); 72 | } 73 | 74 | _did_collide() { 75 | this._kill(); 76 | } 77 | 78 | _check(other) { 79 | if (other instanceof(entity_player_t)) { 80 | other._receive_damage(this, 1); 81 | this._kill(); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /source/entity-spider.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_spider_t extends entity_t { 3 | _init() { 4 | this._animation_time = 0; 5 | this._select_target_counter = 0; 6 | this._target_x = this.x; 7 | this._target_z = this.z; 8 | } 9 | 10 | _update() { 11 | var t = this, 12 | txd = t.x - t._target_x, 13 | tzd = t.z - t._target_z, 14 | xd = t.x - entity_player.x, 15 | zd = t.z - entity_player.z, 16 | dist = _math.sqrt(xd * xd + zd * zd); 17 | 18 | t._select_target_counter -= time_elapsed; 19 | 20 | // select new target after a while 21 | if (t._select_target_counter < 0 && dist < 64) { 22 | t._select_target_counter = _math.random() * 0.5 + 0.3; 23 | t._target_x = entity_player.x; 24 | t._target_z = entity_player.z; 25 | } 26 | 27 | // set velocity towards target 28 | t.ax = _math.abs(txd) > 2 ? (txd > 0 ? -160 : 160) : 0; 29 | t.az = _math.abs(tzd) > 2 ? (tzd > 0 ? -160 : 160) : 0; 30 | 31 | super._update(); 32 | this._animation_time += time_elapsed; 33 | this.s = 27 + ((this._animation_time*15)|0)%3; 34 | } 35 | 36 | _receive_damage(from, amount) { 37 | super._receive_damage(from, amount); 38 | this.vx = from.vx; 39 | this.vz = from.vz; 40 | this._spawn_particles(5); 41 | } 42 | 43 | _check(other) { 44 | // slightly bounce off from other spiders to separate them 45 | if (other instanceof entity_spider_t) { 46 | var 47 | axis = (_math.abs(other.x - this.x) > _math.abs(other.z - this.z) 48 | ? 'x' 49 | : 'z'), 50 | amount = this[axis] > other[axis] ? 0.6 : -0.6; 51 | 52 | this['v'+axis] += amount; 53 | other['v'+axis] -= amount; 54 | } 55 | 56 | // hurt player 57 | else if (other instanceof entity_player_t) { 58 | this.vx *= -1.5; 59 | this.vz *= -1.5; 60 | other._receive_damage(this, 1); 61 | } 62 | } 63 | 64 | _kill() { 65 | super._kill(); 66 | new entity_explosion_t(this.x, 0, this.z, 0, 26); 67 | camera_shake = 1; 68 | audio_play(audio_sfx_explode); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /source/entity.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_t { 3 | constructor(x, y, z, friction, sprite, init_param) { 4 | var t = this; 5 | t.x = x; t.y = y; t.z = z; 6 | t.vx = t.vy = t.vz = t.ax = t.ay = t.az = 0; 7 | t.f = friction; 8 | t.s = sprite; 9 | t.h = 5; 10 | 11 | t._init(init_param); 12 | entities.push(t); 13 | } 14 | 15 | // separate _init() method, because "constructor" cannot be uglyfied 16 | _init(init_param) {} 17 | 18 | _update() { 19 | var t = this, 20 | last_x = t.x, last_z = t.z; 21 | 22 | // velocity 23 | t.vx += t.ax * time_elapsed - t.vx * _math.min(t.f * time_elapsed, 1); 24 | t.vy += t.ay * time_elapsed - t.vy * _math.min(t.f * time_elapsed, 1); 25 | t.vz += t.az * time_elapsed - t.vz * _math.min(t.f * time_elapsed, 1); 26 | 27 | // position 28 | t.x += t.vx * time_elapsed; 29 | t.y += t.vy * time_elapsed; 30 | t.z += t.vz * time_elapsed; 31 | 32 | // check wall collissions, horizontal 33 | if (t._collides(t.x, last_z)) { 34 | t._did_collide(t.x, t.y); 35 | t.x = last_x; 36 | t.vx = 0; 37 | } 38 | 39 | // check wall collissions, vertical 40 | if (t._collides(t.x, t.z)) { 41 | t._did_collide(t.x, t.y); 42 | t.z = last_z; 43 | t.vz = 0; 44 | } 45 | } 46 | 47 | _collides(x, z) { 48 | return level_data[(x >> 3) + (z >> 3) * level_width] > 7 || // top left 49 | level_data[((x + 6) >> 3) + (z >> 3) * level_width] > 7 || // top right 50 | level_data[((x + 6) >> 3) + ((z+4) >> 3) * level_width] > 7 || // bottom right 51 | level_data[(x >> 3) + ((z+4) >> 3) * level_width] > 7; // bottom left 52 | } 53 | 54 | _spawn_particles(amount) { 55 | for (var i = 0; i < amount; i++) { 56 | var particle = new entity_particle_t(this.x, 0, this.z, 1, 30); 57 | particle.vx = (_math.random() - 0.5) * 128; 58 | particle.vy = _math.random() * 96; 59 | particle.vz = (_math.random() - 0.5) * 128; 60 | } 61 | } 62 | 63 | // collision against static walls 64 | _did_collide() {} 65 | 66 | // collision against other entities 67 | _check(other) {} 68 | 69 | _receive_damage(from, amount) { 70 | this.h -= amount; 71 | if (this.h <= 0) { 72 | this._kill(); 73 | } 74 | } 75 | 76 | _kill() { 77 | if (!this._dead) { 78 | this._dead = true; 79 | entities_to_kill.push(this); 80 | } 81 | } 82 | 83 | _render() { // render 84 | var t = this; 85 | push_sprite(t.x-1, t.y, t.z, t.s); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /source/game.js: -------------------------------------------------------------------------------- 1 | 2 | var udef, // global undefined 3 | _math = Math, 4 | _document = document, 5 | _temp, 6 | 7 | keys = {37: 0, 38: 0, 39: 0, 40: 0}, 8 | key_up = 38, key_down = 40, key_left = 37, key_right = 39, key_shoot = 512, 9 | key_convert = {65: 37, 87: 38, 68: 39, 83: 40}, // convert AWDS to left up down right 10 | mouse_x = 0, mouse_y = 0, 11 | 12 | time_elapsed, 13 | time_last = performance.now(), 14 | 15 | level_width = 64, 16 | level_height = 64, 17 | level_data = new Uint8Array(level_width * level_height), 18 | 19 | cpus_total = 0, 20 | cpus_rebooted = 0, 21 | 22 | current_level = 0, 23 | entity_player, 24 | entities = [], 25 | entities_to_kill = []; 26 | 27 | function load_image(name, callback) { 28 | _temp = new Image(); 29 | _temp.src = 'm/'+name+'.png'; 30 | _temp.onload = callback; 31 | } 32 | 33 | function next_level(callback) { 34 | if (current_level == 3) { 35 | entities_to_kill.push(entity_player); 36 | terminal_run_outro(); 37 | } 38 | else { 39 | current_level++; 40 | load_level(current_level, callback); 41 | 42 | } 43 | } 44 | 45 | function load_level(id, callback) { 46 | random_seed(0xBADC0DE1 + id); 47 | load_image('l'+id, function(){ 48 | entities = []; 49 | num_verts = 0; 50 | num_lights = 0; 51 | 52 | cpus_total = 0; 53 | cpus_rebooted = 0; 54 | 55 | _temp = _document.createElement('canvas'); 56 | _temp.width = _temp.height = level_width; // assume square levels 57 | _temp = _temp.getContext('2d') 58 | _temp.drawImage(this, 0, 0); 59 | _temp =_temp.getImageData(0, 0, level_width, level_height).data; 60 | 61 | for (var y = 0, index = 0; y < level_height; y++) { 62 | for (var x = 0; x < level_width; x++, index++) { 63 | 64 | // reduce to 12 bit color to accurately match 65 | var color_key = 66 | ((_temp[index*4]>>4) << 8) + 67 | ((_temp[index*4+1]>>4) << 4) + 68 | (_temp[index*4+2]>>4); 69 | 70 | if (color_key !== 0) { 71 | var tile = level_data[index] = 72 | color_key === 0x888 // wall 73 | ? random_int(0,5) < 4 ? 8 : random_int(8, 17) 74 | : array_rand([1,1,1,1,1,3,3,2,5,5,5,5,5,5,7,7,6]); // floor 75 | 76 | 77 | if (tile > 7) { // walls 78 | push_block(x * 8, y * 8, 4, tile-1); 79 | } 80 | else if (tile > 0) { // floor 81 | push_floor(x * 8, y * 8, tile-1); 82 | 83 | // enemies and items 84 | if (random_int(0, 16 - (id * 2)) == 0) { 85 | new entity_spider_t(x*8, 0, y*8, 5, 27); 86 | } 87 | else if (random_int(0, 100) == 0) { 88 | new entity_health_t(x*8, 0, y*8, 5, 31); 89 | } 90 | } 91 | 92 | // cpu 93 | if (color_key === 0x00f) { 94 | level_data[index] = 8; 95 | new entity_cpu_t(x*8, 0, y*8, 0, 18); 96 | cpus_total++; 97 | } 98 | 99 | // sentry 100 | if (color_key === 0xf00) { 101 | new entity_sentry_t(x*8, 0, y*8, 5, 32); 102 | } 103 | 104 | // player start position (blue) 105 | if (color_key === 0x0f0) { 106 | entity_player = new entity_player_t(x*8, 0, y*8, 5, 18); 107 | } 108 | } 109 | } 110 | } 111 | 112 | // Remove all spiders that spawned close to the player start 113 | for (var i = 0; i < entities.length; i++) { 114 | var e = entities[i]; 115 | if ( 116 | e instanceof(entity_spider_t) && 117 | _math.abs(e.x - entity_player.x) < 64 && 118 | _math.abs(e.z - entity_player.z) < 64 119 | ) { 120 | entities_to_kill.push(e); 121 | } 122 | } 123 | 124 | camera_x = -entity_player.x; 125 | camera_y = -300; 126 | camera_z = -entity_player.z - 100; 127 | 128 | level_num_verts = num_verts; 129 | 130 | terminal_show_notice( 131 | 'SCANNING FOR OFFLINE SYSTEMS...___' + 132 | (cpus_total)+' SYSTEMS FOUND' 133 | ); 134 | callback && callback(); 135 | }); 136 | } 137 | 138 | function reload_level() { 139 | load_level(current_level); 140 | } 141 | 142 | function preventDefault(ev) { 143 | ev.preventDefault(); 144 | } 145 | 146 | _document.onkeydown = function(ev){ 147 | _temp = ev.keyCode; 148 | _temp = key_convert[_temp] || _temp; 149 | if (keys[_temp] !== udef) { 150 | keys[_temp] = 1; 151 | preventDefault(ev); 152 | } 153 | } 154 | 155 | _document.onkeyup = function(ev) { 156 | _temp = ev.keyCode; 157 | _temp = key_convert[_temp] || _temp; 158 | if (keys[_temp] !== udef) { 159 | keys[_temp] = 0; 160 | preventDefault(ev); 161 | } 162 | } 163 | 164 | _document.onmousemove = function(ev) { 165 | mouse_x = (ev.clientX / c.clientWidth) * c.width; 166 | mouse_y = (ev.clientY / c.clientHeight) * c.height; 167 | } 168 | 169 | _document.onmousedown = function(ev) { 170 | keys[key_shoot] = 1; 171 | preventDefault(ev); 172 | } 173 | 174 | _document.onmouseup = function(ev) { 175 | keys[key_shoot] = 0; 176 | preventDefault(ev); 177 | } 178 | 179 | function game_tick() { 180 | var time_now = performance.now(); 181 | time_elapsed = (time_now - time_last)/1000; 182 | time_last = time_now; 183 | 184 | renderer_prepare_frame(); 185 | 186 | // update and render entities 187 | for (var i = 0, e1, e2; i < entities.length; i++) { 188 | e1 = entities[i]; 189 | if (e1._dead) { continue; } 190 | e1._update(); 191 | 192 | // check for collisions between entities - it's quadratic and nobody cares \o/ 193 | for (var j = i+1; j < entities.length; j++) { 194 | e2 = entities[j]; 195 | if(!( 196 | e1.x >= e2.x + 9 || 197 | e1.x + 9 <= e2.x || 198 | e1.z >= e2.z + 9 || 199 | e1.z + 9 <= e2.z 200 | )) { 201 | e1._check(e2); 202 | e2._check(e1); 203 | } 204 | } 205 | 206 | e1._render(); 207 | } 208 | 209 | // center camera on player, apply damping 210 | camera_x = camera_x * 0.92 - entity_player.x * 0.08; 211 | camera_y = camera_y * 0.92 - entity_player.y * 0.08; 212 | camera_z = camera_z * 0.92 - entity_player.z * 0.08; 213 | 214 | // add camera shake 215 | camera_shake *= 0.9; 216 | camera_x += camera_shake * (_math.random()-0.5); 217 | camera_z += camera_shake * (_math.random()-0.5); 218 | 219 | // health bar, render with plasma sprite 220 | for (var i = 0; i < entity_player.h; i++) { 221 | push_sprite(-camera_x - 50 + i * 4, 29-camera_y, -camera_z-30, 26); 222 | } 223 | 224 | renderer_end_frame(); 225 | 226 | 227 | // remove dead entities 228 | entities = entities.filter(function(entity) { 229 | return entities_to_kill.indexOf(entity) === -1; 230 | }); 231 | entities_to_kill = []; 232 | 233 | requestAnimationFrame(game_tick); 234 | } 235 | -------------------------------------------------------------------------------- /source/html-template.html: -------------------------------------------------------------------------------- 1 | UNDERRUN 2 | 22 | 23 | 26 | -------------------------------------------------------------------------------- /source/main.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | terminal_write_line('INITIATING...'); 4 | 5 | audio_init(function(){ 6 | _document.onclick = function() { 7 | _document.onclick = null; 8 | terminal_cancel(); 9 | terminal_write_line('INITIATING...', function(){ 10 | renderer_init(); 11 | 12 | load_image('q2', function() { 13 | terminal_hide(); 14 | renderer_bind_image(this); 15 | next_level(game_tick); 16 | }); 17 | 18 | }); 19 | }; 20 | 21 | terminal_run_intro(); 22 | }); 23 | 24 | 25 | -------------------------------------------------------------------------------- /source/music-dark-meat-beat.js: -------------------------------------------------------------------------------- 1 | var music_dark_meat_beat = { 2 | rowLen: 5513, 3 | endPattern: 25, 4 | songData: [ 5 | { 6 | osc1_oct: 7, 7 | osc1_det: 0, 8 | osc1_detune: 0, 9 | osc1_xenv: 0, 10 | osc1_vol: 255, 11 | osc1_waveform: 2, 12 | osc2_oct: 8, 13 | osc2_det: 0, 14 | osc2_detune: 18, 15 | osc2_xenv: 0, 16 | osc2_vol: 255, 17 | osc2_waveform: 3, 18 | noise_fader: 0, 19 | env_attack: 21074, 20 | env_sustain: 56363, 21 | env_release: 100000, 22 | env_master: 199, 23 | fx_filter: 2, 24 | fx_freq: 948, 25 | fx_resonance: 92, 26 | fx_delay_time: 7, 27 | fx_delay_amt: 60, 28 | fx_pan_freq: 3, 29 | fx_pan_amt: 100, 30 | lfo_osc1_freq: 0, 31 | lfo_fx_freq: 1, 32 | lfo_freq: 7, 33 | lfo_amt: 138, 34 | lfo_waveform: 3, 35 | p: [2,3,4,5,2,3,4,5,2,3,4,5,2,3,4,5,2,3,4,5,2,3,4,5], 36 | c: [ 37 | { 38 | n: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] 39 | }, 40 | { 41 | n: [122,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,121,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] 42 | }, 43 | { 44 | n: [114,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] 45 | }, 46 | { 47 | n: [119,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,121,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] 48 | }, 49 | { 50 | n: [114,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,117,0,0,0,0,0,0,0] 51 | } 52 | ] 53 | }, 54 | { 55 | osc1_oct: 7, 56 | osc1_det: 0, 57 | osc1_detune: 0, 58 | osc1_xenv: 0, 59 | osc1_vol: 192, 60 | osc1_waveform: 3, 61 | osc2_oct: 4, 62 | osc2_det: 0, 63 | osc2_detune: 0, 64 | osc2_xenv: 0, 65 | osc2_vol: 57, 66 | osc2_waveform: 0, 67 | noise_fader: 0, 68 | env_attack: 100, 69 | env_sustain: 150, 70 | env_release: 13636, 71 | env_master: 191, 72 | fx_filter: 2, 73 | fx_freq: 5839, 74 | fx_resonance: 254, 75 | fx_delay_time: 4, 76 | fx_delay_amt: 121, 77 | fx_pan_freq: 6, 78 | fx_pan_amt: 147, 79 | lfo_osc1_freq: 0, 80 | lfo_fx_freq: 0, 81 | lfo_freq: 6, 82 | lfo_amt: 195, 83 | lfo_waveform: 0, 84 | p: [2,0,2,0,2,0,2,0,2,0,2,0,2,0,2,0,2,0,2], 85 | c: [ 86 | { 87 | n: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] 88 | }, 89 | { 90 | n: [131,0,131,0,131,0,0,0,133,0,134,0,134,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] 91 | } 92 | ] 93 | }, 94 | { 95 | osc1_oct: 7, 96 | osc1_det: 2, 97 | osc1_detune: 0, 98 | osc1_xenv: 1, 99 | osc1_vol: 196, 100 | osc1_waveform: 0, 101 | osc2_oct: 7, 102 | osc2_det: 0, 103 | osc2_detune: 0, 104 | osc2_xenv: 1, 105 | osc2_vol: 255, 106 | osc2_waveform: 0, 107 | noise_fader: 0, 108 | env_attack: 100, 109 | env_sustain: 0, 110 | env_release: 3636, 111 | env_master: 254, 112 | fx_filter: 2, 113 | fx_freq: 612, 114 | fx_resonance: 254, 115 | fx_delay_time: 6, 116 | fx_delay_amt: 27, 117 | fx_pan_freq: 0, 118 | fx_pan_amt: 0, 119 | lfo_osc1_freq: 0, 120 | lfo_fx_freq: 0, 121 | lfo_freq: 0, 122 | lfo_amt: 0, 123 | lfo_waveform: 0, 124 | p: [1,1,1,1,1,1,1,1,1,1,1,1], 125 | c: [ 126 | { 127 | n: [140,0,0,0,0,0,0,0,140,0,0,0,0,0,0,0,140,0,0,0,0,0,0,0,140,0,0,0,0,0,0,0] 128 | } 129 | ] 130 | }, 131 | { 132 | osc1_oct: 7, 133 | osc1_det: 0, 134 | osc1_detune: 0, 135 | osc1_xenv: 0, 136 | osc1_vol: 77, 137 | osc1_waveform: 1, 138 | osc2_oct: 2, 139 | osc2_det: 0, 140 | osc2_detune: 188, 141 | osc2_xenv: 0, 142 | osc2_vol: 7, 143 | osc2_waveform: 0, 144 | noise_fader: 21, 145 | env_attack: 53732, 146 | env_sustain: 0, 147 | env_release: 14545, 148 | env_master: 13, 149 | fx_filter: 0, 150 | fx_freq: 0, 151 | fx_resonance: 240, 152 | fx_delay_time: 2, 153 | fx_delay_amt: 222, 154 | fx_pan_freq: 3, 155 | fx_pan_amt: 47, 156 | lfo_osc1_freq: 0, 157 | lfo_fx_freq: 0, 158 | lfo_freq: 0, 159 | lfo_amt: 0, 160 | lfo_waveform: 0, 161 | p: [0,0,0,0,2,4,2,3,2,4,2,3,2,4,2,3,2,4,2,3,2,4,2,3], 162 | c: [ 163 | { 164 | n: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] 165 | }, 166 | { 167 | n: [131,0,131,0,131,0,0,0,133,0,134,0,134,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] 168 | }, 169 | { 170 | n: [131,0,131,0,131,0,0,0,136,0,134,0,133,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] 171 | }, 172 | { 173 | n: [131,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] 174 | } 175 | ] 176 | }, 177 | { 178 | osc1_oct: 5, 179 | osc1_det: 0, 180 | osc1_detune: 0, 181 | osc1_xenv: 1, 182 | osc1_vol: 20, 183 | osc1_waveform: 0, 184 | osc2_oct: 7, 185 | osc2_det: 0, 186 | osc2_detune: 0, 187 | osc2_xenv: 1, 188 | osc2_vol: 7, 189 | osc2_waveform: 0, 190 | noise_fader: 178, 191 | env_attack: 0, 192 | env_sustain: 6338, 193 | env_release: 15454, 194 | env_master: 100, 195 | fx_filter: 3, 196 | fx_freq: 4352, 197 | fx_resonance: 240, 198 | fx_delay_time: 4, 199 | fx_delay_amt: 99, 200 | fx_pan_freq: 0, 201 | fx_pan_amt: 20, 202 | lfo_osc1_freq: 0, 203 | lfo_fx_freq: 1, 204 | lfo_freq: 7, 205 | lfo_amt: 64, 206 | lfo_waveform: 0, 207 | p: [0,0,0,0,1,1,1,1,1,1,1,1], 208 | c: [ 209 | { 210 | n: [0,0,0,0,0,0,0,0,137,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,137,0,0,0,0,0,0,0] 211 | } 212 | ] 213 | }, 214 | { 215 | osc1_oct: 8, 216 | osc1_det: 0, 217 | osc1_detune: 0, 218 | osc1_xenv: 1, 219 | osc1_vol: 82, 220 | osc1_waveform: 2, 221 | osc2_oct: 8, 222 | osc2_det: 0, 223 | osc2_detune: 0, 224 | osc2_xenv: 0, 225 | osc2_vol: 0, 226 | osc2_waveform: 0, 227 | noise_fader: 125, 228 | env_attack: 100, 229 | env_sustain: 0, 230 | env_release: 9090, 231 | env_master: 232, 232 | fx_filter: 3, 233 | fx_freq: 5200, 234 | fx_resonance: 63, 235 | fx_delay_time: 4, 236 | fx_delay_amt: 131, 237 | fx_pan_freq: 0, 238 | fx_pan_amt: 0, 239 | lfo_osc1_freq: 0, 240 | lfo_fx_freq: 0, 241 | lfo_freq: 0, 242 | lfo_amt: 0, 243 | lfo_waveform: 0, 244 | p: [0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,1], 245 | c: [ 246 | { 247 | n: [141,141,141,141,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] 248 | } 249 | ] 250 | } 251 | ], 252 | songLen: 101 253 | }; -------------------------------------------------------------------------------- /source/random.js: -------------------------------------------------------------------------------- 1 | var rand_high, rand_low; 2 | 3 | function random_int(min, max) { 4 | rand_high = ((rand_high << 16) + (rand_high >> 16) + rand_low) & 0xffffffff; 5 | rand_low = (rand_low + rand_high) & 0xffffffff; 6 | var n = (rand_high >>> 0) / 0xffffffff; 7 | return (min + n * (max-min+1))|0; 8 | } 9 | 10 | function random_seed(seed) { 11 | rand_high = seed || 0xBADC0FFE; 12 | rand_low = seed ^ 0x49616E42; 13 | } 14 | 15 | function array_rand(array) { 16 | return array[random_int(0, array.length-1)]; 17 | } 18 | 19 | -------------------------------------------------------------------------------- /source/renderer.js: -------------------------------------------------------------------------------- 1 | var 2 | gl = c.getContext('webgl') || c.getContext('experimental-webgl'), 3 | vertex_buffer, 4 | shader_program, 5 | 6 | texture_size = 1024, 7 | tile_size = 16, 8 | tile_fraction = tile_size / texture_size, 9 | px_nudge = 0.5 / texture_size, 10 | 11 | max_verts = 1024 * 64, 12 | num_verts = 0, 13 | level_num_verts, 14 | buffer_data = new Float32Array(max_verts*8), // allow 64k verts, 8 properties per vert 15 | 16 | light_uniform, 17 | max_lights = 16, 18 | num_lights = 0, 19 | light_data = new Float32Array(max_lights*7), // 32 lights, 7 properties per light 20 | 21 | 22 | camera_x = 0, camera_y = 0, camera_z = 0, camera_shake = 0, 23 | camera_uniform, 24 | 25 | shader_attribute_vec = 'attribute vec', 26 | shader_varying = 27 | 'precision highp float;' + 28 | 'varying vec3 vl;' + 29 | 'varying vec2 vuv;', 30 | shader_uniform = 'uniform ', 31 | shader_const_mat4 = "const mat4 ", 32 | 33 | vertex_shader = 34 | shader_varying + 35 | shader_attribute_vec + "3 p;" + 36 | shader_attribute_vec + "2 uv;" + 37 | shader_attribute_vec + "3 n;" + 38 | shader_uniform + "vec3 cam;" + 39 | shader_uniform + "float l[7*"+max_lights+"];" + 40 | shader_const_mat4 + "v=mat4(1,0,0,0,0,.707,.707,0,0,-.707,.707,0,0,-22.627,-22.627,1);" + // view 41 | shader_const_mat4 + "r=mat4(.977,0,0,0,0,1.303,0,0,0,0,-1,-1,0,0,-2,0);"+ // projection 42 | "void main(void){" + 43 | "vl=vec3(0.3,0.3,0.6);" + // ambient color 44 | "for(int i=0; i<"+max_lights+"; i++) {"+ 45 | "vec3 lp=vec3(l[i*7],l[i*7+1],l[i*7+2]);" + // light position 46 | "vl+=vec3(l[i*7+3],l[i*7+4],l[i*7+5])" + // light color * 47 | "*max(dot(n,normalize(lp-p)),0.)" + // diffuse * 48 | "*(1./(l[i*7+6]*(" + // attentuation * 49 | "length(lp-p)" + // distance 50 | ")));" + 51 | "}" + 52 | "vuv=uv;" + 53 | "gl_Position=r*v*(vec4(p+cam,1.));" + 54 | "}", 55 | 56 | fragment_shader = 57 | shader_varying + 58 | shader_uniform + "sampler2D s;" + 59 | "void main(void){" + 60 | "vec4 t=texture2D(s,vuv);" + 61 | "if(t.a<.8)" + // 1) discard alpha 62 | "discard;" + 63 | "if(t.r>0.95&&t.g>0.25&&t.b==0.0)" + // 2) red glowing spider eyes 64 | "gl_FragColor=t;" + 65 | "else{" + // 3) calculate color with lights and fog 66 | "gl_FragColor=t*vec4(vl,1.);" + 67 | "gl_FragColor.rgb*=smoothstep(" + 68 | "112.,16.," + // fog far, near 69 | "gl_FragCoord.z/gl_FragCoord.w" + // fog depth 70 | ");" + 71 | "}" + 72 | "gl_FragColor.rgb=floor(gl_FragColor.rgb*6.35)/6.35;" + // reduce colors to ~256 73 | "}"; 74 | 75 | 76 | function renderer_init() { 77 | 78 | // Create shorthand WebGL function names 79 | // var webglShortFunctionNames = {}; 80 | for (var name in gl) { 81 | if (gl[name].length != udef) { 82 | gl[name.match(/(^..|[A-Z]|\d.|v$)/g).join('')] = gl[name]; 83 | // webglShortFunctionNames[name] = 'gl.'+name.match(/(^..|[A-Z]|\d.|v$)/g).join(''); 84 | } 85 | } 86 | // console.log(JSON.stringify(webglShortFunctionNames, null, '\t')); 87 | 88 | vertex_buffer = gl.createBuffer(); 89 | gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer); 90 | gl.bufferData(gl.ARRAY_BUFFER, buffer_data, gl.DYNAMIC_DRAW); 91 | 92 | shader_program = gl.createProgram(); 93 | gl.attachShader(shader_program, compile_shader(gl.VERTEX_SHADER, vertex_shader)); 94 | gl.attachShader(shader_program, compile_shader(gl.FRAGMENT_SHADER, fragment_shader)); 95 | gl.linkProgram(shader_program); 96 | gl.useProgram(shader_program); 97 | 98 | camera_uniform = gl.getUniformLocation(shader_program, "cam"); 99 | light_uniform = gl.getUniformLocation(shader_program, "l"); 100 | 101 | gl.enable(gl.DEPTH_TEST); 102 | gl.enable(gl.BLEND); 103 | gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); 104 | gl.viewport(0,0,c.width,c.height); 105 | 106 | enable_vertex_attrib('p', 3, 8, 0); 107 | enable_vertex_attrib('uv', 2, 8, 3); 108 | enable_vertex_attrib('n', 3, 8, 5); 109 | } 110 | 111 | function renderer_bind_image(image) { 112 | var texture_2d = gl.TEXTURE_2D; 113 | gl.bindTexture(texture_2d, gl.createTexture()); 114 | gl.texImage2D(texture_2d, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); 115 | gl.texParameteri(texture_2d, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 116 | gl.texParameteri(texture_2d, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 117 | gl.texParameteri(texture_2d, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 118 | gl.texParameteri(texture_2d, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 119 | } 120 | 121 | function renderer_prepare_frame() { 122 | num_verts = level_num_verts; 123 | num_lights = 0; 124 | 125 | // reset all lights 126 | light_data.fill(1); 127 | } 128 | 129 | function renderer_end_frame() { 130 | gl.uniform3f(camera_uniform, camera_x, camera_y - 10, camera_z-30); 131 | gl.uniform1fv(light_uniform, light_data); 132 | 133 | gl.clearColor(0,0,0,1); 134 | gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT); 135 | 136 | gl.bufferData(gl.ARRAY_BUFFER, buffer_data, gl.DYNAMIC_DRAW); 137 | gl.drawArrays(gl.TRIANGLES, 0, num_verts); 138 | }; 139 | 140 | function push_quad(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4, nx, ny, nz, tile) { 141 | var u = tile * tile_fraction + px_nudge; 142 | buffer_data.set([ 143 | x1, y1, z1, u, 0, nx, ny, nz, 144 | x2, y2, z2, u + tile_fraction - px_nudge, 0, nx, ny, nz, 145 | x3, y3, z3, u, 1, nx, ny, nz, 146 | x2, y2, z2, u + tile_fraction - px_nudge, 0, nx, ny, nz, 147 | x3, y3, z3, u, 1, nx, ny, nz, 148 | x4, y4, z4, u + tile_fraction - px_nudge, 1, nx, ny, nz 149 | ], num_verts * 8); 150 | num_verts += 6; 151 | }; 152 | 153 | function push_sprite(x, y, z, tile) { 154 | // Only push sprites near to the camera 155 | if ( 156 | _math.abs(-x - camera_x) < 128 && 157 | _math.abs(-z - camera_z) < 128 158 | ) { 159 | var tilt = 3+(camera_z + z)/12; // tilt sprite when closer to camera 160 | push_quad(x, y + 6, z, x + 6, y + 6, z, x, y, z + tilt, x + 6, y, z + tilt, 0, 0, 1, tile); 161 | } 162 | } 163 | 164 | function push_floor(x, z, tile) { 165 | push_quad(x, 0, z, x + 8, 0, z, x, 0, z + 8, x + 8, 0, z + 8, 0,1,0, tile); 166 | }; 167 | 168 | function push_block(x, z, tile_top, tile_sites) { 169 | // tall blocks for certain tiles 170 | var y = ~[8, 9, 17].indexOf(tile_sites) ? 16 : 8; 171 | 172 | push_quad(x, y, z, x + 8, y, z, x, y, z + 8, x + 8, y, z + 8, 0, 1, 0, tile_top); // top 173 | push_quad(x + 8, y, z, x + 8, y, z + 8, x + 8, 0, z, x + 8, 0, z + 8, 1, 0, 0, tile_sites); // right 174 | push_quad(x, y, z + 8, x + 8, y, z + 8, x, 0, z + 8, x + 8, 0, z + 8, 0, 0, 1, tile_sites); // front 175 | push_quad(x, y, z, x, y, z + 8, x, 0, z, x, 0, z + 8, -1, 0, 0, tile_sites); // left 176 | }; 177 | 178 | function push_light(x, y, z, r, g, b, falloff) { 179 | // Only push lights near to the camera 180 | var max_light_distance = (128 + 1/falloff); // cheap ass approximation 181 | if ( 182 | num_lights < max_lights && 183 | _math.abs(-x - camera_x) < max_light_distance && 184 | _math.abs(-z - camera_z) < max_light_distance 185 | ) { 186 | light_data.set([x, y, z, r, g, b, falloff], num_lights*7); 187 | num_lights++; 188 | } 189 | } 190 | 191 | function compile_shader(shader_type, shader_source) { 192 | var shader = gl.createShader(shader_type); 193 | gl.shaderSource(shader, shader_source); 194 | gl.compileShader(shader); 195 | // console.log(gl.getShaderInfoLog(shader)); 196 | return shader; 197 | }; 198 | 199 | function enable_vertex_attrib(attrib_name, count, vertex_size, offset) { 200 | var location = gl.getAttribLocation(shader_program, attrib_name); 201 | gl.enableVertexAttribArray(location); 202 | gl.vertexAttribPointer(location, count, gl.FLOAT, false, vertex_size * 4, offset * 4); 203 | }; 204 | -------------------------------------------------------------------------------- /source/sonantx-reduced.js: -------------------------------------------------------------------------------- 1 | // Gutted for js13k and modified to use Float32 buffers directly 2 | // ~ Dominic Szablewski, phoboslab.org, Sep 2018 3 | 4 | // 5 | // Sonant-X 6 | // 7 | // Copyright (c) 2014 Nicolas Vanhoren 8 | // 9 | // Sonant-X is a fork of js-sonant by Marcus Geelnard and Jake Taylor. It is 10 | // still published using the same license (zlib license, see below). 11 | // 12 | // Copyright (c) 2011 Marcus Geelnard 13 | // Copyright (c) 2008-2009 Jake Taylor 14 | // 15 | // This software is provided 'as-is', without any express or implied 16 | // warranty. In no event will the authors be held liable for any damages 17 | // arising from the use of this software. 18 | // 19 | // Permission is granted to anyone to use this software for any purpose, 20 | // including commercial applications, and to alter it and redistribute it 21 | // freely, subject to the following restrictions: 22 | // 23 | // 1. The origin of this software must not be misrepresented; you must not 24 | // claim that you wrote the original software. If you use this software 25 | // in a product, an acknowledgment in the product documentation would be 26 | // appreciated but is not required. 27 | // 28 | // 2. Altered source versions must be plainly marked as such, and must not be 29 | // misrepresented as being the original software. 30 | // 31 | // 3. This notice may not be removed or altered from any source 32 | // distribution. 33 | 34 | var sonantxr_generate_song, sonantxr_generate_sound; 35 | 36 | (function() { 37 | var WAVE_SPS = 44100; // Samples per second 38 | var MAX_TIME = 33; // maximum time, in millis, that the generator can use consecutively 39 | 40 | var audioCtx = null; 41 | 42 | // Oscillators 43 | function osc_sin(value) 44 | { 45 | return _math.sin(value * 6.283184); 46 | } 47 | 48 | function osc_square(value) 49 | { 50 | return osc_sin(value) < 0 ? -1 : 1; 51 | } 52 | 53 | function osc_saw(value) 54 | { 55 | return (value % 1) - 0.5; 56 | } 57 | 58 | function osc_tri(value) 59 | { 60 | var v2 = (value % 1) * 4; 61 | return v2 < 2 ? v2 - 1 : 3 - v2; 62 | } 63 | 64 | // Array of oscillator functions 65 | var oscillators = 66 | [ 67 | osc_sin, 68 | osc_square, 69 | osc_saw, 70 | osc_tri 71 | ]; 72 | 73 | function getnotefreq(n) 74 | { 75 | return 0.00390625 * _math.pow(1.059463094, n - 128); 76 | } 77 | 78 | function generateBuffer(samples) { 79 | return { 80 | left: new Float32Array(samples), 81 | right: new Float32Array(samples) 82 | }; 83 | } 84 | 85 | function applyDelay(chnBuf, waveSamples, instr, rowLen) { 86 | var p1 = (instr.fx_delay_time * rowLen) >> 1; 87 | var t1 = instr.fx_delay_amt / 255; 88 | 89 | var n1 = 0; 90 | while(n1 < waveSamples - p1) { 91 | var b1 = n1; 92 | var l = (n1 + p1); 93 | chnBuf.left[l] += chnBuf.right[b1] * t1; 94 | chnBuf.right[l] += chnBuf.left[b1] * t1; 95 | n1++; 96 | } 97 | } 98 | 99 | 100 | function getAudioBuffer(ctx, mixBuf) { 101 | var buffer = ctx.createBuffer(2, mixBuf.left.length, WAVE_SPS); // Create Mono Source Buffer from Raw Binary 102 | buffer.getChannelData(0).set(mixBuf.left); 103 | buffer.getChannelData(1).set(mixBuf.right); 104 | return buffer; 105 | } 106 | 107 | var SoundGenerator = function(ctx, instr, rowLen) { 108 | this.ctx = ctx; 109 | this.instr = instr; 110 | this.rowLen = rowLen || 5605; 111 | 112 | this.osc_lfo = oscillators[instr.lfo_waveform]; 113 | this.osc1 = oscillators[instr.osc1_waveform]; 114 | this.osc2 = oscillators[instr.osc2_waveform]; 115 | this.attack = instr.env_attack; 116 | this.sustain = instr.env_sustain; 117 | this.release = instr.env_release; 118 | this.panFreq = _math.pow(2, instr.fx_pan_freq - 8) / this.rowLen; 119 | this.lfoFreq = _math.pow(2, instr.lfo_freq - 8) / this.rowLen; 120 | }; 121 | 122 | SoundGenerator.prototype._genSound = function(n, chnBuf, currentpos) { 123 | var c1 = 0; 124 | var c2 = 0; 125 | 126 | // Precalculate frequencues 127 | var o1t = getnotefreq(n + (this.instr.osc1_oct - 8) * 12 + this.instr.osc1_det) * (1 + 0.0008 * this.instr.osc1_detune); 128 | var o2t = getnotefreq(n + (this.instr.osc2_oct - 8) * 12 + this.instr.osc2_det) * (1 + 0.0008 * this.instr.osc2_detune); 129 | 130 | // State variable init 131 | var q = this.instr.fx_resonance / 255; 132 | var low = 0; 133 | var band = 0; 134 | 135 | var chnbufLength = chnBuf.left.length; 136 | var numSamples = this.attack + this.sustain + this.release - 1; 137 | 138 | for (var j = numSamples; j >= 0; --j) { 139 | var k = j + currentpos; 140 | 141 | // LFO 142 | var lfor = this.osc_lfo(k * this.lfoFreq) * this.instr.lfo_amt / 512 + 0.5; 143 | 144 | // Envelope 145 | var e = 1; 146 | if (j < this.attack) { 147 | e = j / this.attack; 148 | } 149 | else if (j >= this.attack + this.sustain) { 150 | e -= (j - this.attack - this.sustain) / this.release; 151 | } 152 | 153 | // Oscillator 1 154 | var t = o1t; 155 | if (this.instr.lfo_osc1_freq) { 156 | t += lfor; 157 | } 158 | if (this.instr.osc1_xenv) { 159 | t *= e * e 160 | } 161 | c1 += t; 162 | var rsample = this.osc1(c1) * this.instr.osc1_vol; 163 | 164 | // Oscillator 2 165 | t = o2t; 166 | if (this.instr.osc2_xenv) { 167 | t *= e * e; 168 | }; 169 | c2 += t; 170 | rsample += this.osc2(c2) * this.instr.osc2_vol; 171 | 172 | // Noise oscillator 173 | if (this.instr.noise_fader) { 174 | rsample += (2*_math.random()-1) * this.instr.noise_fader * e; 175 | } 176 | 177 | rsample *= e / 255; 178 | 179 | // State variable filter 180 | var f = this.instr.fx_freq; 181 | if (this.instr.lfo_fx_freq) { 182 | f *= lfor; 183 | } 184 | f = 1.5 * _math.sin(f * 3.141592 / WAVE_SPS); 185 | low += f * band; 186 | var high = q * (rsample - band) - low; 187 | band += f * high; 188 | switch (this.instr.fx_filter) { 189 | case 1: // Hipass 190 | rsample = high; 191 | break; 192 | case 2: // Lopass 193 | rsample = low; 194 | break; 195 | case 3: // Bandpass 196 | rsample = band; 197 | break; 198 | case 4: // Notch 199 | rsample = low + high; 200 | break; 201 | default: 202 | } 203 | 204 | // Panning & master volume 205 | t = osc_sin(k * this.panFreq) * this.instr.fx_pan_amt / 512 + 0.5; 206 | rsample *= 0.00476 * this.instr.env_master; // 39 / 8192 = 0.00476 207 | 208 | // Add to 16-bit channel buffer 209 | // k = k * 2; 210 | if (k < chnbufLength) { 211 | chnBuf.left[k] += rsample * (1-t) ; 212 | chnBuf.right[k] += rsample * t; 213 | } 214 | } 215 | }; 216 | 217 | SoundGenerator.prototype._createAudioBuffer = function(n, callBack) { 218 | var bufferSize = (this.attack + this.sustain + this.release - 1) + (32 * this.rowLen); 219 | var buffer = generateBuffer(bufferSize); 220 | this._genSound(n, buffer, 0); 221 | applyDelay(buffer, bufferSize, this.instr, this.rowLen); 222 | 223 | callBack(getAudioBuffer(this.ctx, buffer)); 224 | }; 225 | 226 | 227 | 228 | 229 | var MusicGenerator = function(ctx, song) { 230 | this.ctx = ctx; 231 | this.song = song; 232 | // Wave data configuration 233 | this.waveSize = WAVE_SPS * song.songLen; // Total song size (in samples) 234 | }; 235 | 236 | MusicGenerator.prototype._generateTrack = function (instr, mixBuf, callBack) { 237 | var self = this; 238 | var chnBuf = generateBuffer(this.waveSize); 239 | // Preload/precalc some properties/expressions (for improved performance) 240 | var waveSamples = self.waveSize, 241 | rowLen = self.song.rowLen, 242 | endPattern = self.song.endPattern, 243 | soundGen = new SoundGenerator(self.ctx, instr, rowLen); 244 | 245 | var currentpos = 0; 246 | var p = 0; 247 | var row = 0; 248 | var recordSounds = function() { 249 | var beginning = Date.now(); 250 | while (true) { 251 | if (row === 32) { 252 | row = 0; 253 | p += 1; 254 | continue; 255 | } 256 | if (p === endPattern - 1) { 257 | return finalize(); 258 | } 259 | var cp = instr.p[p]; 260 | if (cp) { 261 | var n = instr.c[cp - 1].n[row]; 262 | if (n) { 263 | soundGen._genSound(n, chnBuf, currentpos); 264 | } 265 | } 266 | currentpos += rowLen; 267 | row += 1; 268 | if (Date.now() - beginning > MAX_TIME) { 269 | setTimeout(recordSounds, 0); 270 | return; 271 | } 272 | } 273 | }; 274 | 275 | var finalize = function() { 276 | applyDelay(chnBuf, waveSamples, instr, rowLen); 277 | for (var b2 = 0; b2 < waveSamples; b2++) { 278 | mixBuf.left[b2] += chnBuf.left[b2]; 279 | } 280 | for (var b2 = 0; b2 < waveSamples; b2++) { 281 | mixBuf.right[b2] += chnBuf.right[b2]; 282 | } 283 | callBack(); 284 | }; 285 | 286 | recordSounds(); 287 | }; 288 | 289 | MusicGenerator.prototype._createAudioBuffer = function(callBack) { 290 | var self = this; 291 | var mixBuf = generateBuffer(this.waveSize); 292 | var track = 0; 293 | 294 | var nextTrack = function() { 295 | if (track < self.song.songData.length) { 296 | track += 1; 297 | self._generateTrack(self.song.songData[track - 1], mixBuf, nextTrack); 298 | } 299 | else { 300 | callBack(getAudioBuffer(self.ctx, mixBuf)); 301 | } 302 | }; 303 | nextTrack(); 304 | }; 305 | 306 | 307 | sonantxr_generate_song = function(audio_ctx, song_data, callback) { 308 | var music_generator = new MusicGenerator(audio_ctx, song_data); 309 | music_generator._createAudioBuffer(callback); 310 | }; 311 | 312 | sonantxr_generate_sound = function(audio_ctx, instrument, note, callback) { 313 | var sound_generator = new SoundGenerator(audio_ctx, instrument); 314 | sound_generator._createAudioBuffer(note, callback); 315 | }; 316 | 317 | })(); 318 | 319 | -------------------------------------------------------------------------------- /source/sound-effects.js: -------------------------------------------------------------------------------- 1 | var 2 | sound_terminal = { 3 | osc1_oct: 6, 4 | osc1_det: 0, 5 | osc1_detune: 0, 6 | osc1_xenv: 0, 7 | osc1_vol: 0, 8 | osc1_waveform: 0, 9 | osc2_oct: 10, 10 | osc2_det: 0, 11 | osc2_detune: 0, 12 | osc2_xenv: 0, 13 | osc2_vol: 168, 14 | osc2_waveform: 3, 15 | noise_fader: 0, 16 | env_attack: 351, 17 | env_sustain: 0, 18 | env_release: 444, 19 | env_master: 192, 20 | fx_filter: 2, 21 | fx_freq: 7355, 22 | fx_resonance: 130, 23 | fx_delay_time: 3, 24 | fx_delay_amt: 36, 25 | fx_pan_freq: 0, 26 | fx_pan_amt: 0, 27 | lfo_osc1_freq: 0, 28 | lfo_fx_freq: 0, 29 | lfo_freq: 0, 30 | lfo_amt: 0, 31 | lfo_waveform: 0 32 | }, 33 | 34 | sound_shoot = { 35 | osc1_oct: 7, 36 | osc1_det: 0, 37 | osc1_detune: 0, 38 | osc1_xenv: 0, 39 | osc1_vol: 192, 40 | osc1_waveform: 0, 41 | osc2_oct: 2, 42 | osc2_det: 0, 43 | osc2_detune: 0, 44 | osc2_xenv: 0, 45 | osc2_vol: 192, 46 | osc2_waveform: 0, 47 | noise_fader: 28, 48 | env_attack: 269, 49 | env_sustain: 0, 50 | env_release: 444, 51 | env_master: 255, 52 | fx_filter: 0, 53 | fx_freq: 272, 54 | fx_resonance: 25, 55 | fx_delay_time: 5, 56 | fx_delay_amt: 29, 57 | fx_pan_freq: 0, 58 | fx_pan_amt: 47, 59 | lfo_osc1_freq: 0, 60 | lfo_fx_freq: 0, 61 | lfo_freq: 0, 62 | lfo_amt: 0, 63 | lfo_waveform: 0 64 | }, 65 | 66 | sound_hit = { 67 | osc1_oct: 8, 68 | osc1_det: 0, 69 | osc1_detune: 0, 70 | osc1_xenv: 1, 71 | osc1_vol: 160, 72 | osc1_waveform: 3, 73 | osc2_oct: 8, 74 | osc2_det: 0, 75 | osc2_detune: 0, 76 | osc2_xenv: 1, 77 | osc2_vol: 99, 78 | osc2_waveform: 2, 79 | noise_fader: 60, 80 | env_attack: 50, 81 | env_sustain: 200, 82 | env_release: 6800, 83 | env_master: 125, 84 | fx_filter: 4, 85 | fx_freq: 11025, 86 | fx_resonance: 254, 87 | fx_delay_time: 0, 88 | fx_delay_amt: 13, 89 | fx_pan_freq: 5, 90 | fx_pan_amt: 0, 91 | lfo_osc1_freq: 0, 92 | lfo_fx_freq: 1, 93 | lfo_freq: 4, 94 | lfo_amt: 60, 95 | lfo_waveform: 0 96 | }, 97 | 98 | sound_beep = { 99 | osc1_oct: 10, 100 | osc1_det: 0, 101 | osc1_detune: 0, 102 | osc1_xenv: 0, 103 | osc1_vol: 192, 104 | osc1_waveform: 2, 105 | osc2_oct: 6, 106 | osc2_det: 0, 107 | osc2_detune: 9, 108 | osc2_xenv: 0, 109 | osc2_vol: 192, 110 | osc2_waveform: 1, 111 | noise_fader: 0, 112 | env_attack: 137, 113 | env_sustain: 2000, 114 | env_release: 4611, 115 | env_master: 140, 116 | fx_filter: 1, 117 | fx_freq: 982, 118 | fx_resonance: 89, 119 | fx_delay_time: 6, 120 | fx_delay_amt: 25, 121 | fx_pan_freq: 6, 122 | fx_pan_amt: 77, 123 | lfo_osc1_freq: 0, 124 | lfo_fx_freq: 1, 125 | lfo_freq: 3, 126 | lfo_amt: 69, 127 | lfo_waveform: 0 128 | }, 129 | 130 | sound_hurt = { 131 | osc1_oct: 7, 132 | osc1_det: 3, 133 | osc1_detune: 140, 134 | osc1_xenv: 1, 135 | osc1_vol: 232, 136 | osc1_waveform: 3, 137 | osc2_oct: 6, 138 | osc2_det: 0, 139 | osc2_detune: 9, 140 | osc2_xenv: 0, 141 | osc2_vol: 30, 142 | osc2_waveform: 1, 143 | noise_fader: 17, 144 | env_attack: 4611, 145 | env_sustain: 1403, 146 | env_release: 34215, 147 | env_master: 256, 148 | fx_filter: 4, 149 | fx_freq: 948, 150 | fx_resonance: 196, 151 | fx_delay_time: 0, 152 | fx_delay_amt: 0, 153 | fx_pan_freq: 0, 154 | fx_pan_amt: 1, 155 | lfo_osc1_freq: 0, 156 | lfo_fx_freq: 1, 157 | lfo_freq: 13, 158 | lfo_amt: 255, 159 | lfo_waveform: 2 160 | }, 161 | 162 | sound_pickup = { 163 | osc1_oct: 5, 164 | osc1_det: 0, 165 | osc1_detune: 0, 166 | osc1_xenv: 1, 167 | osc1_vol: 97, 168 | osc1_waveform: 0, 169 | osc2_oct: 8, 170 | osc2_det: 0, 171 | osc2_detune: 0, 172 | osc2_xenv: 1, 173 | osc2_vol: 204, 174 | osc2_waveform: 0, 175 | noise_fader: 0, 176 | env_attack: 4298, 177 | env_sustain: 927, 178 | env_release: 1403, 179 | env_master: 255, 180 | fx_filter: 2, 181 | fx_freq: 484, 182 | fx_resonance: 134, 183 | fx_delay_time: 3, 184 | fx_delay_amt: 35, 185 | fx_pan_freq: 4, 186 | fx_pan_amt: 72, 187 | lfo_osc1_freq: 0, 188 | lfo_fx_freq: 1, 189 | lfo_freq: 6, 190 | lfo_amt: 231, 191 | lfo_waveform: 0 192 | }, 193 | 194 | sound_explode = { 195 | osc1_oct: 8, 196 | osc1_det: 0, 197 | osc1_detune: 0, 198 | osc1_xenv: 1, 199 | osc1_vol: 147, 200 | osc1_waveform: 1, 201 | osc2_oct: 6, 202 | osc2_det: 0, 203 | osc2_detune: 0, 204 | osc2_xenv: 1, 205 | osc2_vol: 159, 206 | osc2_waveform: 1, 207 | noise_fader: 255, 208 | env_attack: 197, 209 | env_sustain: 1234, 210 | env_release: 21759, 211 | env_master: 232, 212 | fx_filter: 2, 213 | fx_freq: 1052, 214 | fx_resonance: 255, 215 | fx_delay_time: 4, 216 | fx_delay_amt: 73, 217 | fx_pan_freq: 3, 218 | fx_pan_amt: 25, 219 | lfo_osc1_freq: 0, 220 | lfo_fx_freq: 0, 221 | lfo_freq: 0, 222 | lfo_amt: 0, 223 | lfo_waveform: 0 224 | }; -------------------------------------------------------------------------------- /source/terminal.js: -------------------------------------------------------------------------------- 1 | 2 | var terminal_text_ident = '> '; 3 | var terminal_text_title = '' + 4 | 'UNDERRUN\n' + 5 | '__ \n' + 6 | 'CONCEPT, GRAPHICS & PROGRAMMING:\n' + 7 | 'DOMINIC SZABLEWSKI // PHOBOSLAB.ORG\n' + 8 | '__ \n' + 9 | 'MUSIC:\n' + 10 | 'ANDREAS LÖSCH // NO-FATE.NET\n' + 11 | '___ \n' + 12 | 'SYSTEM VERSION: 13.20.18\n' + 13 | 'CPU: PL(R) Q-COATL 7240 @ 12.6 THZ\n' + 14 | 'MEMORY: 108086391056891900 BYTES\n' + 15 | ' \n' + 16 | 'CONNECTING...'; 17 | 18 | var terminal_text_garbage = 19 | '´A1e{∏éI9·NQ≥ÀΩ¸94CîyîR›kÈ¡˙ßT-;ûÅf^˛,¬›A∫S〫ÕÕ' + 20 | '1f@çX8ÎRjßf•ò√ã0êÃcÄ]Î≤moDÇ’ñ‰\\ˇ≠n=(s7É;'; 21 | 22 | var terminal_text_story = 23 | 'DATE: SEP. 13, 2718 - 13:32\n' + 24 | 'CRITICAL SOFTWARE FAILURE DETECTED\n' + 25 | 'ANALYZING...\n' + 26 | '____\n \n' + 27 | 'ERROR CODE: JS13K2018\n' + 28 | 'STATUS: SYSTEMS OFFLINE\n' + 29 | 'DESCRIPTION: BUFFER UNDERRUN DUE TO SATCOM R.U.D.\n' + 30 | 'AFFECTED SYSTEM: FACILITY AUTOMATION\n' + 31 | 'AFFECTED SUBSYSTEMS: AI, RADIATION SHIELDS, POWER MANAGEMENT\n' + 32 | ' \n' + 33 | 'INITIATING RESCUE SYSTEM...\n' + 34 | '___' + 35 | 'FAILED\n \n' + 36 | 'ATTEMPTING AUTOMATED REBOOT...\n' + 37 | '___' + 38 | 'FAILED\n' + 39 | '_ \n \n' + 40 | 'MANUAL REBOOT OF ALL SYSTEMS REQUIRED\n' + 41 | '_ \n' + 42 | 'USE WASD OR CURSOR KEYS TO MOVE, MOUSE TO SHOOT\n' + 43 | 'CLICK TO INITIATE YOUR DEPLOYMENT\n '; 44 | 45 | var terminal_text_outro = 46 | 'ALL SATELLITE LINKS ONLINE\n' + 47 | 'CONNECTING...___' + 48 | 'CONNECTION ESTABLISHED\n' + 49 | 'RECEIVING TRANSMISSION...___ \n' + 50 | 51 | 'SENT: SEP. 13, 2018\n' + 52 | 'RCVD: SEP. 13, 2718\n \n' + 53 | 54 | 'THANKS FOR PLAYING ❤_ \n' + 55 | 'I HAVE PREVIOUSLY BEEN A PROUD SPONSOR OF THE JS13K\n' + 56 | 'COMPETITION SINCE THE VERY FIRST ONE BACK IN 2012.\n' + 57 | 'HOWEVER, THIS YEAR\'S COMPETITION WAS MY FIRST ONE\n' + 58 | 'AS A PARTICIPANT AND IT HAS BEEN TREMENDOUS FUN!\n \n' + 59 | 60 | 'I WANT TO THANK MY DEAR FRIEND ANDREAS LÖSCH OF\n' + 61 | 'NO-FATE.NET FOR COMPOSING SOME AWESOME MUSIC ON\n' + 62 | 'SUCH SHORT NOTICE.\n \n' + 63 | 64 | 'FURTHER THANKS GO OUT TO THE JS13K STAFF, THE\n' + 65 | 'SONANT-X DEVELOPERS AND ALL OTHER PARTICIPANTS\n' + 66 | 'IN THIS YEAR\'S JS13K. SEE YOU NEXT YEAR!\n \n' + 67 | 'DOMINIC__' + 68 | 'END OF TRANSMISSION'; 69 | 70 | var terminal_text_buffer = [], 71 | terminal_state = 0, 72 | terminal_current_line, 73 | terminal_line_wait = 100, 74 | terminal_print_ident = true, 75 | terminal_timeout_id = 0, 76 | terminal_hide_timeout = 0; 77 | 78 | terminal_text_garbage += terminal_text_garbage + terminal_text_garbage; 79 | 80 | function terminal_show() { 81 | clearTimeout(terminal_hide_timeout); 82 | a.style.opacity = 1; 83 | a.style.display = 'block'; 84 | } 85 | 86 | function terminal_hide() { 87 | a.style.opacity = 0; 88 | terminal_hide_timeout = setTimeout(function(){a.style.display = 'none'}, 1000); 89 | } 90 | 91 | function terminal_cancel() { 92 | clearTimeout(terminal_timeout_id); 93 | } 94 | 95 | function terminal_prepare_text(text) { 96 | return text.replace(/_/g, '\n'.repeat(10)).split('\n'); 97 | } 98 | 99 | function terminal_write_text(lines, callback) { 100 | if (lines.length) { 101 | terminal_write_line(lines.shift(), terminal_write_text.bind(this, lines, callback)); 102 | } 103 | else { 104 | callback && callback(); 105 | } 106 | } 107 | 108 | function terminal_write_line(line, callback) { 109 | if (terminal_text_buffer.length > 20) { 110 | terminal_text_buffer.shift(); 111 | } 112 | if (line) { 113 | audio_play(audio_sfx_terminal); 114 | terminal_text_buffer.push((terminal_print_ident ? terminal_text_ident : '') + line); 115 | a.innerHTML = '
'+terminal_text_buffer.join(' 
')+'
'; 116 | } 117 | terminal_timeout_id = setTimeout(callback, terminal_line_wait); 118 | } 119 | 120 | function terminal_show_notice(notice, callback) { 121 | a.innerHTML = ''; 122 | terminal_text_buffer = []; 123 | 124 | terminal_cancel(); 125 | terminal_show(); 126 | terminal_write_text(terminal_prepare_text(notice), function(){ 127 | terminal_timeout_id = setTimeout(function(){ 128 | terminal_hide(); 129 | callback && callback(); 130 | }, 2000); 131 | }); 132 | } 133 | 134 | function terminal_run_intro(callback) { 135 | terminal_text_buffer = []; 136 | terminal_write_text(terminal_prepare_text(terminal_text_title), function(){ 137 | terminal_timeout_id = setTimeout(function(){ 138 | terminal_run_garbage(callback); 139 | }, 4000); 140 | }); 141 | } 142 | 143 | function terminal_run_garbage(callback) { 144 | terminal_print_ident = false; 145 | terminal_line_wait = 16; 146 | 147 | var t = terminal_text_garbage, 148 | length = terminal_text_garbage.length; 149 | 150 | for (var i = 0; i < 64; i++) { 151 | var s = (_math.random()*length)|0; 152 | var e = (_math.random()*(length - s))|0; 153 | t += terminal_text_garbage.substr(s, e) + '\n'; 154 | } 155 | t += ' \n \n'; 156 | terminal_write_text(terminal_prepare_text(t), function(){ 157 | terminal_timeout_id = setTimeout(function(){ 158 | terminal_run_story(callback); 159 | }, 1500); 160 | }); 161 | } 162 | 163 | function terminal_run_story(callback) { 164 | terminal_print_ident = true; 165 | terminal_line_wait = 100; 166 | terminal_write_text(terminal_prepare_text(terminal_text_story), callback); 167 | } 168 | 169 | function terminal_run_outro(callback) { 170 | c.style.opacity = 0.3; 171 | a.innerHTML = ''; 172 | terminal_text_buffer = []; 173 | 174 | terminal_cancel(); 175 | terminal_show(); 176 | terminal_write_text(terminal_prepare_text(terminal_text_outro)); 177 | } 178 | --------------------------------------------------------------------------------