diff options
103 files changed, 8587 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0dba330 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +data/ +node_modules/ +*.zip + +# Python/Selenium testing +__pycache__/ +*.py[cod] +*$py.class +.pytest_cache/ +*.log +.coverage +htmlcov/ +*.png +*.jpg @@ -0,0 +1,8 @@ +Art - +opengameart.org user Buch - ui sprites (CC-BY) +lospec.com user Chase Stemel - colorpalette (???) +game-icons.net user lorc - unmasked mask icon (CC-BY-3.0) + +Software - +Matthias Richter (github.com user vrld) - hc.lua (MIT) +Michelle Bu, Eric Zhang, and contributing authors - PeerJS (MIT) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..11b164a --- /dev/null +++ b/Makefile @@ -0,0 +1,109 @@ +LUA?="$(shell where lua | head -n 1)" +MOONC?="$(shell where moonc | head -n 1)" +BUSTED?="$(shell where busted | head -n 1)" +AMULET?="$(shell where amulet | head -n 1)" +MAGICK?="$(shell where magick | head -n 1)" -script +PYTHON?=python +PYTEST?=$(PYTHON) -m pytest +# Works on windows and linux (and mac?) +CP?=cp +RM?=rm +CD?=cd +UNZIP?=7z x -y + +.PHONY: all clean test test-integration test-integration-headless test1 + +# Recursive wildcard +rwildcard=$(foreach d,$(wildcard $(1:=/*)),$(call rwildcard,$d,$2) $(filter $(subst *,%,$2),$d)) + +# Source code +src_moon=$(call rwildcard,src,*.moon) +built_moon=$(src_moon:src/%.moon=data/%.lua) +dbg_moon=$(src_moon:src/%.moon=data/%.lua.X) + +src_lua=$(call rwildcard,src,*.lua) +built_lua=$(src_lua:src/%.lua=data/%.lua) + +src_js=$(call rwildcard,src,*.js) +built_js=$(src_js:src/%.js=data/%.lua) + +src_vert=$(call rwildcard,src,*.vert) +built_vert=$(src_vert:src/%.vert=data/%.vert) + +src_frag=$(call rwildcard,src,*.frag) +built_frag=$(src_frag:src/%.frag=data/%.frag) + +src_imagemagick=$(call rwildcard,assets_src,*.imagemagick) +built_imagemagick=$(src_imagemagick:assets_src/%.imagemagick=data/assets/%.png) +src_png=$(call rwildcard,assets_src,*.png) +built_png=$(src_png:assets_src/%.png=data/assets/%.png) +sprites=$(built_imagemagick) $(built_png) + +built=$(built_moon) $(built_lua) $(built_js) $(built_vert) $(built_frag) $(built_imagemagick) data/sprites.lua data/sprites.png +built_dbg=$(built) $(dbg_moon) + +all: ggj26/amulet.js ggj26/amulet.wasm ggj26/data.pak ggj26/index.html + echo "Done" + $(AMULET) export -r -html -a data + #scp ggj26-0.0.0-html.zip alex@cogarr.net: + $(UNZIP) *.zip + $(CD) ggj26 && python3 -m http.server -b 0.0.0.0 8000 + + +ggj26/index.html ggj26/amulet.js ggj26/amulet.wasm ggj26/data.pak: ggj26-0.0.0-html.zip + $(UNZIP) *.zip + +data/sprites.lua data/sprites.png : $(sprites) + $(AMULET) pack -png data/sprites.png -lua data/sprites.lua yataghan.ttf@64 yataghan.ttf@32 $^ + # $(RM) -rf data/assets + sed -i 's|data/sprites\.png|sprites.png|g; s|data/assets/|assets/|g' data/sprites.lua + +ggj26-0.0.0-html.zip : $(built_dbg) + $(AMULET) export -r -html -a data + +clean: + $(RM) $(built) + +test: $(built_dbg) + $(BUSTED) -m "./data/?.lua" -o utfTerminal -v spec + +test-integration: + $(PYTEST) -v + +test-integration-headless: + @set HEADLESS=1 && $(PYTEST) -v + +test1: test test-integration + +$(built_lua) : data/%.lua : src/%.lua + $(CP) $< $@ + +$(built_moon) : data/%.lua : src/%.moon + # $(MOONC) -l $< # Linter isn't good enough yet + $(MOONC) -p $< > $@ + +$(dbg_moon) : data/%.lua.X : src/%.moon + # $(MOONC) -l $< # Linter isn't good enough yet + $(MOONC) -X $< > $@ + +$(built_js) : data/%.lua : src/%.js + $(if $(findstring party/,$<),@echo Skipping jshint for third-party library: $<,npx jshint $<) + echo "return [=====[" > $@ + cat $< >> $@ + echo "]=====]" >> $@ + +$(built_imagemagick) : data/assets/%.png : assets_src/%.imagemagick + @mkdir -p $(dir $@) + $(MAGICK) $< > $@ + +$(built_png) : data/assets/%.png : assets_src/%.png + @mkdir -p $(dir $@) + $(CP) $< $@ + +$(built_vert) : data/%.vert : src/%.vert + @mkdir -p $(dir $@) + $(CP) $< $@ + +$(built_frag) : data/%.frag : src/%.frag + @mkdir -p $(dir $@) + $(CP) $< $@ diff --git a/README.md b/README.md new file mode 100644 index 0000000..7066826 --- /dev/null +++ b/README.md @@ -0,0 +1,214 @@ +# ggj26-comp + +Extracted components from ggj26: build system, logging, and hub-and-spoke networking. + +## Components + +### Build System +Makefile-based build system for MoonScript/Lua projects: +- Compiles `.moon` files to `.lua` +- Embeds JavaScript files with jshint validation +- Runs tests with busted + +**Usage:** +```bash +make # Build all sources +make test # Run tests +make clean # Clean build artifacts +``` + +### Logging Utility +`src/log.moon` - Singleton logger with level-based and tag-based filtering. + +**Features:** +- Log levels: debug, info, warn, error, panic +- Tag-based filtering for organizing logs +- Observer pattern for real-time monitoring +- Message stream with time tracking + +**Usage:** +```lua +log = require "log" + +-- Log with level and tags +log.info("User connected", {"network", "auth"}) +log.error("Connection failed", {"network"}) + +-- Filter by level +log.of_level("error", function(msg) + print(msg.message) +end) + +-- Filter by tags +log.of_tags({"network"}, function(msg) + print(msg.message) +end) + +-- Listen to all messages +log.listen(function(msg) + if msg.level == "panic" then + -- handle critical error + end +end) +``` + +### Networking - Hub and Spoke Model + +#### Core (`src/net.moon`) +Low-level WebRTC peer connection abstraction using PeerJS: +- Peer creation and connection management +- Message validation and routing +- JavaScript bridge for browser integration + +#### Hub (`src/hub.moon`) +Server-side hub that manages client connections: +- Maintains registry of connected clients +- Routes messages between clients (unicast, multicast, broadcast) +- Connection/disconnection event handling + +**Usage:** +```lua +Hub = require("hub").Hub +hub = Hub() +hub:initialize() + +-- Get hub ID to share with clients +hub_id = hub:get_peer_id() + +-- Listen for client events +hub:on_connect(function(client_id) + print("Client connected:", client_id) +end) + +hub:on_disconnect(function(client_id) + print("Client disconnected:", client_id) +end) + +-- Update loop +function love.update(dt) + hub:pump() +end +``` + +#### Client (`src/client.moon`) +Client-side with router registration system: +- Connect to hub +- Register message handlers (routers) by message type +- Send messages to specific clients or broadcast + +**Usage:** +```lua +Client = require("client").Client +client = Client("player1") + +-- Connect to hub +client:connect_to_hub(hub_id) + +-- Register router for message type +client:register_router("chat", function(from_id, data) + print("Chat from", from_id, ":", data.message) +end) + +client:register_router("game_state", function(from_id, data) + -- handle game state update +end) + +-- Send messages +client:send_to(other_client_id, "chat", {message = "Hello!"}) +client:broadcast("game_state", {position = {x=10, y=20}}) + +-- Update loop +function love.update(dt) + client:pump() +end +``` + +#### Testing Utilities (`src/channel.moon`) +Channel abstractions for testing: +- `SimpleChannel` - Basic in-memory channel +- `FaultyChannel` - Simulates network latency and packet loss + +### Utility Functions (`src/util.lua`) +Helper functions including: +- `typecheck(tbl, ...)` - Validate table fields and types +- `peer_to_code(str)` / `code_to_peer(str)` - Convert peer IDs to shareable codes + +## Architecture + +### Hub-and-Spoke Networking + +``` + [Client A] + | + v + [Hub] ←-→ [Client B] + | + v + [Client C] +``` + +- **Hub**: Single peer that accepts all client connections +- **Clients**: Connect to hub, register message handlers (routers) +- **Message Flow**: Client → Hub → Target Client(s) +- **Routing**: Hub handles unicast, multicast, and broadcast + +This replaces the previous RAFT-based peer-to-peer model with a simpler, centralized approach suitable for game lobbies and small multiplayer sessions. + +## Requirements + +- MoonScript compiler (`moonc`) +- Lua +- Busted (for unit testing) +- Node.js/npm (for jshint) +- Python 3.8+ (for integration testing) +- Chrome browser (for Selenium tests) + +## Installation + +```bash +# Install Node.js dependencies +npm install + +# Install Python test dependencies (for integration testing) +pip install -r requirements-test.txt +``` + +## Testing + +### Run All Tests +Run both unit tests and integration tests: +```bash +make test1 +``` + +### Unit Tests (Lua/Busted) +Run Lua unit tests with busted: +```bash +make test +``` + +### Integration Tests (Selenium) +Run browser-based integration tests: + +```bash +# Using Make +make test-integration # Run with visible browser +make test-integration-headless # Run in headless mode +``` + +Or use the PowerShell script: +```powershell +.\run-integration-tests.ps1 # Run all tests +.\run-integration-tests.ps1 -Headless # Headless mode +.\run-integration-tests.ps1 -Smoke # Smoke tests only +.\run-integration-tests.ps1 -UI # UI interaction tests +``` + +Or use pytest directly: +```bash +pytest # Run all integration tests +pytest -m smoke # Run smoke tests only +pytest -v # Verbose output +``` + +See `tests/integration/README.md` for more details on integration testing. diff --git a/assets_src/Daniel Midgley.txt b/assets_src/Daniel Midgley.txt new file mode 100644 index 0000000..108d0ac --- /dev/null +++ b/assets_src/Daniel Midgley.txt @@ -0,0 +1,21 @@ +Thanks for downloading the Daniel font! + +It’s free for you to use for any purpose, commercial or not. + +You can share this font with anyone, as long as this notice is included. + +Please do not distribute modified copies. + +Visit the Page of Fontery + +http://goodreasonblog.blogspot.com/p/fontery.html + +Here’s where you can +- download more of my fonts +- send me an email +- report problems or suggestions +- express your gratitude in the form of donations to keep the fonts coming. + +Be sure to let me know if you use one of my fonts in an interesting, creative, or beautiful way. I may feature your work on the blog. + +This font may not be appropriate for your purposes. It comes with no guarantees of any kind. While I’ve tested this font, and it seems to work well, I accept no responsibility for any unintended consequences of its use.
\ No newline at end of file diff --git a/assets_src/floor.imagemagick b/assets_src/floor.imagemagick new file mode 100644 index 0000000..1295088 --- /dev/null +++ b/assets_src/floor.imagemagick @@ -0,0 +1,10 @@ +#!/usr/bin/env magick-script + +-size 512x512 pattern:checkerboard +-depth 8 +-colorspace HSL +-level 40%,60% +-blur 0x0.5 +-bordercolor rgb(58,58,58) +-border 8 +-write png:- diff --git a/assets_src/player.imagemagick b/assets_src/player.imagemagick new file mode 100644 index 0000000..e763a70 --- /dev/null +++ b/assets_src/player.imagemagick @@ -0,0 +1,18 @@ +#!/usr/bin/env magick-script + +-size 512x512 xc:transparent +-depth 8 +-fill rgb(60,60,60) +-draw "circle 256,256 256,180" +-fill rgb(80,80,80) +-draw "roundrectangle 200,180 240,220 10,10" +-draw "roundrectangle 200,292 240,332 10,10" +-fill rgb(50,50,50) +-draw "rectangle 256,240 340,272" +-draw "rectangle 320,250 350,262" +-fill rgb(40,40,40) +-draw "circle 256,256 256,200" +-blur 0x0.3 +-bordercolor rgb(58,58,58) +-border 8 +-write png:- diff --git a/assets_src/qrcode_placeholder.imagemagick b/assets_src/qrcode_placeholder.imagemagick new file mode 100644 index 0000000..e891876 --- /dev/null +++ b/assets_src/qrcode_placeholder.imagemagick @@ -0,0 +1,7 @@ +#!/usr/bin/env magick-script + +-size 256x256 xc:transparent +-depth 8 +-fill rgb(60,60,60) +-draw "rectangle 0,0,256,256" +-write png:- diff --git a/assets_src/wall.imagemagick b/assets_src/wall.imagemagick new file mode 100644 index 0000000..392cc98 --- /dev/null +++ b/assets_src/wall.imagemagick @@ -0,0 +1,13 @@ +#!/usr/bin/env magick-script + +-size 512x512 xc:rgb(70,70,70) +-depth 8 +( + -size 512x80 xc:rgb(50,50,50) + -geometry +0+400 +) +-composite +-blur 0x0.5 +-bordercolor rgb(58,58,58) +-border 8 +-write png:- diff --git a/context.md b/context.md new file mode 100644 index 0000000..cb0ee87 --- /dev/null +++ b/context.md @@ -0,0 +1,100 @@ +# Project Context + +## Project Type +Game project (likely a game jam entry - ggj26). Features top-down twin-stick shooter gameplay. + +## Asset Generation +Uses ImageMagick scripts (`.imagemagick` files) in `assets_src/` to procedurally generate game assets. + +### ImageMagick Script Conventions +- Start with `#!/usr/bin/env magick-script` +- Standard canvas: `512x512` +- Consistent styling: 8px border with `rgb(58,58,58)`, slight blur (`0x0.3-0.5`) +- Output to PNG via `-write png:-` +- Color palette: grayscale tones (40-80 RGB range) + +### Existing Assets +- `floor.imagemagick`: Checkerboard pattern floor tile +- `wall.imagemagick`: Gray wall tile with horizontal stripe +- `player.imagemagick`: Top-down player sprite (circle body, shoulder pads, gun pointing east) +- Pre-made button sprites: 18 PNG files for UI buttons (up/down states, 3x3 grid variations) + +## Drawing Approach +ImageMagick uses `-draw` commands for shapes. Player sprite demonstrates layering: +- Base circle for body +- Rounded rectangles for shoulder pads +- Rectangles for gun barrel extending horizontally + +## Shaders (`src/shaders/`) +The game uses custom GLSL shaders with MoonScript/Lua setup code, built on the Amulet game engine. + +### Rendering Architecture +- **Pseudo-3D perspective**: Uses Z-coordinate scaling to create depth without true 3D projection +- **Buffered rendering**: Pre-allocated vertex buffers for performance (world.moon sets MAX_PLAYERS=8, MAX_LEVEL_TRIS=1024) +- **World-space rendering**: All shaders receive `world_x` and `world_y` uniforms for camera positioning +- **Multi-lamp lighting**: Up to 8 dynamic lamps per shader with distance-based illumination + +### Color Palette System +All lit shaders use a consistent 7-color palette for quantized lighting: +- `black` (darkest) +- `outline` +- `shadow` +- `background` +- `midground` +- `foreground` +- `highlight` (brightest) + +Colors are selected based on calculated light intensity thresholds (>1.0, >0.8, >0.6, >0.4, >0.2, else black). + +### Individual Shaders + +#### Lake Shader (`lake.frag`, `lake.vert`, `lake.moon`) +- Water surface rendering with dynamic lighting +- Distance-based lamp illumination (up to 8 lamps: `lamp1` through `lamp8`) +- Noise/random functions for visual texture +- `streamer` uniform: toggles noise off for cleaner rendering +- Normal map support via `atlas` texture and `norm` varying +- Simple vertex shader (no rotation or Z-effects) + +#### Land Shader (`land.frag`, `land.vert`) +- Ground/terrain rendering, nearly identical to lake shader +- Additional `water` uniform: enables simplex noise animation when > 1 +- Vertex shader includes rotation matrix (`rot` uniform) +- Z-scaling for pseudo-3D perspective (clamped ±0.5) +- Normal mapping support + +#### Player Shader (`player.frag`, `player.vert`) +- Character rendering (currently shows UV debug visualization) +- Supports texture mapping with `textures`, `emissives`, `normals` samplers +- Directional rotation via `dir` uniform +- Fixed Z-depth at -2 to position above world +- Lamp support declared but not used in fragment shader + +#### Stars Shader (`stars.frag`, `stars.vert`, `stars.lua`) +- Starfield background using point sprites +- Procedurally generates ~500 stars in Lua setup +- Stars arranged in 3×3 tiling pattern for infinite scrolling +- Animated blinking: `gl_PointSize = pow(intensity, 2.) * stars.z * 0.3` where intensity = `sin(stars.z + time) * cos(time) + 1` +- Stars avoid circular region in center (radius 0.5) for game area +- Wrapping via `world_x_period` and `world_y_period` +- Z-position: -0.1 (behind everything) + +#### World Shader (`world.frag`, `world.vert`, `world.moon`) +- Main 3D geometry renderer for levels +- Texture-mapped rendering (currently uses floor texture) +- Pseudo-3D via Z-based offset: `xoff = clamp(world.z * vxy.x * z_scale, -32., 32.)` +- Geometry generation in MoonScript: + - `up_down_hall`: Creates hallway segments with floor and walls + - `barrel`: Generates cylindrical objects with triangulated sides + - `room`: Room generation (partially implemented) +- Buffered architecture allows dynamic level geometry +- Uses depth testing (`less`) and front-face culling +- Support for round objects via radius attribute (unused currently) + +### Technical Details +- **Noise functions**: Simplex noise from Shadertoy (https://www.shadertoy.com/view/Msf3WH) +- **Random function**: Hash-based PRNG by @patriciogv (2015) +- **Lamp format**: `vec4(x, y, z, strength)` in normalized coordinates, scaled by 256 in shaders +- **Coordinate system**: World space, with fragment coord offset by `worldxy * 256` +- **View matrices**: Custom MV/P matrices in MoonScript, not standard OpenGL projection +- **Engine**: Amulet (`am.*` functions), uses scene graph with `append`, `bind`, `draw` nodes diff --git a/examples.md b/examples.md new file mode 100644 index 0000000..58d947f --- /dev/null +++ b/examples.md @@ -0,0 +1,156 @@ +# Usage Examples + +## Simple Chat Application + +### Hub Side (Server) +```lua +Hub = require("hub").Hub +log = require("log") + +-- Create and initialize hub +hub = Hub() +hub:initialize() + +-- Get the hub ID and share it with clients +print("Hub ID:", hub:get_peer_id()) + +-- Listen for client events +hub:on_connect(function(client_id) + log.info("New client: " .. client_id, {"chat", "connection"}) +end) + +hub:on_disconnect(function(client_id) + log.info("Client left: " .. client_id, {"chat", "connection"}) +end) + +-- Main loop +function love.update(dt) + hub:pump() +end +``` + +### Client Side +```lua +Client = require("client").Client +log = require("log") + +-- Create client with a name +client = Client("Alice") + +-- Connect to hub (use the ID printed by hub) +client:connect_to_hub("hub-id-here") + +-- Register router for chat messages +client:register_router("chat", function(from_id, data) + print(data.username .. ": " .. data.message) +end) + +-- Handle connection +client:on_connect(function() + print("Connected to chat!") +end) + +-- Send a chat message to all +function send_chat(message) + client:broadcast("chat", { + username = "Alice", + message = message + }) +end + +-- Main loop +function love.update(dt) + client:pump() +end +``` + +## Game State Synchronization + +### Hub Side +```lua +Hub = require("hub").Hub + +hub = Hub() +hub:initialize() + +-- Track game state +game_state = { + players = {}, + started = false +} + +hub:on_connect(function(client_id) + -- Send current game state to new player + hub:send_to_client(client_id, { + type = "game_state", + from = "server", + data = game_state + }) +end) + +function love.update(dt) + hub:pump() +end +``` + +### Client Side +```lua +Client = require("client").Client + +client = Client("Player1") +client:connect_to_hub(hub_id) + +local_game_state = {} + +-- Handle game state updates +client:register_router("game_state", function(from_id, data) + local_game_state = data + print("Game state updated") +end) + +-- Handle player actions +client:register_router("player_move", function(from_id, data) + -- Update other player's position + update_player_position(from_id, data.x, data.y) +end) + +-- Send player movement +function move_player(x, y) + client:broadcast("player_move", { + x = x, + y = y + }) +end + +function love.update(dt) + client:pump() +end +``` + +## Logging Examples + +```lua +log = require("log") + +-- Basic logging +log.info("Server started", {"server"}) +log.error("Connection failed", {"network", "error"}) + +-- Query logs by level +log.of_level("error", function(msg) + print(string.format("[%s] %s", msg.level, msg.message)) +end) + +-- Query logs by tags +log.of_tags({"network"}, function(msg) + print("Network event:", msg.message) +end) + +-- Real-time monitoring +log.listen(function(msg) + if msg.level == "panic" then + -- Handle critical errors + crash_handler(msg.message) + end +end) +``` diff --git a/ggj26/amulet.js b/ggj26/amulet.js new file mode 100644 index 0000000..0a9696b --- /dev/null +++ b/ggj26/amulet.js @@ -0,0 +1 @@ +var Module=typeof Module!=="undefined"?Module:{};var moduleOverrides={};var key;for(key in Module){if(Module.hasOwnProperty(key)){moduleOverrides[key]=Module[key]}}var arguments_=[];var thisProgram="./this.program";var quit_=function(status,toThrow){throw toThrow};var ENVIRONMENT_IS_WEB=false;var ENVIRONMENT_IS_WORKER=false;var ENVIRONMENT_IS_NODE=false;var ENVIRONMENT_HAS_NODE=false;var ENVIRONMENT_IS_SHELL=false;ENVIRONMENT_IS_WEB=typeof window==="object";ENVIRONMENT_IS_WORKER=typeof importScripts==="function";ENVIRONMENT_HAS_NODE=typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string";ENVIRONMENT_IS_NODE=ENVIRONMENT_HAS_NODE&&!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_WORKER;ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;if(ENVIRONMENT_IS_NODE){scriptDirectory=__dirname+"/";var nodeFS;var nodePath;read_=function shell_read(filename,binary){var ret;if(!nodeFS)nodeFS=require("fs");if(!nodePath)nodePath=require("path");filename=nodePath["normalize"](filename);ret=nodeFS["readFileSync"](filename);return binary?ret:ret.toString()};readBinary=function readBinary(filename){var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}assert(ret.buffer);return ret};if(process["argv"].length>1){thisProgram=process["argv"][1].replace(/\\/g,"/")}arguments_=process["argv"].slice(2);if(typeof module!=="undefined"){module["exports"]=Module}process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});process["on"]("unhandledRejection",abort);quit_=function(status){process["exit"](status)};Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_SHELL){if(typeof read!="undefined"){read_=function shell_read(f){return read(f)}}readBinary=function readBinary(f){var data;if(typeof readbuffer==="function"){return new Uint8Array(readbuffer(f))}data=read(f,"binary");assert(typeof data==="object");return data};if(typeof scriptArgs!="undefined"){arguments_=scriptArgs}else if(typeof arguments!="undefined"){arguments_=arguments}if(typeof quit==="function"){quit_=function(status){quit(status)}}if(typeof print!=="undefined"){if(typeof console==="undefined")console={};console.log=print;console.warn=console.error=typeof printErr!=="undefined"?printErr:print}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(document.currentScript){scriptDirectory=document.currentScript.src}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.lastIndexOf("/")+1)}else{scriptDirectory=""}read_=function shell_read(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=function readBinary(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=function readAsync(url,onload,onerror){var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=function xhr_onload(){if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)};setWindowTitle=function(title){document.title=title}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);for(key in moduleOverrides){if(moduleOverrides.hasOwnProperty(key)){Module[key]=moduleOverrides[key]}}moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var STACK_ALIGN=16;function dynamicAlloc(size){var ret=HEAP32[DYNAMICTOP_PTR>>2];var end=ret+size+15&-16;if(end>_emscripten_get_heap_size()){abort()}HEAP32[DYNAMICTOP_PTR>>2]=end;return ret}function getNativeTypeSize(type){switch(type){case"i1":case"i8":return 1;case"i16":return 2;case"i32":return 4;case"i64":return 8;case"float":return 4;case"double":return 8;default:{if(type[type.length-1]==="*"){return 4}else if(type[0]==="i"){var bits=parseInt(type.substr(1));assert(bits%8===0,"getNativeTypeSize invalid bits "+bits+", type "+type);return bits/8}else{return 0}}}}function warnOnce(text){if(!warnOnce.shown)warnOnce.shown={};if(!warnOnce.shown[text]){warnOnce.shown[text]=1;err(text)}}var asm2wasmImports={"f64-rem":function(x,y){return x%y},"debugger":function(){}};var jsCallStartIndex=1;var functionPointers=new Array(0);function convertJsFunctionToWasm(func,sig){var typeSection=[1,0,1,96];var sigRet=sig.slice(0,1);var sigParam=sig.slice(1);var typeCodes={"i":127,"j":126,"f":125,"d":124};typeSection.push(sigParam.length);for(var i=0;i<sigParam.length;++i){typeSection.push(typeCodes[sigParam[i]])}if(sigRet=="v"){typeSection.push(0)}else{typeSection=typeSection.concat([1,typeCodes[sigRet]])}typeSection[1]=typeSection.length-2;var bytes=new Uint8Array([0,97,115,109,1,0,0,0].concat(typeSection,[2,7,1,1,101,1,102,0,0,7,5,1,1,102,0,0]));var module=new WebAssembly.Module(bytes);var instance=new WebAssembly.Instance(module,{e:{f:func}});var wrappedFunc=instance.exports.f;return wrappedFunc}var funcWrappers={};function dynCall(sig,ptr,args){if(args&&args.length){return Module["dynCall_"+sig].apply(null,[ptr].concat(args))}else{return Module["dynCall_"+sig].call(null,ptr)}}var tempRet0=0;var setTempRet0=function(value){tempRet0=value};var getTempRet0=function(){return tempRet0};var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime;if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(typeof WebAssembly!=="object"){err("no native wasm support detected")}function setValue(ptr,value,type,noSafe){type=type||"i8";if(type.charAt(type.length-1)==="*")type="i32";switch(type){case"i1":HEAP8[ptr>>0]=value;break;case"i8":HEAP8[ptr>>0]=value;break;case"i16":HEAP16[ptr>>1]=value;break;case"i32":HEAP32[ptr>>2]=value;break;case"i64":tempI64=[value>>>0,(tempDouble=value,+Math_abs(tempDouble)>=1?tempDouble>0?(Math_min(+Math_floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math_ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[ptr>>2]=tempI64[0],HEAP32[ptr+4>>2]=tempI64[1];break;case"float":HEAPF32[ptr>>2]=value;break;case"double":HEAPF64[ptr>>3]=value;break;default:abort("invalid type for setValue: "+type)}}var wasmMemory;var wasmTable=new WebAssembly.Table({"initial":1418,"maximum":1418,"element":"anyfunc"});var ABORT=false;var EXITSTATUS=0;function assert(condition,text){if(!condition){abort("Assertion failed: "+text)}}function getCFunc(ident){var func=Module["_"+ident];assert(func,"Cannot call unknown function "+ident+", make sure it is exported");return func}function ccall(ident,returnType,argTypes,args,opts){var toC={"string":function(str){var ret=0;if(str!==null&&str!==undefined&&str!==0){var len=(str.length<<2)+1;ret=stackAlloc(len);stringToUTF8(str,ret,len)}return ret},"array":function(arr){var ret=stackAlloc(arr.length);writeArrayToMemory(arr,ret);return ret}};function convertReturnValue(ret){if(returnType==="string")return UTF8ToString(ret);if(returnType==="boolean")return Boolean(ret);return ret}var func=getCFunc(ident);var cArgs=[];var stack=0;if(args){for(var i=0;i<args.length;i++){var converter=toC[argTypes[i]];if(converter){if(stack===0)stack=stackSave();cArgs[i]=converter(args[i])}else{cArgs[i]=args[i]}}}var ret=func.apply(null,cArgs);ret=convertReturnValue(ret);if(stack!==0)stackRestore(stack);return ret}var ALLOC_NORMAL=0;var ALLOC_STACK=1;var ALLOC_NONE=3;function allocate(slab,types,allocator,ptr){var zeroinit,size;if(typeof slab==="number"){zeroinit=true;size=slab}else{zeroinit=false;size=slab.length}var singleType=typeof types==="string"?types:null;var ret;if(allocator==ALLOC_NONE){ret=ptr}else{ret=[_malloc,stackAlloc,dynamicAlloc][allocator](Math.max(size,singleType?1:types.length))}if(zeroinit){var stop;ptr=ret;assert((ret&3)==0);stop=ret+(size&~3);for(;ptr<stop;ptr+=4){HEAP32[ptr>>2]=0}stop=ret+size;while(ptr<stop){HEAP8[ptr++>>0]=0}return ret}if(singleType==="i8"){if(slab.subarray||slab.slice){HEAPU8.set(slab,ret)}else{HEAPU8.set(new Uint8Array(slab),ret)}return ret}var i=0,type,typeSize,previousType;while(i<size){var curr=slab[i];type=singleType||types[i];if(type===0){i++;continue}if(type=="i64")type="i32";setValue(ret+i,curr,type);if(previousType!==type){typeSize=getNativeTypeSize(type);previousType=type}i+=typeSize}return ret}function getMemory(size){if(!runtimeInitialized)return dynamicAlloc(size);return _malloc(size)}var UTF8Decoder=typeof TextDecoder!=="undefined"?new TextDecoder("utf8"):undefined;function UTF8ArrayToString(u8Array,idx,maxBytesToRead){var endIdx=idx+maxBytesToRead;var endPtr=idx;while(u8Array[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&u8Array.subarray&&UTF8Decoder){return UTF8Decoder.decode(u8Array.subarray(idx,endPtr))}else{var str="";while(idx<endPtr){var u0=u8Array[idx++];if(!(u0&128)){str+=String.fromCharCode(u0);continue}var u1=u8Array[idx++]&63;if((u0&224)==192){str+=String.fromCharCode((u0&31)<<6|u1);continue}var u2=u8Array[idx++]&63;if((u0&240)==224){u0=(u0&15)<<12|u1<<6|u2}else{u0=(u0&7)<<18|u1<<12|u2<<6|u8Array[idx++]&63}if(u0<65536){str+=String.fromCharCode(u0)}else{var ch=u0-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function stringToUTF8Array(str,outU8Array,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i<str.length;++i){var u=str.charCodeAt(i);if(u>=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;outU8Array[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;outU8Array[outIdx++]=192|u>>6;outU8Array[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;outU8Array[outIdx++]=224|u>>12;outU8Array[outIdx++]=128|u>>6&63;outU8Array[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;outU8Array[outIdx++]=240|u>>18;outU8Array[outIdx++]=128|u>>12&63;outU8Array[outIdx++]=128|u>>6&63;outU8Array[outIdx++]=128|u&63}}outU8Array[outIdx]=0;return outIdx-startIdx}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function lengthBytesUTF8(str){var len=0;for(var i=0;i<str.length;++i){var u=str.charCodeAt(i);if(u>=55296&&u<=57343)u=65536+((u&1023)<<10)|str.charCodeAt(++i)&1023;if(u<=127)++len;else if(u<=2047)len+=2;else if(u<=65535)len+=3;else len+=4}return len}var UTF16Decoder=typeof TextDecoder!=="undefined"?new TextDecoder("utf-16le"):undefined;function allocateUTF8(str){var size=lengthBytesUTF8(str)+1;var ret=_malloc(size);if(ret)stringToUTF8Array(str,HEAP8,ret,size);return ret}function allocateUTF8OnStack(str){var size=lengthBytesUTF8(str)+1;var ret=stackAlloc(size);stringToUTF8Array(str,HEAP8,ret,size);return ret}function writeArrayToMemory(array,buffer){HEAP8.set(array,buffer)}function writeAsciiToMemory(str,buffer,dontAddNull){for(var i=0;i<str.length;++i){HEAP8[buffer++>>0]=str.charCodeAt(i)}if(!dontAddNull)HEAP8[buffer>>0]=0}var WASM_PAGE_SIZE=65536;function alignUp(x,multiple){if(x%multiple>0){x+=multiple-x%multiple}return x}var buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferAndViews(buf){buffer=buf;Module["HEAP8"]=HEAP8=new Int8Array(buf);Module["HEAP16"]=HEAP16=new Int16Array(buf);Module["HEAP32"]=HEAP32=new Int32Array(buf);Module["HEAPU8"]=HEAPU8=new Uint8Array(buf);Module["HEAPU16"]=HEAPU16=new Uint16Array(buf);Module["HEAPU32"]=HEAPU32=new Uint32Array(buf);Module["HEAPF32"]=HEAPF32=new Float32Array(buf);Module["HEAPF64"]=HEAPF64=new Float64Array(buf)}var STACK_BASE=201504,DYNAMIC_BASE=5444384,DYNAMICTOP_PTR=201296;var INITIAL_TOTAL_MEMORY=Module["TOTAL_MEMORY"]||16777216;if(Module["wasmMemory"]){wasmMemory=Module["wasmMemory"]}else{wasmMemory=new WebAssembly.Memory({"initial":INITIAL_TOTAL_MEMORY/WASM_PAGE_SIZE})}if(wasmMemory){buffer=wasmMemory.buffer}INITIAL_TOTAL_MEMORY=buffer.byteLength;updateGlobalBufferAndViews(buffer);HEAP32[DYNAMICTOP_PTR>>2]=DYNAMIC_BASE;function callRuntimeCallbacks(callbacks){while(callbacks.length>0){var callback=callbacks.shift();if(typeof callback=="function"){callback();continue}var func=callback.func;if(typeof func==="number"){if(callback.arg===undefined){Module["dynCall_v"](func)}else{Module["dynCall_vi"](func,callback.arg)}}else{func(callback.arg===undefined?null:callback.arg)}}}var __ATPRERUN__=[];var __ATINIT__=[];var __ATMAIN__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;var runtimeExited=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();TTY.init();callRuntimeCallbacks(__ATINIT__)}function preMain(){FS.ignorePermissions=false;callRuntimeCallbacks(__ATMAIN__)}function exitRuntime(){runtimeExited=true}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var Math_abs=Math.abs;var Math_ceil=Math.ceil;var Math_floor=Math.floor;var Math_min=Math.min;var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}Module["preloadedImages"]={};Module["preloadedAudios"]={};function abort(what){if(Module["onAbort"]){Module["onAbort"](what)}what+="";out(what);err(what);ABORT=true;EXITSTATUS=1;throw"abort("+what+"). Build with -s ASSERTIONS=1 for more info."}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return String.prototype.startsWith?filename.startsWith(dataURIPrefix):filename.indexOf(dataURIPrefix)===0}var wasmBinaryFile="amulet.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(){try{if(wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(wasmBinaryFile)}else{throw"both async and sync fetching of the wasm failed"}}catch(err){abort(err)}}function getBinaryPromise(){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)&&typeof fetch==="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){if(!response["ok"]){throw"failed to load wasm binary file at '"+wasmBinaryFile+"'"}return response["arrayBuffer"]()}).catch(function(){return getBinary()})}return new Promise(function(resolve,reject){resolve(getBinary())})}function createWasm(){var info={"env":asmLibraryArg,"wasi_unstable":asmLibraryArg,"global":{"NaN":NaN,Infinity:Infinity},"global.Math":Math,"asm2wasm":asm2wasmImports};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;removeRunDependency("wasm-instantiate")}addRunDependency("wasm-instantiate");function receiveInstantiatedSource(output){receiveInstance(output["instance"])}function instantiateArrayBuffer(receiver){return getBinaryPromise().then(function(binary){return WebAssembly.instantiate(binary,info)}).then(receiver,function(reason){err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(){if(!wasmBinary&&typeof WebAssembly.instantiateStreaming==="function"&&!isDataURI(wasmBinaryFile)&&typeof fetch==="function"){fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){var result=WebAssembly.instantiateStreaming(response,info);return result.then(receiveInstantiatedSource,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");instantiateArrayBuffer(receiveInstantiatedSource)})})}else{return instantiateArrayBuffer(receiveInstantiatedSource)}}if(Module["instantiateWasm"]){try{var exports=Module["instantiateWasm"](info,receiveInstance);return exports}catch(e){err("Module.instantiateWasm callback failed with error: "+e);return false}}instantiateAsync();return{}}Module["asm"]=createWasm;var tempDouble;var tempI64;var ASM_CONSTS=[function($0){window.amulet.highdpi=$0},function($0){var title_el=document.getElementById("title");if(title_el)title_el.innerHTML=Module.UTF8ToString($0)},function($0){return window.amulet.no_load_data?1:0},function($0){return window.amulet.run_waiting?1:0},function(){window.amulet.load_progress=100;window.amulet.ready()},function(){window.amulet.error("Error loading data file")},function($0){window.amulet.load_progress=$0},function($0){if(window.amulet.window_hidden||!window.amulet.window_has_focus){return 1}else{return 0}},function($0){return window.amulet.have_pointer_lock()?1:0},function(){window.amulet.pointer_lock_requested=true},function(){window.amulet.pointer_lock_requested=false;if(document.exitPointerLock)document.exitPointerLock()},function(){window.amulet.show_cursor=true;document.getElementById("canvas").style.cursor="pointer"},function(){window.amulet.show_cursor=false;document.getElementById("canvas").style.cursor="none"},function($0){return window.amulet.show_cursor?1:0},function(){(function(){if(!window.amulet.video_capture_initialized){if(!navigator.getUserMedia){if(!window.amulet.displayed_no_video_capture_alert){alert("Sorry, your browser does not appear to support video catpure.");window.amulet.displayed_no_video_capture_alert=true}return}var errorCallback=function(e){window.amulet.video_capture_disallowed=true};navigator.getUserMedia({video:true},function(stream){var video=document.getElementById("video");video.src=window.URL.createObjectURL(stream)},errorCallback);window.amulet.video_capture_initialized=true;window.amulet.video_element=document.getElementById("video")}else if(!window.amulet.video_capture_disallowed){GLctx.texImage2D(GLctx.TEXTURE_2D,0,GLctx.RGBA,GLctx.RGBA,GLctx.UNSIGNED_BYTE,window.amulet.video_element)}})()},function($0){var js_str=UTF8ToString($0);var res=eval(js_str);var res_str;if(res===undefined){res_str="null"}else{res_str=JSON.stringify(res)}var len=Module.lengthBytesUTF8(res_str)+1;var buffer=Module._malloc(len);Module.stringToUTF8(res_str,buffer,len);return buffer},function($0){delete window.amulet.http_reqs[$0]},function($0){return window.amulet.http_reqs[$0].status},function($0){var xhr=window.amulet.http_reqs[$0];var txt=xhr.responseText;if(txt==null)return 0;var len=Module.lengthBytesUTF8(txt)+1;var buffer=Module._malloc(len);Module.stringToUTF8(txt,buffer,len);return buffer},function($0){return window.amulet.http_reqs[$0].readyState},function($0,$1,$2){if(!window.amulet.http_reqs)window.amulet.http_reqs={};var xhr=new XMLHttpRequest;window.amulet.http_reqs[$0]=xhr;var url=UTF8ToString($1);var data=null;if($2==0){xhr.open("GET",url,true);xhr.send()}else{data=UTF8ToString($2);xhr.open("POST",url,true);xhr.send(data)}}];function _emscripten_asm_const_i(code){return ASM_CONSTS[code]()}function _emscripten_asm_const_ii(code,a0){return ASM_CONSTS[code](a0)}function _emscripten_asm_const_iiii(code,a0,a1,a2){return ASM_CONSTS[code](a0,a1,a2)}__ATINIT__.push({func:function(){globalCtors()}});var tempDoublePtr=201488;function demangle(func){return func}function demangleAll(text){var regex=/\b__Z[\w\d_]+/g;return text.replace(regex,function(x){var y=demangle(x);return x===y?x:y+" ["+x+"]"})}function jsStackTrace(){var err=new Error;if(!err.stack){try{throw new Error(0)}catch(e){err=e}if(!err.stack){return"(no stack trace available)"}}return err.stack.toString()}function stackTrace(){var js=jsStackTrace();if(Module["extraStackTrace"])js+="\n"+Module["extraStackTrace"]();return demangleAll(js)}function ___setErrNo(value){if(Module["___errno_location"])HEAP32[Module["___errno_location"]()>>2]=value;return value}var PATH={splitPath:function(filename){var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:function(parts,allowAboveRoot){var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:function(path){var isAbsolute=path.charAt(0)==="/",trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(function(p){return!!p}),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:function(path){var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:function(path){if(path==="/")return"/";var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},extname:function(path){return PATH.splitPath(path)[3]},join:function(){var paths=Array.prototype.slice.call(arguments,0);return PATH.normalize(paths.join("/"))},join2:function(l,r){return PATH.normalize(l+"/"+r)}};var PATH_FS={resolve:function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:FS.cwd();if(typeof path!=="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=path.charAt(0)==="/"}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(function(p){return!!p}),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:function(from,to){from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start<arr.length;start++){if(arr[start]!=="")break}var end=arr.length-1;for(;end>=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i<length;i++){if(fromParts[i]!==toParts[i]){samePartsLength=i;break}}var outputParts=[];for(var i=samePartsLength;i<fromParts.length;i++){outputParts.push("..")}outputParts=outputParts.concat(toParts.slice(samePartsLength));return outputParts.join("/")}};var TTY={ttys:[],init:function(){},shutdown:function(){},register:function(dev,ops){TTY.ttys[dev]={input:[],output:[],ops:ops};FS.registerDevice(dev,TTY.stream_ops)},stream_ops:{open:function(stream){var tty=TTY.ttys[stream.node.rdev];if(!tty){throw new FS.ErrnoError(43)}stream.tty=tty;stream.seekable=false},close:function(stream){stream.tty.ops.flush(stream.tty)},flush:function(stream){stream.tty.ops.flush(stream.tty)},read:function(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.get_char){throw new FS.ErrnoError(60)}var bytesRead=0;for(var i=0;i<length;i++){var result;try{result=stream.tty.ops.get_char(stream.tty)}catch(e){throw new FS.ErrnoError(29)}if(result===undefined&&bytesRead===0){throw new FS.ErrnoError(6)}if(result===null||result===undefined)break;bytesRead++;buffer[offset+i]=result}if(bytesRead){stream.node.timestamp=Date.now()}return bytesRead},write:function(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.put_char){throw new FS.ErrnoError(60)}try{for(var i=0;i<length;i++){stream.tty.ops.put_char(stream.tty,buffer[offset+i])}}catch(e){throw new FS.ErrnoError(29)}if(length){stream.node.timestamp=Date.now()}return i}},default_tty_ops:{get_char:function(tty){if(!tty.input.length){var result=null;if(ENVIRONMENT_IS_NODE){var BUFSIZE=256;var buf=Buffer.alloc?Buffer.alloc(BUFSIZE):new Buffer(BUFSIZE);var bytesRead=0;try{bytesRead=fs.readSync(process.stdin.fd,buf,0,BUFSIZE,null)}catch(e){if(e.toString().indexOf("EOF")!=-1)bytesRead=0;else throw e}if(bytesRead>0){result=buf.slice(0,bytesRead).toString("utf-8")}else{result=null}}else if(typeof window!="undefined"&&typeof window.prompt=="function"){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else if(typeof readline=="function"){result=readline();if(result!==null){result+="\n"}}if(!result){return null}tty.input=intArrayFromString(result,true)}return tty.input.shift()},put_char:function(tty,val){if(val===null||val===10){out(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}}},default_tty1_ops:{put_char:function(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};var MEMFS={ops_table:null,mount:function(mount){return MEMFS.createNode(null,"/",16384|511,0)},createNode:function(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}if(!MEMFS.ops_table){MEMFS.ops_table={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,allocate:MEMFS.stream_ops.allocate,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}}}var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=null}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.timestamp=Date.now();if(parent){parent.contents[name]=node}return node},getFileDataAsRegularArray:function(node){if(node.contents&&node.contents.subarray){var arr=[];for(var i=0;i<node.usedBytes;++i)arr.push(node.contents[i]);return arr}return node.contents},getFileDataAsTypedArray:function(node){if(!node.contents)return new Uint8Array;if(node.contents.subarray)return node.contents.subarray(0,node.usedBytes);return new Uint8Array(node.contents)},expandFileStorage:function(node,newCapacity){var prevCapacity=node.contents?node.contents.length:0;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity<CAPACITY_DOUBLING_MAX?2:1.125)|0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0);return},resizeFileStorage:function(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0;return}if(!node.contents||node.contents.subarray){var oldContents=node.contents;node.contents=new Uint8Array(new ArrayBuffer(newSize));if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize;return}if(!node.contents)node.contents=[];if(node.contents.length>newSize)node.contents.length=newSize;else while(node.contents.length<newSize)node.contents.push(0);node.usedBytes=newSize},node_ops:{getattr:function(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.timestamp);attr.mtime=new Date(node.timestamp);attr.ctime=new Date(node.timestamp);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr:function(node,attr){if(attr.mode!==undefined){node.mode=attr.mode}if(attr.timestamp!==undefined){node.timestamp=attr.timestamp}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup:function(parent,name){throw FS.genericErrors[44]},mknod:function(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename:function(old_node,new_dir,new_name){if(FS.isDir(old_node.mode)){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}}delete old_node.parent.contents[old_node.name];old_node.name=new_name;new_dir.contents[new_name]=old_node;old_node.parent=new_dir},unlink:function(parent,name){delete parent.contents[name]},rmdir:function(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name]},readdir:function(node){var entries=[".",".."];for(var key in node.contents){if(!node.contents.hasOwnProperty(key)){continue}entries.push(key)}return entries},symlink:function(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink:function(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read:function(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i<size;i++)buffer[offset+i]=contents[position+i]}return size},write:function(stream,buffer,offset,length,position,canOwn){canOwn=false;if(!length)return 0;var node=stream.node;node.timestamp=Date.now();if(buffer.subarray&&(!node.contents||node.contents.subarray)){if(canOwn){node.contents=buffer.subarray(offset,offset+length);node.usedBytes=length;return length}else if(node.usedBytes===0&&position===0){node.contents=new Uint8Array(buffer.subarray(offset,offset+length));node.usedBytes=length;return length}else if(position+length<=node.usedBytes){node.contents.set(buffer.subarray(offset,offset+length),position);return length}}MEMFS.expandFileStorage(node,position+length);if(node.contents.subarray&&buffer.subarray)node.contents.set(buffer.subarray(offset,offset+length),position);else{for(var i=0;i<length;i++){node.contents[position+i]=buffer[offset+i]}}node.usedBytes=Math.max(node.usedBytes,position+length);return length},llseek:function(stream,offset,whence){var position=offset;if(whence===1){position+=stream.position}else if(whence===2){if(FS.isFile(stream.node.mode)){position+=stream.node.usedBytes}}if(position<0){throw new FS.ErrnoError(28)}return position},allocate:function(stream,offset,length){MEMFS.expandFileStorage(stream.node,offset+length);stream.node.usedBytes=Math.max(stream.node.usedBytes,offset+length)},mmap:function(stream,buffer,offset,length,position,prot,flags){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}var ptr;var allocated;var contents=stream.node.contents;if(!(flags&2)&&(contents.buffer===buffer||contents.buffer===buffer.buffer)){allocated=false;ptr=contents.byteOffset}else{if(position>0||position+length<stream.node.usedBytes){if(contents.subarray){contents=contents.subarray(position,position+length)}else{contents=Array.prototype.slice.call(contents,position,position+length)}}allocated=true;var fromHeap=buffer.buffer==HEAP8.buffer;ptr=_malloc(length);if(!ptr){throw new FS.ErrnoError(48)}(fromHeap?HEAP8:buffer).set(contents,ptr)}return{ptr:ptr,allocated:allocated}},msync:function(stream,buffer,offset,length,mmapFlags){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}if(mmapFlags&2){return 0}var bytesWritten=MEMFS.stream_ops.write(stream,buffer,0,length,offset,false);return 0}}};var IDBFS={dbs:{},indexedDB:function(){if(typeof indexedDB!=="undefined")return indexedDB;var ret=null;if(typeof window==="object")ret=window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB;assert(ret,"IDBFS used, but indexedDB not supported");return ret},DB_VERSION:21,DB_STORE_NAME:"FILE_DATA",mount:function(mount){return MEMFS.mount.apply(null,arguments)},syncfs:function(mount,populate,callback){IDBFS.getLocalSet(mount,function(err,local){if(err)return callback(err);IDBFS.getRemoteSet(mount,function(err,remote){if(err)return callback(err);var src=populate?remote:local;var dst=populate?local:remote;IDBFS.reconcile(src,dst,callback)})})},getDB:function(name,callback){var db=IDBFS.dbs[name];if(db){return callback(null,db)}var req;try{req=IDBFS.indexedDB().open(name,IDBFS.DB_VERSION)}catch(e){return callback(e)}if(!req){return callback("Unable to connect to IndexedDB")}req.onupgradeneeded=function(e){var db=e.target.result;var transaction=e.target.transaction;var fileStore;if(db.objectStoreNames.contains(IDBFS.DB_STORE_NAME)){fileStore=transaction.objectStore(IDBFS.DB_STORE_NAME)}else{fileStore=db.createObjectStore(IDBFS.DB_STORE_NAME)}if(!fileStore.indexNames.contains("timestamp")){fileStore.createIndex("timestamp","timestamp",{unique:false})}};req.onsuccess=function(){db=req.result;IDBFS.dbs[name]=db;callback(null,db)};req.onerror=function(e){callback(this.error);e.preventDefault()}},getLocalSet:function(mount,callback){var entries={};function isRealDir(p){return p!=="."&&p!==".."}function toAbsolute(root){return function(p){return PATH.join2(root,p)}}var check=FS.readdir(mount.mountpoint).filter(isRealDir).map(toAbsolute(mount.mountpoint));while(check.length){var path=check.pop();var stat;try{stat=FS.stat(path)}catch(e){return callback(e)}if(FS.isDir(stat.mode)){check.push.apply(check,FS.readdir(path).filter(isRealDir).map(toAbsolute(path)))}entries[path]={timestamp:stat.mtime}}return callback(null,{type:"local",entries:entries})},getRemoteSet:function(mount,callback){var entries={};IDBFS.getDB(mount.mountpoint,function(err,db){if(err)return callback(err);try{var transaction=db.transaction([IDBFS.DB_STORE_NAME],"readonly");transaction.onerror=function(e){callback(this.error);e.preventDefault()};var store=transaction.objectStore(IDBFS.DB_STORE_NAME);var index=store.index("timestamp");index.openKeyCursor().onsuccess=function(event){var cursor=event.target.result;if(!cursor){return callback(null,{type:"remote",db:db,entries:entries})}entries[cursor.primaryKey]={timestamp:cursor.key};cursor.continue()}}catch(e){return callback(e)}})},loadLocalEntry:function(path,callback){var stat,node;try{var lookup=FS.lookupPath(path);node=lookup.node;stat=FS.stat(path)}catch(e){return callback(e)}if(FS.isDir(stat.mode)){return callback(null,{timestamp:stat.mtime,mode:stat.mode})}else if(FS.isFile(stat.mode)){node.contents=MEMFS.getFileDataAsTypedArray(node);return callback(null,{timestamp:stat.mtime,mode:stat.mode,contents:node.contents})}else{return callback(new Error("node type not supported"))}},storeLocalEntry:function(path,entry,callback){try{if(FS.isDir(entry.mode)){FS.mkdir(path,entry.mode)}else if(FS.isFile(entry.mode)){FS.writeFile(path,entry.contents,{canOwn:true})}else{return callback(new Error("node type not supported"))}FS.chmod(path,entry.mode);FS.utime(path,entry.timestamp,entry.timestamp)}catch(e){return callback(e)}callback(null)},removeLocalEntry:function(path,callback){try{var lookup=FS.lookupPath(path);var stat=FS.stat(path);if(FS.isDir(stat.mode)){FS.rmdir(path)}else if(FS.isFile(stat.mode)){FS.unlink(path)}}catch(e){return callback(e)}callback(null)},loadRemoteEntry:function(store,path,callback){var req=store.get(path);req.onsuccess=function(event){callback(null,event.target.result)};req.onerror=function(e){callback(this.error);e.preventDefault()}},storeRemoteEntry:function(store,path,entry,callback){var req=store.put(entry,path);req.onsuccess=function(){callback(null)};req.onerror=function(e){callback(this.error);e.preventDefault()}},removeRemoteEntry:function(store,path,callback){var req=store.delete(path);req.onsuccess=function(){callback(null)};req.onerror=function(e){callback(this.error);e.preventDefault()}},reconcile:function(src,dst,callback){var total=0;var create=[];Object.keys(src.entries).forEach(function(key){var e=src.entries[key];var e2=dst.entries[key];if(!e2||e.timestamp>e2.timestamp){create.push(key);total++}});var remove=[];Object.keys(dst.entries).forEach(function(key){var e=dst.entries[key];var e2=src.entries[key];if(!e2){remove.push(key);total++}});if(!total){return callback(null)}var errored=false;var db=src.type==="remote"?src.db:dst.db;var transaction=db.transaction([IDBFS.DB_STORE_NAME],"readwrite");var store=transaction.objectStore(IDBFS.DB_STORE_NAME);function done(err){if(err&&!errored){errored=true;return callback(err)}}transaction.onerror=function(e){done(this.error);e.preventDefault()};transaction.oncomplete=function(e){if(!errored){callback(null)}};create.sort().forEach(function(path){if(dst.type==="local"){IDBFS.loadRemoteEntry(store,path,function(err,entry){if(err)return done(err);IDBFS.storeLocalEntry(path,entry,done)})}else{IDBFS.loadLocalEntry(path,function(err,entry){if(err)return done(err);IDBFS.storeRemoteEntry(store,path,entry,done)})}});remove.sort().reverse().forEach(function(path){if(dst.type==="local"){IDBFS.removeLocalEntry(path,done)}else{IDBFS.removeRemoteEntry(store,path,done)}})}};var ERRNO_CODES={EPERM:63,ENOENT:44,ESRCH:71,EINTR:27,EIO:29,ENXIO:60,E2BIG:1,ENOEXEC:45,EBADF:8,ECHILD:12,EAGAIN:6,EWOULDBLOCK:6,ENOMEM:48,EACCES:2,EFAULT:21,ENOTBLK:105,EBUSY:10,EEXIST:20,EXDEV:75,ENODEV:43,ENOTDIR:54,EISDIR:31,EINVAL:28,ENFILE:41,EMFILE:33,ENOTTY:59,ETXTBSY:74,EFBIG:22,ENOSPC:51,ESPIPE:70,EROFS:69,EMLINK:34,EPIPE:64,EDOM:18,ERANGE:68,ENOMSG:49,EIDRM:24,ECHRNG:106,EL2NSYNC:156,EL3HLT:107,EL3RST:108,ELNRNG:109,EUNATCH:110,ENOCSI:111,EL2HLT:112,EDEADLK:16,ENOLCK:46,EBADE:113,EBADR:114,EXFULL:115,ENOANO:104,EBADRQC:103,EBADSLT:102,EDEADLOCK:16,EBFONT:101,ENOSTR:100,ENODATA:116,ETIME:117,ENOSR:118,ENONET:119,ENOPKG:120,EREMOTE:121,ENOLINK:47,EADV:122,ESRMNT:123,ECOMM:124,EPROTO:65,EMULTIHOP:36,EDOTDOT:125,EBADMSG:9,ENOTUNIQ:126,EBADFD:127,EREMCHG:128,ELIBACC:129,ELIBBAD:130,ELIBSCN:131,ELIBMAX:132,ELIBEXEC:133,ENOSYS:52,ENOTEMPTY:55,ENAMETOOLONG:37,ELOOP:32,EOPNOTSUPP:138,EPFNOSUPPORT:139,ECONNRESET:15,ENOBUFS:42,EAFNOSUPPORT:5,EPROTOTYPE:67,ENOTSOCK:57,ENOPROTOOPT:50,ESHUTDOWN:140,ECONNREFUSED:14,EADDRINUSE:3,ECONNABORTED:13,ENETUNREACH:40,ENETDOWN:38,ETIMEDOUT:73,EHOSTDOWN:142,EHOSTUNREACH:23,EINPROGRESS:26,EALREADY:7,EDESTADDRREQ:17,EMSGSIZE:35,EPROTONOSUPPORT:66,ESOCKTNOSUPPORT:137,EADDRNOTAVAIL:4,ENETRESET:39,EISCONN:30,ENOTCONN:53,ETOOMANYREFS:141,EUSERS:136,EDQUOT:19,ESTALE:72,ENOTSUP:138,ENOMEDIUM:148,EILSEQ:25,EOVERFLOW:61,ECANCELED:11,ENOTRECOVERABLE:56,EOWNERDEAD:62,ESTRPIPE:135};var NODEFS={isWindows:false,staticInit:function(){NODEFS.isWindows=!!process.platform.match(/^win/);var flags=process["binding"]("constants");if(flags["fs"]){flags=flags["fs"]}NODEFS.flagsForNodeMap={1024:flags["O_APPEND"],64:flags["O_CREAT"],128:flags["O_EXCL"],0:flags["O_RDONLY"],2:flags["O_RDWR"],4096:flags["O_SYNC"],512:flags["O_TRUNC"],1:flags["O_WRONLY"]}},bufferFrom:function(arrayBuffer){return Buffer["alloc"]?Buffer.from(arrayBuffer):new Buffer(arrayBuffer)},convertNodeCode:function(e){var code=e.code;assert(code in ERRNO_CODES);return ERRNO_CODES[code]},mount:function(mount){assert(ENVIRONMENT_HAS_NODE);return NODEFS.createNode(null,"/",NODEFS.getMode(mount.opts.root),0)},createNode:function(parent,name,mode,dev){if(!FS.isDir(mode)&&!FS.isFile(mode)&&!FS.isLink(mode)){throw new FS.ErrnoError(28)}var node=FS.createNode(parent,name,mode);node.node_ops=NODEFS.node_ops;node.stream_ops=NODEFS.stream_ops;return node},getMode:function(path){var stat;try{stat=fs.lstatSync(path);if(NODEFS.isWindows){stat.mode=stat.mode|(stat.mode&292)>>2}}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}return stat.mode},realPath:function(node){var parts=[];while(node.parent!==node){parts.push(node.name);node=node.parent}parts.push(node.mount.opts.root);parts.reverse();return PATH.join.apply(null,parts)},flagsForNode:function(flags){flags&=~2097152;flags&=~2048;flags&=~32768;flags&=~524288;var newFlags=0;for(var k in NODEFS.flagsForNodeMap){if(flags&k){newFlags|=NODEFS.flagsForNodeMap[k];flags^=k}}if(!flags){return newFlags}else{throw new FS.ErrnoError(28)}},node_ops:{getattr:function(node){var path=NODEFS.realPath(node);var stat;try{stat=fs.lstatSync(path)}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}if(NODEFS.isWindows&&!stat.blksize){stat.blksize=4096}if(NODEFS.isWindows&&!stat.blocks){stat.blocks=(stat.size+stat.blksize-1)/stat.blksize|0}return{dev:stat.dev,ino:stat.ino,mode:stat.mode,nlink:stat.nlink,uid:stat.uid,gid:stat.gid,rdev:stat.rdev,size:stat.size,atime:stat.atime,mtime:stat.mtime,ctime:stat.ctime,blksize:stat.blksize,blocks:stat.blocks}},setattr:function(node,attr){var path=NODEFS.realPath(node);try{if(attr.mode!==undefined){fs.chmodSync(path,attr.mode);node.mode=attr.mode}if(attr.timestamp!==undefined){var date=new Date(attr.timestamp);fs.utimesSync(path,date,date)}if(attr.size!==undefined){fs.truncateSync(path,attr.size)}}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}},lookup:function(parent,name){var path=PATH.join2(NODEFS.realPath(parent),name);var mode=NODEFS.getMode(path);return NODEFS.createNode(parent,name,mode)},mknod:function(parent,name,mode,dev){var node=NODEFS.createNode(parent,name,mode,dev);var path=NODEFS.realPath(node);try{if(FS.isDir(node.mode)){fs.mkdirSync(path,node.mode)}else{fs.writeFileSync(path,"",{mode:node.mode})}}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}return node},rename:function(oldNode,newDir,newName){var oldPath=NODEFS.realPath(oldNode);var newPath=PATH.join2(NODEFS.realPath(newDir),newName);try{fs.renameSync(oldPath,newPath)}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}},unlink:function(parent,name){var path=PATH.join2(NODEFS.realPath(parent),name);try{fs.unlinkSync(path)}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}},rmdir:function(parent,name){var path=PATH.join2(NODEFS.realPath(parent),name);try{fs.rmdirSync(path)}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}},readdir:function(node){var path=NODEFS.realPath(node);try{return fs.readdirSync(path)}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}},symlink:function(parent,newName,oldPath){var newPath=PATH.join2(NODEFS.realPath(parent),newName);try{fs.symlinkSync(oldPath,newPath)}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}},readlink:function(node){var path=NODEFS.realPath(node);try{path=fs.readlinkSync(path);path=NODEJS_PATH.relative(NODEJS_PATH.resolve(node.mount.opts.root),path);return path}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}}},stream_ops:{open:function(stream){var path=NODEFS.realPath(stream.node);try{if(FS.isFile(stream.node.mode)){stream.nfd=fs.openSync(path,NODEFS.flagsForNode(stream.flags))}}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}},close:function(stream){try{if(FS.isFile(stream.node.mode)&&stream.nfd){fs.closeSync(stream.nfd)}}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}},read:function(stream,buffer,offset,length,position){if(length===0)return 0;try{return fs.readSync(stream.nfd,NODEFS.bufferFrom(buffer.buffer),offset,length,position)}catch(e){throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}},write:function(stream,buffer,offset,length,position){try{return fs.writeSync(stream.nfd,NODEFS.bufferFrom(buffer.buffer),offset,length,position)}catch(e){throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}},llseek:function(stream,offset,whence){var position=offset;if(whence===1){position+=stream.position}else if(whence===2){if(FS.isFile(stream.node.mode)){try{var stat=fs.fstatSync(stream.nfd);position+=stat.size}catch(e){throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}}}if(position<0){throw new FS.ErrnoError(28)}return position}}};var WORKERFS={DIR_MODE:16895,FILE_MODE:33279,reader:null,mount:function(mount){assert(ENVIRONMENT_IS_WORKER);if(!WORKERFS.reader)WORKERFS.reader=new FileReaderSync;var root=WORKERFS.createNode(null,"/",WORKERFS.DIR_MODE,0);var createdParents={};function ensureParent(path){var parts=path.split("/");var parent=root;for(var i=0;i<parts.length-1;i++){var curr=parts.slice(0,i+1).join("/");if(!createdParents[curr]){createdParents[curr]=WORKERFS.createNode(parent,parts[i],WORKERFS.DIR_MODE,0)}parent=createdParents[curr]}return parent}function base(path){var parts=path.split("/");return parts[parts.length-1]}Array.prototype.forEach.call(mount.opts["files"]||[],function(file){WORKERFS.createNode(ensureParent(file.name),base(file.name),WORKERFS.FILE_MODE,0,file,file.lastModifiedDate)});(mount.opts["blobs"]||[]).forEach(function(obj){WORKERFS.createNode(ensureParent(obj["name"]),base(obj["name"]),WORKERFS.FILE_MODE,0,obj["data"])});(mount.opts["packages"]||[]).forEach(function(pack){pack["metadata"].files.forEach(function(file){var name=file.filename.substr(1);WORKERFS.createNode(ensureParent(name),base(name),WORKERFS.FILE_MODE,0,pack["blob"].slice(file.start,file.end))})});return root},createNode:function(parent,name,mode,dev,contents,mtime){var node=FS.createNode(parent,name,mode);node.mode=mode;node.node_ops=WORKERFS.node_ops;node.stream_ops=WORKERFS.stream_ops;node.timestamp=(mtime||new Date).getTime();assert(WORKERFS.FILE_MODE!==WORKERFS.DIR_MODE);if(mode===WORKERFS.FILE_MODE){node.size=contents.size;node.contents=contents}else{node.size=4096;node.contents={}}if(parent){parent.contents[name]=node}return node},node_ops:{getattr:function(node){return{dev:1,ino:undefined,mode:node.mode,nlink:1,uid:0,gid:0,rdev:undefined,size:node.size,atime:new Date(node.timestamp),mtime:new Date(node.timestamp),ctime:new Date(node.timestamp),blksize:4096,blocks:Math.ceil(node.size/4096)}},setattr:function(node,attr){if(attr.mode!==undefined){node.mode=attr.mode}if(attr.timestamp!==undefined){node.timestamp=attr.timestamp}},lookup:function(parent,name){throw new FS.ErrnoError(44)},mknod:function(parent,name,mode,dev){throw new FS.ErrnoError(63)},rename:function(oldNode,newDir,newName){throw new FS.ErrnoError(63)},unlink:function(parent,name){throw new FS.ErrnoError(63)},rmdir:function(parent,name){throw new FS.ErrnoError(63)},readdir:function(node){var entries=[".",".."];for(var key in node.contents){if(!node.contents.hasOwnProperty(key)){continue}entries.push(key)}return entries},symlink:function(parent,newName,oldPath){throw new FS.ErrnoError(63)},readlink:function(node){throw new FS.ErrnoError(63)}},stream_ops:{read:function(stream,buffer,offset,length,position){if(position>=stream.node.size)return 0;var chunk=stream.node.contents.slice(position,position+length);var ab=WORKERFS.reader.readAsArrayBuffer(chunk);buffer.set(new Uint8Array(ab),offset);return chunk.size},write:function(stream,buffer,offset,length,position){throw new FS.ErrnoError(29)},llseek:function(stream,offset,whence){var position=offset;if(whence===1){position+=stream.position}else if(whence===2){if(FS.isFile(stream.node.mode)){position+=stream.node.size}}if(position<0){throw new FS.ErrnoError(28)}return position}}};var FS={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:false,ignorePermissions:true,trackingDelegate:{},tracking:{openFlags:{READ:1,WRITE:2}},ErrnoError:null,genericErrors:{},filesystems:null,syncFSRequests:0,handleFSError:function(e){if(!(e instanceof FS.ErrnoError))throw e+" : "+stackTrace();return ___setErrNo(e.errno)},lookupPath:function(path,opts){path=PATH_FS.resolve(FS.cwd(),path);opts=opts||{};if(!path)return{path:"",node:null};var defaults={follow_mount:true,recurse_count:0};for(var key in defaults){if(opts[key]===undefined){opts[key]=defaults[key]}}if(opts.recurse_count>8){throw new FS.ErrnoError(32)}var parts=PATH.normalizeArray(path.split("/").filter(function(p){return!!p}),false);var current=FS.root;var current_path="/";for(var i=0;i<parts.length;i++){var islast=i===parts.length-1;if(islast&&opts.parent){break}current=FS.lookupNode(current,parts[i]);current_path=PATH.join2(current_path,parts[i]);if(FS.isMountpoint(current)){if(!islast||islast&&opts.follow_mount){current=current.mounted.root}}if(!islast||opts.follow){var count=0;while(FS.isLink(current.mode)){var link=FS.readlink(current_path);current_path=PATH_FS.resolve(PATH.dirname(current_path),link);var lookup=FS.lookupPath(current_path,{recurse_count:opts.recurse_count});current=lookup.node;if(count++>40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath:function(node){var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?mount+"/"+path:mount+path}path=path?node.name+"/"+path:node.name;node=node.parent}},hashName:function(parentid,name){var hash=0;for(var i=0;i<name.length;i++){hash=(hash<<5)-hash+name.charCodeAt(i)|0}return(parentid+hash>>>0)%FS.nameTable.length},hashAddNode:function(node){var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode:function(node){var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode:function(parent,name){var err=FS.mayLookup(parent);if(err){throw new FS.ErrnoError(err,parent)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode:function(parent,name,mode,rdev){if(!FS.FSNode){FS.FSNode=function(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev};FS.FSNode.prototype={};var readMode=292|73;var writeMode=146;Object.defineProperties(FS.FSNode.prototype,{read:{get:function(){return(this.mode&readMode)===readMode},set:function(val){val?this.mode|=readMode:this.mode&=~readMode}},write:{get:function(){return(this.mode&writeMode)===writeMode},set:function(val){val?this.mode|=writeMode:this.mode&=~writeMode}},isFolder:{get:function(){return FS.isDir(this.mode)}},isDevice:{get:function(){return FS.isChrdev(this.mode)}}})}var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode:function(node){FS.hashRemoveNode(node)},isRoot:function(node){return node===node.parent},isMountpoint:function(node){return!!node.mounted},isFile:function(mode){return(mode&61440)===32768},isDir:function(mode){return(mode&61440)===16384},isLink:function(mode){return(mode&61440)===40960},isChrdev:function(mode){return(mode&61440)===8192},isBlkdev:function(mode){return(mode&61440)===24576},isFIFO:function(mode){return(mode&61440)===4096},isSocket:function(mode){return(mode&49152)===49152},flagModes:{"r":0,"rs":1052672,"r+":2,"w":577,"wx":705,"xw":705,"w+":578,"wx+":706,"xw+":706,"a":1089,"ax":1217,"xa":1217,"a+":1090,"ax+":1218,"xa+":1218},modeStringToFlags:function(str){var flags=FS.flagModes[str];if(typeof flags==="undefined"){throw new Error("Unknown file open mode: "+str)}return flags},flagsToPermissionString:function(flag){var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions:function(node,perms){if(FS.ignorePermissions){return 0}if(perms.indexOf("r")!==-1&&!(node.mode&292)){return 2}else if(perms.indexOf("w")!==-1&&!(node.mode&146)){return 2}else if(perms.indexOf("x")!==-1&&!(node.mode&73)){return 2}return 0},mayLookup:function(dir){var err=FS.nodePermissions(dir,"x");if(err)return err;if(!dir.node_ops.lookup)return 2;return 0},mayCreate:function(dir,name){try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete:function(dir,name,isdir){var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var err=FS.nodePermissions(dir,"wx");if(err){return err}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen:function(node,flags){if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd:function(fd_start,fd_end){fd_start=fd_start||0;fd_end=fd_end||FS.MAX_OPEN_FDS;for(var fd=fd_start;fd<=fd_end;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStream:function(fd){return FS.streams[fd]},createStream:function(stream,fd_start,fd_end){if(!FS.FSStream){FS.FSStream=function(){};FS.FSStream.prototype={};Object.defineProperties(FS.FSStream.prototype,{object:{get:function(){return this.node},set:function(val){this.node=val}},isRead:{get:function(){return(this.flags&2097155)!==1}},isWrite:{get:function(){return(this.flags&2097155)!==0}},isAppend:{get:function(){return this.flags&1024}}})}var newStream=new FS.FSStream;for(var p in stream){newStream[p]=stream[p]}stream=newStream;var fd=FS.nextfd(fd_start,fd_end);stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream:function(fd){FS.streams[fd]=null},chrdev_stream_ops:{open:function(stream){var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;if(stream.stream_ops.open){stream.stream_ops.open(stream)}},llseek:function(){throw new FS.ErrnoError(70)}},major:function(dev){return dev>>8},minor:function(dev){return dev&255},makedev:function(ma,mi){return ma<<8|mi},registerDevice:function(dev,ops){FS.devices[dev]={stream_ops:ops}},getDevice:function(dev){return FS.devices[dev]},getMounts:function(mount){var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push.apply(check,m.mounts)}return mounts},syncfs:function(populate,callback){if(typeof populate==="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){console.log("warning: "+FS.syncFSRequests+" FS.syncfs operations in flight at once, probably just doing extra work")}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(err){FS.syncFSRequests--;return callback(err)}function done(err){if(err){if(!done.errored){done.errored=true;return doCallback(err)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(function(mount){if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount:function(type,opts,mountpoint){var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount:function(mountpoint){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(function(hash){var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.indexOf(current.mount)!==-1){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup:function(parent,name){return parent.node_ops.lookup(parent,name)},mknod:function(path,mode,dev){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var err=FS.mayCreate(parent,name);if(err){throw new FS.ErrnoError(err)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create:function(path,mode){mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir:function(path,mode){mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree:function(path,mode){var dirs=path.split("/");var d="";for(var i=0;i<dirs.length;++i){if(!dirs[i])continue;d+="/"+dirs[i];try{FS.mkdir(d,mode)}catch(e){if(e.errno!=20)throw e}}},mkdev:function(path,mode,dev){if(typeof dev==="undefined"){dev=mode;mode=438}mode|=8192;return FS.mknod(path,mode,dev)},symlink:function(oldpath,newpath){if(!PATH_FS.resolve(oldpath)){throw new FS.ErrnoError(44)}var lookup=FS.lookupPath(newpath,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var newname=PATH.basename(newpath);var err=FS.mayCreate(parent,newname);if(err){throw new FS.ErrnoError(err)}if(!parent.node_ops.symlink){throw new FS.ErrnoError(63)}return parent.node_ops.symlink(parent,newname,oldpath)},rename:function(old_path,new_path){var old_dirname=PATH.dirname(old_path);var new_dirname=PATH.dirname(new_path);var old_name=PATH.basename(old_path);var new_name=PATH.basename(new_path);var lookup,old_dir,new_dir;try{lookup=FS.lookupPath(old_path,{parent:true});old_dir=lookup.node;lookup=FS.lookupPath(new_path,{parent:true});new_dir=lookup.node}catch(e){throw new FS.ErrnoError(10)}if(!old_dir||!new_dir)throw new FS.ErrnoError(44);if(old_dir.mount!==new_dir.mount){throw new FS.ErrnoError(75)}var old_node=FS.lookupNode(old_dir,old_name);var relative=PATH_FS.relative(old_path,new_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(28)}relative=PATH_FS.relative(new_path,old_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(55)}var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(old_node===new_node){return}var isdir=FS.isDir(old_node.mode);var err=FS.mayDelete(old_dir,old_name,isdir);if(err){throw new FS.ErrnoError(err)}err=new_node?FS.mayDelete(new_dir,new_name,isdir):FS.mayCreate(new_dir,new_name);if(err){throw new FS.ErrnoError(err)}if(!old_dir.node_ops.rename){throw new FS.ErrnoError(63)}if(FS.isMountpoint(old_node)||new_node&&FS.isMountpoint(new_node)){throw new FS.ErrnoError(10)}if(new_dir!==old_dir){err=FS.nodePermissions(old_dir,"w");if(err){throw new FS.ErrnoError(err)}}try{if(FS.trackingDelegate["willMovePath"]){FS.trackingDelegate["willMovePath"](old_path,new_path)}}catch(e){console.log("FS.trackingDelegate['willMovePath']('"+old_path+"', '"+new_path+"') threw an exception: "+e.message)}FS.hashRemoveNode(old_node);try{old_dir.node_ops.rename(old_node,new_dir,new_name)}catch(e){throw e}finally{FS.hashAddNode(old_node)}try{if(FS.trackingDelegate["onMovePath"])FS.trackingDelegate["onMovePath"](old_path,new_path)}catch(e){console.log("FS.trackingDelegate['onMovePath']('"+old_path+"', '"+new_path+"') threw an exception: "+e.message)}},rmdir:function(path){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var err=FS.mayDelete(parent,name,true);if(err){throw new FS.ErrnoError(err)}if(!parent.node_ops.rmdir){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}try{if(FS.trackingDelegate["willDeletePath"]){FS.trackingDelegate["willDeletePath"](path)}}catch(e){console.log("FS.trackingDelegate['willDeletePath']('"+path+"') threw an exception: "+e.message)}parent.node_ops.rmdir(parent,name);FS.destroyNode(node);try{if(FS.trackingDelegate["onDeletePath"])FS.trackingDelegate["onDeletePath"](path)}catch(e){console.log("FS.trackingDelegate['onDeletePath']('"+path+"') threw an exception: "+e.message)}},readdir:function(path){var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;if(!node.node_ops.readdir){throw new FS.ErrnoError(54)}return node.node_ops.readdir(node)},unlink:function(path){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var err=FS.mayDelete(parent,name,false);if(err){throw new FS.ErrnoError(err)}if(!parent.node_ops.unlink){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}try{if(FS.trackingDelegate["willDeletePath"]){FS.trackingDelegate["willDeletePath"](path)}}catch(e){console.log("FS.trackingDelegate['willDeletePath']('"+path+"') threw an exception: "+e.message)}parent.node_ops.unlink(parent,name);FS.destroyNode(node);try{if(FS.trackingDelegate["onDeletePath"])FS.trackingDelegate["onDeletePath"](path)}catch(e){console.log("FS.trackingDelegate['onDeletePath']('"+path+"') threw an exception: "+e.message)}},readlink:function(path){var lookup=FS.lookupPath(path);var link=lookup.node;if(!link){throw new FS.ErrnoError(44)}if(!link.node_ops.readlink){throw new FS.ErrnoError(28)}return PATH_FS.resolve(FS.getPath(link.parent),link.node_ops.readlink(link))},stat:function(path,dontFollow){var lookup=FS.lookupPath(path,{follow:!dontFollow});var node=lookup.node;if(!node){throw new FS.ErrnoError(44)}if(!node.node_ops.getattr){throw new FS.ErrnoError(63)}return node.node_ops.getattr(node)},lstat:function(path){return FS.stat(path,true)},chmod:function(path,mode,dontFollow){var node;if(typeof path==="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{mode:mode&4095|node.mode&~4095,timestamp:Date.now()})},lchmod:function(path,mode){FS.chmod(path,mode,true)},fchmod:function(fd,mode){var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chmod(stream.node,mode)},chown:function(path,uid,gid,dontFollow){var node;if(typeof path==="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{timestamp:Date.now()})},lchown:function(path,uid,gid){FS.chown(path,uid,gid,true)},fchown:function(fd,uid,gid){var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chown(stream.node,uid,gid)},truncate:function(path,len){if(len<0){throw new FS.ErrnoError(28)}var node;if(typeof path==="string"){var lookup=FS.lookupPath(path,{follow:true});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}if(FS.isDir(node.mode)){throw new FS.ErrnoError(31)}if(!FS.isFile(node.mode)){throw new FS.ErrnoError(28)}var err=FS.nodePermissions(node,"w");if(err){throw new FS.ErrnoError(err)}node.node_ops.setattr(node,{size:len,timestamp:Date.now()})},ftruncate:function(fd,len){var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(28)}FS.truncate(stream.node,len)},utime:function(path,atime,mtime){var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;node.node_ops.setattr(node,{timestamp:Math.max(atime,mtime)})},open:function(path,flags,mode,fd_start,fd_end){if(path===""){throw new FS.ErrnoError(44)}flags=typeof flags==="string"?FS.modeStringToFlags(flags):flags;mode=typeof mode==="undefined"?438:mode;if(flags&64){mode=mode&4095|32768}else{mode=0}var node;if(typeof path==="object"){node=path}else{path=PATH.normalize(path);try{var lookup=FS.lookupPath(path,{follow:!(flags&131072)});node=lookup.node}catch(e){}}var created=false;if(flags&64){if(node){if(flags&128){throw new FS.ErrnoError(20)}}else{node=FS.mknod(path,mode,0);created=true}}if(!node){throw new FS.ErrnoError(44)}if(FS.isChrdev(node.mode)){flags&=~512}if(flags&65536&&!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}if(!created){var err=FS.mayOpen(node,flags);if(err){throw new FS.ErrnoError(err)}}if(flags&512){FS.truncate(node,0)}flags&=~(128|512);var stream=FS.createStream({node:node,path:FS.getPath(node),flags:flags,seekable:true,position:0,stream_ops:node.stream_ops,ungotten:[],error:false},fd_start,fd_end);if(stream.stream_ops.open){stream.stream_ops.open(stream)}if(Module["logReadFiles"]&&!(flags&1)){if(!FS.readFiles)FS.readFiles={};if(!(path in FS.readFiles)){FS.readFiles[path]=1;console.log("FS.trackingDelegate error on read file: "+path)}}try{if(FS.trackingDelegate["onOpenFile"]){var trackingFlags=0;if((flags&2097155)!==1){trackingFlags|=FS.tracking.openFlags.READ}if((flags&2097155)!==0){trackingFlags|=FS.tracking.openFlags.WRITE}FS.trackingDelegate["onOpenFile"](path,trackingFlags)}}catch(e){console.log("FS.trackingDelegate['onOpenFile']('"+path+"', flags) threw an exception: "+e.message)}return stream},close:function(stream){if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(stream.getdents)stream.getdents=null;try{if(stream.stream_ops.close){stream.stream_ops.close(stream)}}catch(e){throw e}finally{FS.closeStream(stream.fd)}stream.fd=null},isClosed:function(stream){return stream.fd===null},llseek:function(stream,offset,whence){if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(!stream.seekable||!stream.stream_ops.llseek){throw new FS.ErrnoError(70)}if(whence!=0&&whence!=1&&whence!=2){throw new FS.ErrnoError(28)}stream.position=stream.stream_ops.llseek(stream,offset,whence);stream.ungotten=[];return stream.position},read:function(stream,buffer,offset,length,position){if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.read){throw new FS.ErrnoError(28)}var seeking=typeof position!=="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesRead=stream.stream_ops.read(stream,buffer,offset,length,position);if(!seeking)stream.position+=bytesRead;return bytesRead},write:function(stream,buffer,offset,length,position,canOwn){if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.write){throw new FS.ErrnoError(28)}if(stream.flags&1024){FS.llseek(stream,0,2)}var seeking=typeof position!=="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesWritten=stream.stream_ops.write(stream,buffer,offset,length,position,canOwn);if(!seeking)stream.position+=bytesWritten;try{if(stream.path&&FS.trackingDelegate["onWriteToFile"])FS.trackingDelegate["onWriteToFile"](stream.path)}catch(e){console.log("FS.trackingDelegate['onWriteToFile']('"+stream.path+"') threw an exception: "+e.message)}return bytesWritten},allocate:function(stream,offset,length){if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(offset<0||length<=0){throw new FS.ErrnoError(28)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(!FS.isFile(stream.node.mode)&&!FS.isDir(stream.node.mode)){throw new FS.ErrnoError(43)}if(!stream.stream_ops.allocate){throw new FS.ErrnoError(138)}stream.stream_ops.allocate(stream,offset,length)},mmap:function(stream,buffer,offset,length,position,prot,flags){if((prot&2)!==0&&(flags&2)===0&&(stream.flags&2097155)!==2){throw new FS.ErrnoError(2)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(2)}if(!stream.stream_ops.mmap){throw new FS.ErrnoError(43)}return stream.stream_ops.mmap(stream,buffer,offset,length,position,prot,flags)},msync:function(stream,buffer,offset,length,mmapFlags){if(!stream||!stream.stream_ops.msync){return 0}return stream.stream_ops.msync(stream,buffer,offset,length,mmapFlags)},munmap:function(stream){return 0},ioctl:function(stream,cmd,arg){if(!stream.stream_ops.ioctl){throw new FS.ErrnoError(59)}return stream.stream_ops.ioctl(stream,cmd,arg)},readFile:function(path,opts){opts=opts||{};opts.flags=opts.flags||"r";opts.encoding=opts.encoding||"binary";if(opts.encoding!=="utf8"&&opts.encoding!=="binary"){throw new Error('Invalid encoding type "'+opts.encoding+'"')}var ret;var stream=FS.open(path,opts.flags);var stat=FS.stat(path);var length=stat.size;var buf=new Uint8Array(length);FS.read(stream,buf,0,length,0);if(opts.encoding==="utf8"){ret=UTF8ArrayToString(buf,0)}else if(opts.encoding==="binary"){ret=buf}FS.close(stream);return ret},writeFile:function(path,data,opts){opts=opts||{};opts.flags=opts.flags||"w";var stream=FS.open(path,opts.flags,opts.mode);if(typeof data==="string"){var buf=new Uint8Array(lengthBytesUTF8(data)+1);var actualNumBytes=stringToUTF8Array(data,buf,0,buf.length);FS.write(stream,buf,0,actualNumBytes,undefined,opts.canOwn)}else if(ArrayBuffer.isView(data)){FS.write(stream,data,0,data.byteLength,undefined,opts.canOwn)}else{throw new Error("Unsupported data type")}FS.close(stream)},cwd:function(){return FS.currentPath},chdir:function(path){var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var err=FS.nodePermissions(lookup.node,"x");if(err){throw new FS.ErrnoError(err)}FS.currentPath=lookup.path},createDefaultDirectories:function(){FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices:function(){FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:function(){return 0},write:function(stream,buffer,offset,length,pos){return length}});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var random_device;if(typeof crypto==="object"&&typeof crypto["getRandomValues"]==="function"){var randomBuffer=new Uint8Array(1);random_device=function(){crypto.getRandomValues(randomBuffer);return randomBuffer[0]}}else if(ENVIRONMENT_IS_NODE){try{var crypto_module=require("crypto");random_device=function(){return crypto_module["randomBytes"](1)[0]}}catch(e){}}else{}if(!random_device){random_device=function(){abort("random_device")}}FS.createDevice("/dev","random",random_device);FS.createDevice("/dev","urandom",random_device);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories:function(){FS.mkdir("/proc");FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount:function(){var node=FS.createNode("/proc/self","fd",16384|511,73);node.node_ops={lookup:function(parent,name){var fd=+name;var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:function(){return stream.path}}};ret.parent=ret;return ret}};return node}},{},"/proc/self/fd")},createStandardStreams:function(){if(Module["stdin"]){FS.createDevice("/dev","stdin",Module["stdin"])}else{FS.symlink("/dev/tty","/dev/stdin")}if(Module["stdout"]){FS.createDevice("/dev","stdout",null,Module["stdout"])}else{FS.symlink("/dev/tty","/dev/stdout")}if(Module["stderr"]){FS.createDevice("/dev","stderr",null,Module["stderr"])}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin","r");var stdout=FS.open("/dev/stdout","w");var stderr=FS.open("/dev/stderr","w")},ensureErrnoError:function(){if(FS.ErrnoError)return;FS.ErrnoError=function ErrnoError(errno,node){this.node=node;this.setErrno=function(errno){this.errno=errno};this.setErrno(errno);this.message="FS error"};FS.ErrnoError.prototype=new Error;FS.ErrnoError.prototype.constructor=FS.ErrnoError;[44].forEach(function(code){FS.genericErrors[code]=new FS.ErrnoError(code);FS.genericErrors[code].stack="<generic error, no stack>"})},staticInit:function(){FS.ensureErrnoError();FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={"MEMFS":MEMFS,"IDBFS":IDBFS,"NODEFS":NODEFS,"WORKERFS":WORKERFS}},init:function(input,output,error){FS.init.initialized=true;FS.ensureErrnoError();Module["stdin"]=input||Module["stdin"];Module["stdout"]=output||Module["stdout"];Module["stderr"]=error||Module["stderr"];FS.createStandardStreams()},quit:function(){FS.init.initialized=false;var fflush=Module["_fflush"];if(fflush)fflush(0);for(var i=0;i<FS.streams.length;i++){var stream=FS.streams[i];if(!stream){continue}FS.close(stream)}},getMode:function(canRead,canWrite){var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode},joinPath:function(parts,forceRelative){var path=PATH.join.apply(null,parts);if(forceRelative&&path[0]=="/")path=path.substr(1);return path},absolutePath:function(relative,base){return PATH_FS.resolve(base,relative)},standardizePath:function(path){return PATH.normalize(path)},findObject:function(path,dontResolveLastLink){var ret=FS.analyzePath(path,dontResolveLastLink);if(ret.exists){return ret.object}else{___setErrNo(ret.error);return null}},analyzePath:function(path,dontResolveLastLink){try{var lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});path=lookup.path}catch(e){}var ret={isRoot:false,exists:false,error:0,name:null,path:null,object:null,parentExists:false,parentPath:null,parentObject:null};try{var lookup=FS.lookupPath(path,{parent:true});ret.parentExists=true;ret.parentPath=lookup.path;ret.parentObject=lookup.node;ret.name=PATH.basename(path);lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});ret.exists=true;ret.path=lookup.path;ret.object=lookup.node;ret.name=lookup.node.name;ret.isRoot=lookup.path==="/"}catch(e){ret.error=e.errno}return ret},createFolder:function(parent,name,canRead,canWrite){var path=PATH.join2(typeof parent==="string"?parent:FS.getPath(parent),name);var mode=FS.getMode(canRead,canWrite);return FS.mkdir(path,mode)},createPath:function(parent,path,canRead,canWrite){parent=typeof parent==="string"?parent:FS.getPath(parent);var parts=path.split("/").reverse();while(parts.length){var part=parts.pop();if(!part)continue;var current=PATH.join2(parent,part);try{FS.mkdir(current)}catch(e){}parent=current}return current},createFile:function(parent,name,properties,canRead,canWrite){var path=PATH.join2(typeof parent==="string"?parent:FS.getPath(parent),name);var mode=FS.getMode(canRead,canWrite);return FS.create(path,mode)},createDataFile:function(parent,name,data,canRead,canWrite,canOwn){var path=name?PATH.join2(typeof parent==="string"?parent:FS.getPath(parent),name):parent;var mode=FS.getMode(canRead,canWrite);var node=FS.create(path,mode);if(data){if(typeof data==="string"){var arr=new Array(data.length);for(var i=0,len=data.length;i<len;++i)arr[i]=data.charCodeAt(i);data=arr}FS.chmod(node,mode|146);var stream=FS.open(node,"w");FS.write(stream,data,0,data.length,0,canOwn);FS.close(stream);FS.chmod(node,mode)}return node},createDevice:function(parent,name,input,output){var path=PATH.join2(typeof parent==="string"?parent:FS.getPath(parent),name);var mode=FS.getMode(!!input,!!output);if(!FS.createDevice.major)FS.createDevice.major=64;var dev=FS.makedev(FS.createDevice.major++,0);FS.registerDevice(dev,{open:function(stream){stream.seekable=false},close:function(stream){if(output&&output.buffer&&output.buffer.length){output(10)}},read:function(stream,buffer,offset,length,pos){var bytesRead=0;for(var i=0;i<length;i++){var result;try{result=input()}catch(e){throw new FS.ErrnoError(29)}if(result===undefined&&bytesRead===0){throw new FS.ErrnoError(6)}if(result===null||result===undefined)break;bytesRead++;buffer[offset+i]=result}if(bytesRead){stream.node.timestamp=Date.now()}return bytesRead},write:function(stream,buffer,offset,length,pos){for(var i=0;i<length;i++){try{output(buffer[offset+i])}catch(e){throw new FS.ErrnoError(29)}}if(length){stream.node.timestamp=Date.now()}return i}});return FS.mkdev(path,mode,dev)},createLink:function(parent,name,target,canRead,canWrite){var path=PATH.join2(typeof parent==="string"?parent:FS.getPath(parent),name);return FS.symlink(target,path)},forceLoadFile:function(obj){if(obj.isDevice||obj.isFolder||obj.link||obj.contents)return true;var success=true;if(typeof XMLHttpRequest!=="undefined"){throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.")}else if(read_){try{obj.contents=intArrayFromString(read_(obj.url),true);obj.usedBytes=obj.contents.length}catch(e){success=false}}else{throw new Error("Cannot load without read() or XMLHttpRequest.")}if(!success)___setErrNo(29);return success},createLazyFile:function(parent,name,url,canRead,canWrite){function LazyUint8Array(){this.lengthKnown=false;this.chunks=[]}LazyUint8Array.prototype.get=function LazyUint8Array_get(idx){if(idx>this.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]};LazyUint8Array.prototype.setDataGetter=function LazyUint8Array_setDataGetter(getter){this.getter=getter};LazyUint8Array.prototype.cacheLength=function LazyUint8Array_cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=function(from,to){if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);if(typeof Uint8Array!="undefined")xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}else{return intArrayFromString(xhr.responseText||"",true)}};var lazyArray=this;lazyArray.setDataGetter(function(chunkNum){var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]==="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]==="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;console.log("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true};if(typeof XMLHttpRequest!=="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;Object.defineProperties(lazyArray,{length:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._length}},chunkSize:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}});var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(function(key){var fn=node.stream_ops[key];stream_ops[key]=function forceLoadLazyFile(){if(!FS.forceLoadFile(node)){throw new FS.ErrnoError(29)}return fn.apply(null,arguments)}});stream_ops.read=function stream_ops_read(stream,buffer,offset,length,position){if(!FS.forceLoadFile(node)){throw new FS.ErrnoError(29)}var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i<size;i++){buffer[offset+i]=contents[position+i]}}else{for(var i=0;i<size;i++){buffer[offset+i]=contents.get(position+i)}}return size};node.stream_ops=stream_ops;return node},createPreloadedFile:function(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish){Browser.init();var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency("cp "+fullname);function processData(byteArray){function finish(byteArray){if(preFinish)preFinish();if(!dontCreateFile){FS.createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}if(onload)onload();removeRunDependency(dep)}var handled=false;Module["preloadPlugins"].forEach(function(plugin){if(handled)return;if(plugin["canHandle"](fullname)){plugin["handle"](byteArray,fullname,finish,function(){if(onerror)onerror();removeRunDependency(dep)});handled=true}});if(!handled)finish(byteArray)}addRunDependency(dep);if(typeof url=="string"){Browser.asyncLoad(url,function(byteArray){processData(byteArray)},onerror)}else{processData(url)}},indexedDB:function(){return window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB},DB_NAME:function(){return"EM_FS_"+window.location.pathname},DB_VERSION:20,DB_STORE_NAME:"FILE_DATA",saveFilesToDB:function(paths,onload,onerror){onload=onload||function(){};onerror=onerror||function(){};var indexedDB=FS.indexedDB();try{var openRequest=indexedDB.open(FS.DB_NAME(),FS.DB_VERSION)}catch(e){return onerror(e)}openRequest.onupgradeneeded=function openRequest_onupgradeneeded(){console.log("creating db");var db=openRequest.result;db.createObjectStore(FS.DB_STORE_NAME)};openRequest.onsuccess=function openRequest_onsuccess(){var db=openRequest.result;var transaction=db.transaction([FS.DB_STORE_NAME],"readwrite");var files=transaction.objectStore(FS.DB_STORE_NAME);var ok=0,fail=0,total=paths.length;function finish(){if(fail==0)onload();else onerror()}paths.forEach(function(path){var putRequest=files.put(FS.analyzePath(path).object.contents,path);putRequest.onsuccess=function putRequest_onsuccess(){ok++;if(ok+fail==total)finish()};putRequest.onerror=function putRequest_onerror(){fail++;if(ok+fail==total)finish()}});transaction.onerror=onerror};openRequest.onerror=onerror},loadFilesFromDB:function(paths,onload,onerror){onload=onload||function(){};onerror=onerror||function(){};var indexedDB=FS.indexedDB();try{var openRequest=indexedDB.open(FS.DB_NAME(),FS.DB_VERSION)}catch(e){return onerror(e)}openRequest.onupgradeneeded=onerror;openRequest.onsuccess=function openRequest_onsuccess(){var db=openRequest.result;try{var transaction=db.transaction([FS.DB_STORE_NAME],"readonly")}catch(e){onerror(e);return}var files=transaction.objectStore(FS.DB_STORE_NAME);var ok=0,fail=0,total=paths.length;function finish(){if(fail==0)onload();else onerror()}paths.forEach(function(path){var getRequest=files.get(path);getRequest.onsuccess=function getRequest_onsuccess(){if(FS.analyzePath(path).exists){FS.unlink(path)}FS.createDataFile(PATH.dirname(path),PATH.basename(path),getRequest.result,true,true,true);ok++;if(ok+fail==total)finish()};getRequest.onerror=function getRequest_onerror(){fail++;if(ok+fail==total)finish()}});transaction.onerror=onerror};openRequest.onerror=onerror}};function _emscripten_set_main_loop_timing(mode,value){Browser.mainLoop.timingMode=mode;Browser.mainLoop.timingValue=value;if(!Browser.mainLoop.func){return 1}if(mode==0){Browser.mainLoop.scheduler=function Browser_mainLoop_scheduler_setTimeout(){var timeUntilNextTick=Math.max(0,Browser.mainLoop.tickStartTime+value-_emscripten_get_now())|0;setTimeout(Browser.mainLoop.runner,timeUntilNextTick)};Browser.mainLoop.method="timeout"}else if(mode==1){Browser.mainLoop.scheduler=function Browser_mainLoop_scheduler_rAF(){Browser.requestAnimationFrame(Browser.mainLoop.runner)};Browser.mainLoop.method="rAF"}else if(mode==2){if(typeof setImmediate==="undefined"){var setImmediates=[];var emscriptenMainLoopMessageId="setimmediate";var Browser_setImmediate_messageHandler=function(event){if(event.data===emscriptenMainLoopMessageId||event.data.target===emscriptenMainLoopMessageId){event.stopPropagation();setImmediates.shift()()}};addEventListener("message",Browser_setImmediate_messageHandler,true);setImmediate=function Browser_emulated_setImmediate(func){setImmediates.push(func);if(ENVIRONMENT_IS_WORKER){if(Module["setImmediates"]===undefined)Module["setImmediates"]=[];Module["setImmediates"].push(func);postMessage({target:emscriptenMainLoopMessageId})}else postMessage(emscriptenMainLoopMessageId,"*")}}Browser.mainLoop.scheduler=function Browser_mainLoop_scheduler_setImmediate(){setImmediate(Browser.mainLoop.runner)};Browser.mainLoop.method="immediate"}return 0}function _emscripten_get_now(){abort()}function _emscripten_set_main_loop(func,fps,simulateInfiniteLoop,arg,noSetTiming){noExitRuntime=true;assert(!Browser.mainLoop.func,"emscripten_set_main_loop: there can only be one main loop function at once: call emscripten_cancel_main_loop to cancel the previous one before setting a new one with different parameters.");Browser.mainLoop.func=func;Browser.mainLoop.arg=arg;var browserIterationFunc;if(typeof arg!=="undefined"){browserIterationFunc=function(){Module["dynCall_vi"](func,arg)}}else{browserIterationFunc=function(){Module["dynCall_v"](func)}}var thisMainLoopId=Browser.mainLoop.currentlyRunningMainloop;Browser.mainLoop.runner=function Browser_mainLoop_runner(){if(ABORT)return;if(Browser.mainLoop.queue.length>0){var start=Date.now();var blocker=Browser.mainLoop.queue.shift();blocker.func(blocker.arg);if(Browser.mainLoop.remainingBlockers){var remaining=Browser.mainLoop.remainingBlockers;var next=remaining%1==0?remaining-1:Math.floor(remaining);if(blocker.counted){Browser.mainLoop.remainingBlockers=next}else{next=next+.5;Browser.mainLoop.remainingBlockers=(8*remaining+next)/9}}console.log('main loop blocker "'+blocker.name+'" took '+(Date.now()-start)+" ms");Browser.mainLoop.updateStatus();if(thisMainLoopId<Browser.mainLoop.currentlyRunningMainloop)return;setTimeout(Browser.mainLoop.runner,0);return}if(thisMainLoopId<Browser.mainLoop.currentlyRunningMainloop)return;Browser.mainLoop.currentFrameNumber=Browser.mainLoop.currentFrameNumber+1|0;if(Browser.mainLoop.timingMode==1&&Browser.mainLoop.timingValue>1&&Browser.mainLoop.currentFrameNumber%Browser.mainLoop.timingValue!=0){Browser.mainLoop.scheduler();return}else if(Browser.mainLoop.timingMode==0){Browser.mainLoop.tickStartTime=_emscripten_get_now()}if(Browser.mainLoop.method==="timeout"&&Module.ctx){err("Looks like you are rendering without using requestAnimationFrame for the main loop. You should use 0 for the frame rate in emscripten_set_main_loop in order to use requestAnimationFrame, as that can greatly improve your frame rates!");Browser.mainLoop.method=""}Browser.mainLoop.runIter(browserIterationFunc);if(thisMainLoopId<Browser.mainLoop.currentlyRunningMainloop)return;if(typeof SDL==="object"&&SDL.audio&&SDL.audio.queueNewAudioData)SDL.audio.queueNewAudioData();Browser.mainLoop.scheduler()};if(!noSetTiming){if(fps&&fps>0)_emscripten_set_main_loop_timing(0,1e3/fps);else _emscripten_set_main_loop_timing(1,1);Browser.mainLoop.scheduler()}if(simulateInfiniteLoop){throw"SimulateInfiniteLoop"}}var Browser={mainLoop:{scheduler:null,method:"",currentlyRunningMainloop:0,func:null,arg:0,timingMode:0,timingValue:0,currentFrameNumber:0,queue:[],pause:function(){Browser.mainLoop.scheduler=null;Browser.mainLoop.currentlyRunningMainloop++},resume:function(){Browser.mainLoop.currentlyRunningMainloop++;var timingMode=Browser.mainLoop.timingMode;var timingValue=Browser.mainLoop.timingValue;var func=Browser.mainLoop.func;Browser.mainLoop.func=null;_emscripten_set_main_loop(func,0,false,Browser.mainLoop.arg,true);_emscripten_set_main_loop_timing(timingMode,timingValue);Browser.mainLoop.scheduler()},updateStatus:function(){if(Module["setStatus"]){var message=Module["statusMessage"]||"Please wait...";var remaining=Browser.mainLoop.remainingBlockers;var expected=Browser.mainLoop.expectedBlockers;if(remaining){if(remaining<expected){Module["setStatus"](message+" ("+(expected-remaining)+"/"+expected+")")}else{Module["setStatus"](message)}}else{Module["setStatus"]("")}}},runIter:function(func){if(ABORT)return;if(Module["preMainLoop"]){var preRet=Module["preMainLoop"]();if(preRet===false){return}}try{func()}catch(e){if(e instanceof ExitStatus){return}else{if(e&&typeof e==="object"&&e.stack)err("exception thrown: "+[e,e.stack]);throw e}}if(Module["postMainLoop"])Module["postMainLoop"]()}},isFullscreen:false,pointerLock:false,moduleContextCreatedCallbacks:[],workers:[],init:function(){if(!Module["preloadPlugins"])Module["preloadPlugins"]=[];if(Browser.initted)return;Browser.initted=true;try{new Blob;Browser.hasBlobConstructor=true}catch(e){Browser.hasBlobConstructor=false;console.log("warning: no blob constructor, cannot create blobs with mimetypes")}Browser.BlobBuilder=typeof MozBlobBuilder!="undefined"?MozBlobBuilder:typeof WebKitBlobBuilder!="undefined"?WebKitBlobBuilder:!Browser.hasBlobConstructor?console.log("warning: no BlobBuilder"):null;Browser.URLObject=typeof window!="undefined"?window.URL?window.URL:window.webkitURL:undefined;if(!Module.noImageDecoding&&typeof Browser.URLObject==="undefined"){console.log("warning: Browser does not support creating object URLs. Built-in browser image decoding will not be available.");Module.noImageDecoding=true}var imagePlugin={};imagePlugin["canHandle"]=function imagePlugin_canHandle(name){return!Module.noImageDecoding&&/\.(jpg|jpeg|png|bmp)$/i.test(name)};imagePlugin["handle"]=function imagePlugin_handle(byteArray,name,onload,onerror){var b=null;if(Browser.hasBlobConstructor){try{b=new Blob([byteArray],{type:Browser.getMimetype(name)});if(b.size!==byteArray.length){b=new Blob([new Uint8Array(byteArray).buffer],{type:Browser.getMimetype(name)})}}catch(e){warnOnce("Blob constructor present but fails: "+e+"; falling back to blob builder")}}if(!b){var bb=new Browser.BlobBuilder;bb.append(new Uint8Array(byteArray).buffer);b=bb.getBlob()}var url=Browser.URLObject.createObjectURL(b);var img=new Image;img.onload=function img_onload(){assert(img.complete,"Image "+name+" could not be decoded");var canvas=document.createElement("canvas");canvas.width=img.width;canvas.height=img.height;var ctx=canvas.getContext("2d");ctx.drawImage(img,0,0);Module["preloadedImages"][name]=canvas;Browser.URLObject.revokeObjectURL(url);if(onload)onload(byteArray)};img.onerror=function img_onerror(event){console.log("Image "+url+" could not be decoded");if(onerror)onerror()};img.src=url};Module["preloadPlugins"].push(imagePlugin);var audioPlugin={};audioPlugin["canHandle"]=function audioPlugin_canHandle(name){return!Module.noAudioDecoding&&name.substr(-4)in{".ogg":1,".wav":1,".mp3":1}};audioPlugin["handle"]=function audioPlugin_handle(byteArray,name,onload,onerror){var done=false;function finish(audio){if(done)return;done=true;Module["preloadedAudios"][name]=audio;if(onload)onload(byteArray)}function fail(){if(done)return;done=true;Module["preloadedAudios"][name]=new Audio;if(onerror)onerror()}if(Browser.hasBlobConstructor){try{var b=new Blob([byteArray],{type:Browser.getMimetype(name)})}catch(e){return fail()}var url=Browser.URLObject.createObjectURL(b);var audio=new Audio;audio.addEventListener("canplaythrough",function(){finish(audio)},false);audio.onerror=function audio_onerror(event){if(done)return;console.log("warning: browser could not fully decode audio "+name+", trying slower base64 approach");function encode64(data){var BASE="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";var PAD="=";var ret="";var leftchar=0;var leftbits=0;for(var i=0;i<data.length;i++){leftchar=leftchar<<8|data[i];leftbits+=8;while(leftbits>=6){var curr=leftchar>>leftbits-6&63;leftbits-=6;ret+=BASE[curr]}}if(leftbits==2){ret+=BASE[(leftchar&3)<<4];ret+=PAD+PAD}else if(leftbits==4){ret+=BASE[(leftchar&15)<<2];ret+=PAD}return ret}audio.src="data:audio/x-"+name.substr(-3)+";base64,"+encode64(byteArray);finish(audio)};audio.src=url;Browser.safeSetTimeout(function(){finish(audio)},1e4)}else{return fail()}};Module["preloadPlugins"].push(audioPlugin);function pointerLockChange(){Browser.pointerLock=document["pointerLockElement"]===Module["canvas"]||document["mozPointerLockElement"]===Module["canvas"]||document["webkitPointerLockElement"]===Module["canvas"]||document["msPointerLockElement"]===Module["canvas"]}var canvas=Module["canvas"];if(canvas){canvas.requestPointerLock=canvas["requestPointerLock"]||canvas["mozRequestPointerLock"]||canvas["webkitRequestPointerLock"]||canvas["msRequestPointerLock"]||function(){};canvas.exitPointerLock=document["exitPointerLock"]||document["mozExitPointerLock"]||document["webkitExitPointerLock"]||document["msExitPointerLock"]||function(){};canvas.exitPointerLock=canvas.exitPointerLock.bind(document);document.addEventListener("pointerlockchange",pointerLockChange,false);document.addEventListener("mozpointerlockchange",pointerLockChange,false);document.addEventListener("webkitpointerlockchange",pointerLockChange,false);document.addEventListener("mspointerlockchange",pointerLockChange,false);if(Module["elementPointerLock"]){canvas.addEventListener("click",function(ev){if(!Browser.pointerLock&&Module["canvas"].requestPointerLock){Module["canvas"].requestPointerLock();ev.preventDefault()}},false)}}},createContext:function(canvas,useWebGL,setInModule,webGLContextAttributes){if(useWebGL&&Module.ctx&&canvas==Module.canvas)return Module.ctx;var ctx;var contextHandle;if(useWebGL){var contextAttributes={antialias:false,alpha:false,majorVersion:1};if(webGLContextAttributes){for(var attribute in webGLContextAttributes){contextAttributes[attribute]=webGLContextAttributes[attribute]}}if(typeof GL!=="undefined"){contextHandle=GL.createContext(canvas,contextAttributes);if(contextHandle){ctx=GL.getContext(contextHandle).GLctx}}}else{ctx=canvas.getContext("2d")}if(!ctx)return null;if(setInModule){if(!useWebGL)assert(typeof GLctx==="undefined","cannot set in module if GLctx is used, but we are a non-GL context that would replace it");Module.ctx=ctx;if(useWebGL)GL.makeContextCurrent(contextHandle);Module.useWebGL=useWebGL;Browser.moduleContextCreatedCallbacks.forEach(function(callback){callback()});Browser.init()}return ctx},destroyContext:function(canvas,useWebGL,setInModule){},fullscreenHandlersInstalled:false,lockPointer:undefined,resizeCanvas:undefined,requestFullscreen:function(lockPointer,resizeCanvas,vrDevice){Browser.lockPointer=lockPointer;Browser.resizeCanvas=resizeCanvas;Browser.vrDevice=vrDevice;if(typeof Browser.lockPointer==="undefined")Browser.lockPointer=true;if(typeof Browser.resizeCanvas==="undefined")Browser.resizeCanvas=false;if(typeof Browser.vrDevice==="undefined")Browser.vrDevice=null;var canvas=Module["canvas"];function fullscreenChange(){Browser.isFullscreen=false;var canvasContainer=canvas.parentNode;if((document["fullscreenElement"]||document["mozFullScreenElement"]||document["msFullscreenElement"]||document["webkitFullscreenElement"]||document["webkitCurrentFullScreenElement"])===canvasContainer){canvas.exitFullscreen=Browser.exitFullscreen;if(Browser.lockPointer)canvas.requestPointerLock();Browser.isFullscreen=true;if(Browser.resizeCanvas){Browser.setFullscreenCanvasSize()}else{Browser.updateCanvasDimensions(canvas)}}else{canvasContainer.parentNode.insertBefore(canvas,canvasContainer);canvasContainer.parentNode.removeChild(canvasContainer);if(Browser.resizeCanvas){Browser.setWindowedCanvasSize()}else{Browser.updateCanvasDimensions(canvas)}}if(Module["onFullScreen"])Module["onFullScreen"](Browser.isFullscreen);if(Module["onFullscreen"])Module["onFullscreen"](Browser.isFullscreen)}if(!Browser.fullscreenHandlersInstalled){Browser.fullscreenHandlersInstalled=true;document.addEventListener("fullscreenchange",fullscreenChange,false);document.addEventListener("mozfullscreenchange",fullscreenChange,false);document.addEventListener("webkitfullscreenchange",fullscreenChange,false);document.addEventListener("MSFullscreenChange",fullscreenChange,false)}var canvasContainer=document.createElement("div");canvas.parentNode.insertBefore(canvasContainer,canvas);canvasContainer.appendChild(canvas);canvasContainer.requestFullscreen=canvasContainer["requestFullscreen"]||canvasContainer["mozRequestFullScreen"]||canvasContainer["msRequestFullscreen"]||(canvasContainer["webkitRequestFullscreen"]?function(){canvasContainer["webkitRequestFullscreen"](Element["ALLOW_KEYBOARD_INPUT"])}:null)||(canvasContainer["webkitRequestFullScreen"]?function(){canvasContainer["webkitRequestFullScreen"](Element["ALLOW_KEYBOARD_INPUT"])}:null);if(vrDevice){canvasContainer.requestFullscreen({vrDisplay:vrDevice})}else{canvasContainer.requestFullscreen()}},requestFullScreen:function(lockPointer,resizeCanvas,vrDevice){err("Browser.requestFullScreen() is deprecated. Please call Browser.requestFullscreen instead.");Browser.requestFullScreen=function(lockPointer,resizeCanvas,vrDevice){return Browser.requestFullscreen(lockPointer,resizeCanvas,vrDevice)};return Browser.requestFullscreen(lockPointer,resizeCanvas,vrDevice)},exitFullscreen:function(){if(!Browser.isFullscreen){return false}var CFS=document["exitFullscreen"]||document["cancelFullScreen"]||document["mozCancelFullScreen"]||document["msExitFullscreen"]||document["webkitCancelFullScreen"]||function(){};CFS.apply(document,[]);return true},nextRAF:0,fakeRequestAnimationFrame:function(func){var now=Date.now();if(Browser.nextRAF===0){Browser.nextRAF=now+1e3/60}else{while(now+2>=Browser.nextRAF){Browser.nextRAF+=1e3/60}}var delay=Math.max(Browser.nextRAF-now,0);setTimeout(func,delay)},requestAnimationFrame:function(func){if(typeof requestAnimationFrame==="function"){requestAnimationFrame(func);return}var RAF=Browser.fakeRequestAnimationFrame;RAF(func)},safeCallback:function(func){return function(){if(!ABORT)return func.apply(null,arguments)}},allowAsyncCallbacks:true,queuedAsyncCallbacks:[],pauseAsyncCallbacks:function(){Browser.allowAsyncCallbacks=false},resumeAsyncCallbacks:function(){Browser.allowAsyncCallbacks=true;if(Browser.queuedAsyncCallbacks.length>0){var callbacks=Browser.queuedAsyncCallbacks;Browser.queuedAsyncCallbacks=[];callbacks.forEach(function(func){func()})}},safeRequestAnimationFrame:function(func){return Browser.requestAnimationFrame(function(){if(ABORT)return;if(Browser.allowAsyncCallbacks){func()}else{Browser.queuedAsyncCallbacks.push(func)}})},safeSetTimeout:function(func,timeout){noExitRuntime=true;return setTimeout(function(){if(ABORT)return;if(Browser.allowAsyncCallbacks){func()}else{Browser.queuedAsyncCallbacks.push(func)}},timeout)},safeSetInterval:function(func,timeout){noExitRuntime=true;return setInterval(function(){if(ABORT)return;if(Browser.allowAsyncCallbacks){func()}},timeout)},getMimetype:function(name){return{"jpg":"image/jpeg","jpeg":"image/jpeg","png":"image/png","bmp":"image/bmp","ogg":"audio/ogg","wav":"audio/wav","mp3":"audio/mpeg"}[name.substr(name.lastIndexOf(".")+1)]},getUserMedia:function(func){if(!window.getUserMedia){window.getUserMedia=navigator["getUserMedia"]||navigator["mozGetUserMedia"]}window.getUserMedia(func)},getMovementX:function(event){return event["movementX"]||event["mozMovementX"]||event["webkitMovementX"]||0},getMovementY:function(event){return event["movementY"]||event["mozMovementY"]||event["webkitMovementY"]||0},getMouseWheelDelta:function(event){var delta=0;switch(event.type){case"DOMMouseScroll":delta=event.detail/3;break;case"mousewheel":delta=event.wheelDelta/120;break;case"wheel":delta=event.deltaY;switch(event.deltaMode){case 0:delta/=100;break;case 1:delta/=3;break;case 2:delta*=80;break;default:throw"unrecognized mouse wheel delta mode: "+event.deltaMode}break;default:throw"unrecognized mouse wheel event: "+event.type}return delta},mouseX:0,mouseY:0,mouseMovementX:0,mouseMovementY:0,touches:{},lastTouches:{},calculateMouseEvent:function(event){if(Browser.pointerLock){if(event.type!="mousemove"&&"mozMovementX"in event){Browser.mouseMovementX=Browser.mouseMovementY=0}else{Browser.mouseMovementX=Browser.getMovementX(event);Browser.mouseMovementY=Browser.getMovementY(event)}if(typeof SDL!="undefined"){Browser.mouseX=SDL.mouseX+Browser.mouseMovementX;Browser.mouseY=SDL.mouseY+Browser.mouseMovementY}else{Browser.mouseX+=Browser.mouseMovementX;Browser.mouseY+=Browser.mouseMovementY}}else{var rect=Module["canvas"].getBoundingClientRect();var cw=Module["canvas"].width;var ch=Module["canvas"].height;var scrollX=typeof window.scrollX!=="undefined"?window.scrollX:window.pageXOffset;var scrollY=typeof window.scrollY!=="undefined"?window.scrollY:window.pageYOffset;if(event.type==="touchstart"||event.type==="touchend"||event.type==="touchmove"){var touch=event.touch;if(touch===undefined){return}var adjustedX=touch.pageX-(scrollX+rect.left);var adjustedY=touch.pageY-(scrollY+rect.top);adjustedX=adjustedX*(cw/rect.width);adjustedY=adjustedY*(ch/rect.height);var coords={x:adjustedX,y:adjustedY};if(event.type==="touchstart"){Browser.lastTouches[touch.identifier]=coords;Browser.touches[touch.identifier]=coords}else if(event.type==="touchend"||event.type==="touchmove"){var last=Browser.touches[touch.identifier];if(!last)last=coords;Browser.lastTouches[touch.identifier]=last;Browser.touches[touch.identifier]=coords}return}var x=event.pageX-(scrollX+rect.left);var y=event.pageY-(scrollY+rect.top);x=x*(cw/rect.width);y=y*(ch/rect.height);Browser.mouseMovementX=x-Browser.mouseX;Browser.mouseMovementY=y-Browser.mouseY;Browser.mouseX=x;Browser.mouseY=y}},asyncLoad:function(url,onload,onerror,noRunDep){var dep=!noRunDep?getUniqueRunDependency("al "+url):"";readAsync(url,function(arrayBuffer){assert(arrayBuffer,'Loading data file "'+url+'" failed (no arrayBuffer).');onload(new Uint8Array(arrayBuffer));if(dep)removeRunDependency(dep)},function(event){if(onerror){onerror()}else{throw'Loading data file "'+url+'" failed.'}});if(dep)addRunDependency(dep)},resizeListeners:[],updateResizeListeners:function(){var canvas=Module["canvas"];Browser.resizeListeners.forEach(function(listener){listener(canvas.width,canvas.height)})},setCanvasSize:function(width,height,noUpdates){var canvas=Module["canvas"];Browser.updateCanvasDimensions(canvas,width,height);if(!noUpdates)Browser.updateResizeListeners()},windowedWidth:0,windowedHeight:0,setFullscreenCanvasSize:function(){if(typeof SDL!="undefined"){var flags=HEAPU32[SDL.screen>>2];flags=flags|8388608;HEAP32[SDL.screen>>2]=flags}Browser.updateCanvasDimensions(Module["canvas"]);Browser.updateResizeListeners()},setWindowedCanvasSize:function(){if(typeof SDL!="undefined"){var flags=HEAPU32[SDL.screen>>2];flags=flags&~8388608;HEAP32[SDL.screen>>2]=flags}Browser.updateCanvasDimensions(Module["canvas"]);Browser.updateResizeListeners()},updateCanvasDimensions:function(canvas,wNative,hNative){if(wNative&&hNative){canvas.widthNative=wNative;canvas.heightNative=hNative}else{wNative=canvas.widthNative;hNative=canvas.heightNative}var w=wNative;var h=hNative;if(Module["forcedAspectRatio"]&&Module["forcedAspectRatio"]>0){if(w/h<Module["forcedAspectRatio"]){w=Math.round(h*Module["forcedAspectRatio"])}else{h=Math.round(w/Module["forcedAspectRatio"])}}if((document["fullscreenElement"]||document["mozFullScreenElement"]||document["msFullscreenElement"]||document["webkitFullscreenElement"]||document["webkitCurrentFullScreenElement"])===canvas.parentNode&&typeof screen!="undefined"){var factor=Math.min(screen.width/w,screen.height/h);w=Math.round(w*factor);h=Math.round(h*factor)}if(Browser.resizeCanvas){if(canvas.width!=w)canvas.width=w;if(canvas.height!=h)canvas.height=h;if(typeof canvas.style!="undefined"){canvas.style.removeProperty("width");canvas.style.removeProperty("height")}}else{if(canvas.width!=wNative)canvas.width=wNative;if(canvas.height!=hNative)canvas.height=hNative;if(typeof canvas.style!="undefined"){if(w!=wNative||h!=hNative){canvas.style.setProperty("width",w+"px","important");canvas.style.setProperty("height",h+"px","important")}else{canvas.style.removeProperty("width");canvas.style.removeProperty("height")}}}},wgetRequests:{},nextWgetRequestHandle:0,getNextWgetRequestHandle:function(){var handle=Browser.nextWgetRequestHandle;Browser.nextWgetRequestHandle++;return handle}};function _SDL_GetTicks(){return Date.now()-SDL.startTime|0}var SDL={defaults:{width:320,height:200},version:null,surfaces:{},canvasPool:[],events:[],fonts:[null],audios:[null],rwops:[null],music:{audio:null,volume:1},mixerFrequency:22050,mixerFormat:32784,mixerNumChannels:2,mixerChunkSize:1024,channelMinimumNumber:0,GL:false,glAttributes:{0:3,1:3,2:2,3:0,4:0,5:1,6:16,7:0,8:0,9:0,10:0,11:0,12:0,13:0,14:0,15:1,16:0,17:0,18:0},keyboardState:null,keyboardMap:{},keyDown:{},canRequestFullscreen:false,isRequestingFullscreen:false,textInput:false,startTime:null,initFlags:0,buttonState:0,modState:0,DOMButtons:[0,0,0],DOMEventToSDLEvent:{},TOUCH_DEFAULT_ID:0,eventHandler:null,eventHandlerContext:null,keyCodes:{16:1249,17:1248,18:1250,20:1081,33:1099,34:1102,35:1101,36:1098,37:1104,38:1106,39:1103,40:1105,44:316,45:1097,46:127,91:1251,93:1255,96:1122,97:1113,98:1114,99:1115,100:1116,101:1117,102:1118,103:1119,104:1120,105:1121,106:1109,107:1111,109:1110,110:1123,111:1108,112:1082,113:1083,114:1084,115:1085,116:1086,117:1087,118:1088,119:1089,120:1090,121:1091,122:1092,123:1093,124:1128,125:1129,126:1130,127:1131,128:1132,129:1133,130:1134,131:1135,132:1136,133:1137,134:1138,135:1139,144:1107,160:94,161:33,162:34,163:35,164:36,165:37,166:38,167:95,168:40,169:41,170:42,171:43,172:124,173:45,174:123,175:125,176:126,181:127,182:129,183:128,186:59,187:61,188:44,189:45,190:46,191:47,192:96,219:91,220:92,221:93,222:39,224:1251},scanCodes:{8:42,9:43,13:40,27:41,32:44,35:204,39:53,44:54,46:55,47:56,48:39,49:30,50:31,51:32,52:33,53:34,54:35,55:36,56:37,57:38,58:203,59:51,61:46,91:47,92:49,93:48,96:52,97:4,98:5,99:6,100:7,101:8,102:9,103:10,104:11,105:12,106:13,107:14,108:15,109:16,110:17,111:18,112:19,113:20,114:21,115:22,116:23,117:24,118:25,119:26,120:27,121:28,122:29,127:76,305:224,308:226,316:70},loadRect:function(rect){return{x:HEAP32[rect+0>>2],y:HEAP32[rect+4>>2],w:HEAP32[rect+8>>2],h:HEAP32[rect+12>>2]}},checkPixelFormat:function(fmt){},loadColorToCSSRGB:function(color){var rgba=HEAP32[color>>2];return"rgb("+(rgba&255)+","+(rgba>>8&255)+","+(rgba>>16&255)+")"},loadColorToCSSRGBA:function(color){var rgba=HEAP32[color>>2];return"rgba("+(rgba&255)+","+(rgba>>8&255)+","+(rgba>>16&255)+","+(rgba>>24&255)/255+")"},translateColorToCSSRGBA:function(rgba){return"rgba("+(rgba&255)+","+(rgba>>8&255)+","+(rgba>>16&255)+","+(rgba>>>24)/255+")"},translateRGBAToCSSRGBA:function(r,g,b,a){return"rgba("+(r&255)+","+(g&255)+","+(b&255)+","+(a&255)/255+")"},translateRGBAToColor:function(r,g,b,a){return r|g<<8|b<<16|a<<24},makeSurface:function(width,height,flags,usePageCanvas,source,rmask,gmask,bmask,amask){flags=flags||0;var is_SDL_HWSURFACE=flags&1;var is_SDL_HWPALETTE=flags&2097152;var is_SDL_OPENGL=flags&67108864;var surf=_malloc(60);var pixelFormat=_malloc(44);var bpp=is_SDL_HWPALETTE?1:4;var buffer=0;HEAP32[surf>>2]=flags;HEAP32[surf+4>>2]=pixelFormat;HEAP32[surf+8>>2]=width;HEAP32[surf+12>>2]=height;HEAP32[surf+16>>2]=width*bpp;HEAP32[surf+20>>2]=buffer;HEAP32[surf+36>>2]=0;HEAP32[surf+40>>2]=0;HEAP32[surf+44>>2]=Module["canvas"].width;HEAP32[surf+48>>2]=Module["canvas"].height;HEAP32[surf+56>>2]=1;HEAP32[pixelFormat>>2]=-2042224636;HEAP32[pixelFormat+4>>2]=0;HEAP8[pixelFormat+8>>0]=bpp*8;HEAP8[pixelFormat+9>>0]=bpp;HEAP32[pixelFormat+12>>2]=rmask||255;HEAP32[pixelFormat+16>>2]=gmask||65280;HEAP32[pixelFormat+20>>2]=bmask||16711680;HEAP32[pixelFormat+24>>2]=amask||4278190080;SDL.GL=SDL.GL||is_SDL_OPENGL;var canvas;if(!usePageCanvas){if(SDL.canvasPool.length>0){canvas=SDL.canvasPool.pop()}else{canvas=document.createElement("canvas")}canvas.width=width;canvas.height=height}else{canvas=Module["canvas"]}var webGLContextAttributes={antialias:SDL.glAttributes[13]!=0&&SDL.glAttributes[14]>1,depth:is_in_editor||SDL.glAttributes[6]>0,stencil:is_in_editor||SDL.glAttributes[7]>0,preserveDrawingBuffer:false,alpha:false};var ctx=Browser.createContext(canvas,is_SDL_OPENGL,usePageCanvas,webGLContextAttributes);SDL.surfaces[surf]={width:width,height:height,canvas:canvas,ctx:ctx,surf:surf,buffer:buffer,pixelFormat:pixelFormat,alpha:255,flags:flags,locked:0,usePageCanvas:usePageCanvas,source:source,isFlagSet:function(flag){return flags&flag}};return surf},copyIndexedColorData:function(surfData,rX,rY,rW,rH){if(!surfData.colors){return}var fullWidth=Module["canvas"].width;var fullHeight=Module["canvas"].height;var startX=rX||0;var startY=rY||0;var endX=(rW||fullWidth-startX)+startX;var endY=(rH||fullHeight-startY)+startY;var buffer=surfData.buffer;if(!surfData.image.data32){surfData.image.data32=new Uint32Array(surfData.image.data.buffer)}var data32=surfData.image.data32;var colors32=surfData.colors32;for(var y=startY;y<endY;++y){var base=y*fullWidth;for(var x=startX;x<endX;++x){data32[base+x]=colors32[HEAPU8[buffer+base+x>>0]]}}},freeSurface:function(surf){var refcountPointer=surf+56;var refcount=HEAP32[refcountPointer>>2];if(refcount>1){HEAP32[refcountPointer>>2]=refcount-1;return}var info=SDL.surfaces[surf];if(!info.usePageCanvas&&info.canvas)SDL.canvasPool.push(info.canvas);if(info.buffer)_free(info.buffer);_free(info.pixelFormat);_free(surf);SDL.surfaces[surf]=null;if(surf===SDL.screen){SDL.screen=null}},downFingers:{},savedKeydown:null,receiveEvent:function(event){function unpressAllPressedKeys(){for(var code in SDL.keyboardMap){SDL.events.push({type:"keyup",keyCode:SDL.keyboardMap[code]})}}switch(event.type){case"touchstart":case"touchmove":{event.preventDefault();var touches=[];if(event.type==="touchstart"){for(var i=0;i<event.touches.length;i++){var touch=event.touches[i];if(SDL.downFingers[touch.identifier]!=true){SDL.downFingers[touch.identifier]=true;touches.push(touch)}}}else{touches=event.touches}var firstTouch=touches[0];if(event.type=="touchstart"){SDL.DOMButtons[0]=1}var mouseEventType;switch(event.type){case"touchstart":mouseEventType="mousedown";break;case"touchmove":mouseEventType="mousemove";break}var mouseEvent={type:mouseEventType,button:0,pageX:firstTouch.clientX,pageY:firstTouch.clientY};SDL.events.push(mouseEvent);for(var i=0;i<touches.length;i++){var touch=touches[i];SDL.events.push({type:event.type,touch:touch})}break}case"touchend":{event.preventDefault();for(var i=0;i<event.changedTouches.length;i++){var touch=event.changedTouches[i];if(SDL.downFingers[touch.identifier]===true){delete SDL.downFingers[touch.identifier]}}var mouseEvent={type:"mouseup",button:0,pageX:event.changedTouches[0].clientX,pageY:event.changedTouches[0].clientY};SDL.DOMButtons[0]=0;SDL.events.push(mouseEvent);for(var i=0;i<event.changedTouches.length;i++){var touch=event.changedTouches[i];SDL.events.push({type:"touchend",touch:touch})}break}case"DOMMouseScroll":case"mousewheel":case"wheel":var delta=-Browser.getMouseWheelDelta(event);delta=delta==0?0:delta>0?Math.max(delta,1):Math.min(delta,-1);var button=delta>0?3:4;SDL.events.push({type:"mousedown",button:button,pageX:event.pageX,pageY:event.pageY});SDL.events.push({type:"mouseup",button:button,pageX:event.pageX,pageY:event.pageY});SDL.events.push({type:"wheel",deltaX:0,deltaY:delta});event.preventDefault();break;case"mousemove":if(SDL.DOMButtons[0]===1){SDL.events.push({type:"touchmove",touch:{identifier:0,deviceID:-1,pageX:event.pageX,pageY:event.pageY}})}if(Browser.pointerLock){if("mozMovementX"in event){event["movementX"]=event["mozMovementX"];event["movementY"]=event["mozMovementY"]}if(event["movementX"]==0&&event["movementY"]==0){event.preventDefault();return}}case"keydown":case"keyup":case"keypress":case"mousedown":case"mouseup":if(event.type!=="mousedown"&&event.type!=="mouseup"&&(event.type!=="keydown"||!SDL.unicode&&!SDL.textInput||(event.keyCode===8||event.keyCode===9))){event.preventDefault()}if(event.type=="mousedown"){SDL.DOMButtons[event.button]=1;SDL.events.push({type:"touchstart",touch:{identifier:0,deviceID:-1,pageX:event.pageX,pageY:event.pageY}})}else if(event.type=="mouseup"){if(!SDL.DOMButtons[event.button]){return}SDL.events.push({type:"touchend",touch:{identifier:0,deviceID:-1,pageX:event.pageX,pageY:event.pageY}});SDL.DOMButtons[event.button]=0}if(event.type==="keydown"||event.type==="mousedown"){SDL.canRequestFullscreen=true}else if(event.type==="keyup"||event.type==="mouseup"){if(SDL.isRequestingFullscreen){var canvas=Module.canvas;canvas.requestFullScreen=canvas["requestFullScreen"]||canvas["mozRequestFullScreen"]||canvas["msRequestFullscreen"]||(canvas["webkitRequestFullScreen"]?function(){canvas["webkitRequestFullScreen"](Element["ALLOW_KEYBOARD_INPUT"])}:null);canvas.requestFullScreen();SDL.isRequestingFullscreen=false}SDL.canRequestFullscreen=false}if(event.type==="keypress"&&SDL.savedKeydown){SDL.savedKeydown.keypressCharCode=event.charCode;SDL.savedKeydown=null}else if(event.type==="keydown"){SDL.savedKeydown=event}if(event.type!=="keypress"||SDL.textInput){SDL.events.push(event)}break;case"mouseout":for(var i=0;i<3;i++){if(SDL.DOMButtons[i]){SDL.events.push({type:"mouseup",button:i,pageX:event.pageX,pageY:event.pageY});SDL.DOMButtons[i]=0}}event.preventDefault();break;case"focus":SDL.events.push(event);event.preventDefault();break;case"blur":SDL.events.push(event);unpressAllPressedKeys();event.preventDefault();break;case"visibilitychange":SDL.events.push({type:"visibilitychange",visible:!document.hidden});unpressAllPressedKeys();event.preventDefault();break;case"unload":if(Browser.mainLoop.runner){SDL.events.push(event);Browser.mainLoop.runner()}return;case"resize":SDL.events.push(event);if(event.preventDefault){event.preventDefault()}break}if(SDL.events.length>=1e4){Module.printErr("SDL event queue full, dropping events");SDL.events=SDL.events.slice(0,1e4)}SDL.flushEventsToHandler();return},lookupKeyCodeForEvent:function(event){var code=event.keyCode;if(code>=65&&code<=90){code+=32}else{code=SDL.keyCodes[event.keyCode]||event.keyCode;if(event.location===KeyboardEvent.DOM_KEY_LOCATION_RIGHT&&code>=(224|1<<10)&&code<=(227|1<<10)){code+=4}}return code},handleEvent:function(event){if(event.handled)return;event.handled=true;switch(event.type){case"touchstart":case"touchend":case"touchmove":{Browser.calculateMouseEvent(event);break}case"keydown":case"keyup":{var down=event.type==="keydown";var code=SDL.lookupKeyCodeForEvent(event);HEAP8[SDL.keyboardState+code>>0]=down;SDL.modState=(HEAP8[SDL.keyboardState+1248>>0]?64:0)|(HEAP8[SDL.keyboardState+1249>>0]?1:0)|(HEAP8[SDL.keyboardState+1250>>0]?256:0)|(HEAP8[SDL.keyboardState+1252>>0]?128:0)|(HEAP8[SDL.keyboardState+1253>>0]?2:0)|(HEAP8[SDL.keyboardState+1254>>0]?512:0);if(down){SDL.keyboardMap[code]=event.keyCode}else{delete SDL.keyboardMap[code]}break}case"mousedown":case"mouseup":if(event.type=="mousedown"){SDL.buttonState|=1<<event.button}else if(event.type=="mouseup"){SDL.buttonState&=~(1<<event.button)}case"mousemove":{Browser.calculateMouseEvent(event);break}}},flushEventsToHandler:function(){if(!SDL.eventHandler)return;var sdlEventPtr=allocate(28,"i8",ALLOC_STACK);while(SDL.pollEvent(sdlEventPtr)){dynCall("iii",SDL.eventHandler,[SDL.eventHandlerContext,sdlEventPtr])}},pollEvent:function(ptr){if(SDL.initFlags&512&&SDL.joystickEventState){SDL.queryJoysticks()}if(ptr){while(SDL.events.length>0){if(SDL.makeCEvent(SDL.events.shift(),ptr)!==false)return 1}return 0}else{return SDL.events.length>0}},makeCEvent:function(event,ptr){if(typeof event==="number"){_memcpy(ptr,event,28);_free(event);return}SDL.handleEvent(event);switch(event.type){case"keydown":case"keyup":{var down=event.type==="keydown";var key=SDL.lookupKeyCodeForEvent(event);var scan;if(key>=1024){scan=key-1024}else{scan=SDL.scanCodes[key]||key}var repeat=down&&SDL.keyDown[key];SDL.keyDown[key]=down;HEAP32[ptr>>2]=SDL.DOMEventToSDLEvent[event.type];HEAP8[ptr+8>>0]=down?1:0;HEAP8[ptr+9>>0]=repeat?1:0;HEAP32[ptr+12>>2]=scan;HEAP32[ptr+16>>2]=key;HEAP16[ptr+20>>1]=SDL.modState;HEAP32[ptr+24>>2]=event.keypressCharCode||key;break}case"keypress":{HEAP32[ptr>>2]=SDL.DOMEventToSDLEvent[event.type];var cStr=intArrayFromString(String.fromCharCode(event.charCode));for(var i=0;i<cStr.length;++i){HEAP8[ptr+(8+i)>>0]=cStr[i]}break}case"mousedown":case"mouseup":case"mousemove":{if(event.type!="mousemove"){var down=event.type==="mousedown";HEAP32[ptr>>2]=SDL.DOMEventToSDLEvent[event.type];HEAP32[ptr+4>>2]=0;HEAP32[ptr+8>>2]=0;HEAP32[ptr+12>>2]=0;HEAP8[ptr+16>>0]=event.button+1;HEAP8[ptr+17>>0]=down?1:0;HEAP32[ptr+20>>2]=Browser.mouseX;HEAP32[ptr+24>>2]=Browser.mouseY}else{HEAP32[ptr>>2]=SDL.DOMEventToSDLEvent[event.type];HEAP32[ptr+4>>2]=0;HEAP32[ptr+8>>2]=0;HEAP32[ptr+12>>2]=0;HEAP32[ptr+16>>2]=SDL.buttonState;HEAP32[ptr+20>>2]=Browser.mouseX;HEAP32[ptr+24>>2]=Browser.mouseY;HEAP32[ptr+28>>2]=Browser.mouseMovementX;HEAP32[ptr+32>>2]=Browser.mouseMovementY}break}case"wheel":{HEAP32[ptr>>2]=SDL.DOMEventToSDLEvent[event.type];HEAP32[ptr+16>>2]=event.deltaX;HEAP32[ptr+20>>2]=event.deltaY;break}case"touchstart":case"touchend":case"touchmove":{var touch=event.touch;if(!Browser.touches[touch.identifier])break;var w=Module["canvas"].width;var h=Module["canvas"].height;var x=Browser.touches[touch.identifier].x/w;var y=Browser.touches[touch.identifier].y/h;var lx=Browser.lastTouches[touch.identifier].x/w;var ly=Browser.lastTouches[touch.identifier].y/h;var dx=x-lx;var dy=y-ly;if(touch["deviceID"]===undefined)touch.deviceID=SDL.TOUCH_DEFAULT_ID;if(dx===0&&dy===0&&event.type==="touchmove")return false;HEAP32[ptr>>2]=SDL.DOMEventToSDLEvent[event.type];HEAP32[ptr+4>>2]=_SDL_GetTicks();tempI64=[touch.deviceID>>>0,(tempDouble=touch.deviceID,+Math_abs(tempDouble)>=1?tempDouble>0?(Math_min(+Math_floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math_ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[ptr+8>>2]=tempI64[0],HEAP32[ptr+12>>2]=tempI64[1];tempI64=[touch.identifier>>>0,(tempDouble=touch.identifier,+Math_abs(tempDouble)>=1?tempDouble>0?(Math_min(+Math_floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math_ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[ptr+16>>2]=tempI64[0],HEAP32[ptr+20>>2]=tempI64[1];HEAPF32[ptr+24>>2]=x;HEAPF32[ptr+28>>2]=y;HEAPF32[ptr+32>>2]=dx;HEAPF32[ptr+36>>2]=dy;if(touch.force!==undefined){HEAPF32[ptr+40>>2]=touch.force}else{HEAPF32[ptr+40>>2]=event.type=="touchend"?0:1}break}case"unload":{HEAP32[ptr>>2]=SDL.DOMEventToSDLEvent[event.type];break}case"resize":{HEAP32[ptr>>2]=SDL.DOMEventToSDLEvent[event.type];HEAP32[ptr+4>>2]=event.w;HEAP32[ptr+8>>2]=event.h;break}case"joystick_button_up":case"joystick_button_down":{var state=event.type==="joystick_button_up"?0:1;HEAP32[ptr>>2]=SDL.DOMEventToSDLEvent[event.type];HEAP8[ptr+4>>0]=event.index;HEAP8[ptr+5>>0]=event.button;HEAP8[ptr+6>>0]=state;break}case"joystick_axis_motion":{HEAP32[ptr>>2]=SDL.DOMEventToSDLEvent[event.type];HEAP8[ptr+4>>0]=event.index;HEAP8[ptr+5>>0]=event.axis;HEAP32[ptr+8>>2]=SDL.joystickAxisValueConversion(event.value);break}case"focus":{var SDL_WINDOWEVENT_FOCUS_GAINED=12;HEAP32[ptr>>2]=SDL.DOMEventToSDLEvent[event.type];HEAP32[ptr+4>>2]=0;HEAP8[ptr+8>>0]=SDL_WINDOWEVENT_FOCUS_GAINED;break}case"blur":{var SDL_WINDOWEVENT_FOCUS_LOST=13;HEAP32[ptr>>2]=SDL.DOMEventToSDLEvent[event.type];HEAP32[ptr+4>>2]=0;HEAP8[ptr+8>>0]=SDL_WINDOWEVENT_FOCUS_LOST;break}case"visibilitychange":{var SDL_WINDOWEVENT_SHOWN=1;var SDL_WINDOWEVENT_HIDDEN=2;var visibilityEventID=event.visible?SDL_WINDOWEVENT_SHOWN:SDL_WINDOWEVENT_HIDDEN;HEAP32[ptr>>2]=SDL.DOMEventToSDLEvent[event.type];HEAP32[ptr+4>>2]=0;HEAP8[ptr+8>>0]=visibilityEventID;break}default:throw"Unhandled SDL event: "+event.type}},estimateTextWidth:function(fontData,text){var h=fontData.size;var fontString=h+"px "+fontData.name;var tempCtx=SDL.ttfContext;tempCtx.save();tempCtx.font=fontString;var ret=tempCtx.measureText(text).width|0;tempCtx.restore();return ret},allocateChannels:function(num){if(SDL.numChannels&&SDL.numChannels>=num&&num!=0)return;SDL.numChannels=num;SDL.channels=[];for(var i=0;i<num;i++){SDL.channels[i]={audio:null,volume:1}}},setGetVolume:function(info,volume){if(!info)return 0;var ret=info.volume*128;if(volume!=-1){info.volume=Math.min(Math.max(volume,0),128)/128;if(info.audio){try{info.audio.volume=info.volume;if(info.audio.webAudioGainNode)info.audio.webAudioGainNode["gain"]["value"]=info.volume}catch(e){Module.printErr("setGetVolume failed to set audio volume: "+e)}}}return ret},playWebAudio:function(audio){if(!audio)return;if(audio.webAudioNode)return;if(!SDL.webAudioAvailable())return;try{var webAudio=audio.resource.webAudio;audio.paused=false;if(!webAudio.decodedBuffer){if(webAudio.onDecodeComplete===undefined)abort("Cannot play back audio object that was not loaded");webAudio.onDecodeComplete.push(function(){if(!audio.paused)SDL.playWebAudio(audio)});return}audio.webAudioNode=SDL.audioContext["createBufferSource"]();audio.webAudioNode["buffer"]=webAudio.decodedBuffer;audio.webAudioNode["loop"]=audio.loop;audio.webAudioNode["onended"]=function(){audio.onended()};audio.webAudioGainNode=SDL.audioContext["createGain"]();audio.webAudioGainNode["gain"]["value"]=audio.volume;audio.webAudioNode["connect"](audio.webAudioGainNode);audio.webAudioGainNode["connect"](SDL.audioContext["destination"]);audio.webAudioNode["start"](0,audio.currentPosition);audio.startTime=SDL.audioContext["currentTime"]-audio.currentPosition}catch(e){Module.printErr("playWebAudio failed: "+e)}},pauseWebAudio:function(audio){if(!audio)return;if(audio.webAudioNode){try{audio.currentPosition=(SDL.audioContext["currentTime"]-audio.startTime)%audio.resource.webAudio.decodedBuffer.duration;audio.webAudioNode["onended"]=undefined;audio.webAudioNode.stop();audio.webAudioNode=undefined}catch(e){Module.printErr("pauseWebAudio failed: "+e)}}audio.paused=true},openAudioContext:function(){if(!SDL.audioContext){if(typeof AudioContext!=="undefined")SDL.audioContext=new AudioContext;else if(typeof webkitAudioContext!=="undefined")SDL.audioContext=new webkitAudioContext}},webAudioAvailable:function(){return!!SDL.audioContext},fillWebAudioBufferFromHeap:function(heapPtr,sizeSamplesPerChannel,dstAudioBuffer){var numChannels=SDL.audio.channels;for(var c=0;c<numChannels;++c){var channelData=dstAudioBuffer["getChannelData"](c);if(channelData.length!=sizeSamplesPerChannel){throw"Web Audio output buffer length mismatch! Destination size: "+channelData.length+" samples vs expected "+sizeSamplesPerChannel+" samples!"}var i=c*sizeSamplesPerChannel*4;for(var j=0;j<sizeSamplesPerChannel;++j){channelData[j]=HEAPF32[heapPtr+i>>2];i+=4}}},debugSurface:function(surfData){console.log("dumping surface "+[surfData.surf,surfData.source,surfData.width,surfData.height]);var image=surfData.ctx.getImageData(0,0,surfData.width,surfData.height);var data=image.data;var num=Math.min(surfData.width,surfData.height);for(var i=0;i<num;i++){console.log(" diagonal "+i+":"+[data[i*surfData.width*4+i*4+0],data[i*surfData.width*4+i*4+1],data[i*surfData.width*4+i*4+2],data[i*surfData.width*4+i*4+3]])}},joystickEventState:1,lastJoystickState:{},joystickNamePool:{},recordJoystickState:function(joystick,state){var buttons=new Array(state.buttons.length);for(var i=0;i<state.buttons.length;i++){buttons[i]=SDL.getJoystickButtonState(state.buttons[i])}SDL.lastJoystickState[joystick]={buttons:buttons,axes:state.axes.slice(0),timestamp:state.timestamp,index:state.index,id:state.id}},getJoystickButtonState:function(button){if(typeof button==="object"){return button.pressed}else{return button>0}},queryJoysticks:function(){for(var joystick in SDL.lastJoystickState){var state=SDL.getGamepad(joystick-1);var prevState=SDL.lastJoystickState[joystick];if(typeof state.timestamp!=="number"||state.timestamp!==prevState.timestamp){var i;for(i=0;i<state.buttons.length;i++){var buttonState=SDL.getJoystickButtonState(state.buttons[i]);if(buttonState!==prevState.buttons[i]){SDL.events.push({type:buttonState?"joystick_button_down":"joystick_button_up",joystick:joystick,index:joystick-1,button:i})}}for(i=0;i<state.axes.length;i++){if(state.axes[i]!==prevState.axes[i]){SDL.events.push({type:"joystick_axis_motion",joystick:joystick,index:joystick-1,axis:i,value:state.axes[i]})}}SDL.recordJoystickState(joystick,state)}}},joystickAxisValueConversion:function(value){return Math.ceil((value+1)*32767.5-32768)},getGamepads:function(){var fcn=navigator.getGamepads||navigator.webkitGamepads||navigator.mozGamepads||navigator.gamepads||navigator.webkitGetGamepads;if(fcn!==undefined){return fcn.apply(navigator)}else{return[]}},getGamepad:function(deviceIndex){var gamepads=SDL.getGamepads();if(gamepads.length>deviceIndex&&deviceIndex>=0){return gamepads[deviceIndex]}return null}};function _SDL_GL_SetAttribute(attr,value){if(!(attr in SDL.glAttributes)){abort("Unknown SDL GL attribute ("+attr+"). Please check if your SDL version is supported.")}SDL.glAttributes[attr]=value}function _SDL_GL_SwapBuffers(){if(Browser.doSwapBuffers)Browser.doSwapBuffers()}function _SDL_GetModState(){return SDL.modState}function _SDL_GetMouseState(x,y){if(x)HEAP32[x>>2]=Browser.mouseX;if(y)HEAP32[y>>2]=Browser.mouseY;return SDL.buttonState}function _SDL_GetWindowSize(sdl_window,width,height){var canvas=Module["canvas"];var w=canvas.width;var h=canvas.height;var devicePixelRatio=1;if(window.amulet.highdpi){devicePixelRatio=window.devicePixelRatio||1}var desiredWidth=canvas.clientWidth*devicePixelRatio;var desiredHeight=canvas.clientHeight*devicePixelRatio;if(w!=desiredWidth||h!=desiredHeight){w=desiredWidth;h=desiredHeight;canvas.width=w;canvas.height=h}if(width)HEAP32[width>>2]=w;if(height)HEAP32[height>>2]=h}function _SDL_Init(initFlags){SDL.startTime=Date.now();SDL.initFlags=initFlags;if(!Module["doNotCaptureKeyboard"]){var keyboardListeningElement=Module["keyboardListeningElement"]||document;keyboardListeningElement.addEventListener("keydown",SDL.receiveEvent);keyboardListeningElement.addEventListener("keyup",SDL.receiveEvent);keyboardListeningElement.addEventListener("keypress",SDL.receiveEvent);window.addEventListener("focus",SDL.receiveEvent);window.addEventListener("blur",SDL.receiveEvent);document.addEventListener("visibilitychange",SDL.receiveEvent)}if(initFlags&512){addEventListener("gamepadconnected",function(){})}window.addEventListener("unload",SDL.receiveEvent);SDL.keyboardState=_malloc(65536);_memset(SDL.keyboardState,0,65536);SDL.DOMEventToSDLEvent["keydown"]=768;SDL.DOMEventToSDLEvent["keyup"]=769;SDL.DOMEventToSDLEvent["keypress"]=771;SDL.DOMEventToSDLEvent["mousedown"]=1025;SDL.DOMEventToSDLEvent["mouseup"]=1026;SDL.DOMEventToSDLEvent["mousemove"]=1024;SDL.DOMEventToSDLEvent["wheel"]=1027;SDL.DOMEventToSDLEvent["touchstart"]=1792;SDL.DOMEventToSDLEvent["touchend"]=1793;SDL.DOMEventToSDLEvent["touchmove"]=1794;SDL.DOMEventToSDLEvent["unload"]=256;SDL.DOMEventToSDLEvent["resize"]=28673;SDL.DOMEventToSDLEvent["visibilitychange"]=512;SDL.DOMEventToSDLEvent["focus"]=512;SDL.DOMEventToSDLEvent["blur"]=512;SDL.DOMEventToSDLEvent["joystick_axis_motion"]=1536;SDL.DOMEventToSDLEvent["joystick_button_down"]=1539;SDL.DOMEventToSDLEvent["joystick_button_up"]=1540;return 0}function _SDL_OpenAudio(desired,obtained){try{SDL.audio={freq:HEAPU32[desired>>2],format:HEAPU16[desired+4>>1],channels:HEAPU8[desired+6>>0],samples:HEAPU16[desired+8>>1],callback:HEAPU32[desired+16>>2],userdata:HEAPU32[desired+20>>2],paused:true,timer:null};if(SDL.audio.format==8){SDL.audio.silence=128}else if(SDL.audio.format==32784){SDL.audio.silence=0}else{throw"Invalid SDL audio format "+SDL.audio.format+"!"}if(SDL.audio.freq<=0){throw"Unsupported sound frequency "+SDL.audio.freq+"!"}else if(SDL.audio.freq<=22050){SDL.audio.freq=22050}else if(SDL.audio.freq<=32e3){SDL.audio.freq=32e3}else if(SDL.audio.freq<=44100){SDL.audio.freq=44100}else if(SDL.audio.freq<=48e3){SDL.audio.freq=48e3}else if(SDL.audio.freq<=96e3){SDL.audio.freq=96e3}else{throw"Unsupported sound frequency "+SDL.audio.freq+"!"}if(SDL.audio.channels==0){SDL.audio.channels=1}else if(SDL.audio.channels<0||SDL.audio.channels>32){throw"Unsupported number of audio channels for SDL audio: "+SDL.audio.channels+"!"}else if(SDL.audio.channels!=1&&SDL.audio.channels!=2){console.log("Warning: Using untested number of audio channels "+SDL.audio.channels)}if(SDL.audio.samples<128||SDL.audio.samples>524288){throw"Unsupported audio callback buffer size "+SDL.audio.samples+"!"}else if((SDL.audio.samples&SDL.audio.samples-1)!=0){throw"Audio callback buffer size "+SDL.audio.samples+" must be a power-of-two!"}var totalSamples=SDL.audio.samples*SDL.audio.channels;SDL.audio.bytesPerSample=4;SDL.audio.bufferSize=totalSamples*SDL.audio.bytesPerSample;SDL.audio.bufferDurationSecs=SDL.audio.bufferSize/SDL.audio.bytesPerSample/SDL.audio.channels/SDL.audio.freq;SDL.audio.bufferingDelay=50/1e3;SDL.audio.buffer=_malloc(SDL.audio.bufferSize);SDL.audio.numSimultaneouslyQueuedBuffers=Module["SDL_numSimultaneouslyQueuedBuffers"]||5;SDL.audio.queueNewAudioData=function SDL_queueNewAudioData(){if(!SDL.audio)return;var secsUntilNextPlayStart=SDL.audio.nextPlayTime-SDL.audioContext["currentTime"];for(var i=0;i<SDL.audio.numSimultaneouslyQueuedBuffers;++i){if(secsUntilNextPlayStart>=SDL.audio.bufferingDelay+SDL.audio.bufferDurationSecs*SDL.audio.numSimultaneouslyQueuedBuffers)return;dynCall("viii",SDL.audio.callback,[SDL.audio.userdata,SDL.audio.buffer,SDL.audio.bufferSize]);SDL.audio.pushAudio(SDL.audio.buffer,SDL.audio.bufferSize)}};SDL.audio.caller=function SDL_audioCaller(){if(!SDL.audio)return;--SDL.audio.numAudioTimersPending;SDL.audio.queueNewAudioData();var secsUntilNextPlayStart=SDL.audio.nextPlayTime-SDL.audioContext["currentTime"];var preemptBufferFeedSecs=SDL.audio.bufferDurationSecs/2;if(SDL.audio.numAudioTimersPending<SDL.audio.numSimultaneouslyQueuedBuffers){++SDL.audio.numAudioTimersPending;SDL.audio.timer=Browser.safeSetTimeout(SDL.audio.caller,Math.max(0,1e3*(secsUntilNextPlayStart-preemptBufferFeedSecs)));if(SDL.audio.numAudioTimersPending<SDL.audio.numSimultaneouslyQueuedBuffers){++SDL.audio.numAudioTimersPending;Browser.safeSetTimeout(SDL.audio.caller,1)}}};SDL.audio.audioOutput=new Audio;SDL.openAudioContext();if(!SDL.audioContext)throw"Web Audio API is not available!";SDL.audio.nextPlayTime=0;SDL.audio.pushAudio=function(ptr,sizeBytes){try{if(SDL.audio.paused)return;if(window.amulet.window_hidden)return;if(!window.amulet.window_has_focus)return;var sizeSamples=sizeBytes/SDL.audio.bytesPerSample;var sizeSamplesPerChannel=sizeSamples/SDL.audio.channels;if(sizeSamplesPerChannel!=SDL.audio.samples){throw"Received mismatching audio buffer size!"}var source=SDL.audioContext["createBufferSource"]();var soundBuffer=SDL.audioContext["createBuffer"](SDL.audio.channels,sizeSamplesPerChannel,SDL.audio.freq);source["connect"](SDL.audioContext["destination"]);SDL.fillWebAudioBufferFromHeap(ptr,sizeSamplesPerChannel,soundBuffer);source["buffer"]=soundBuffer;var curtime=SDL.audioContext["currentTime"];var playtime=Math.max(curtime+SDL.audio.bufferingDelay,SDL.audio.nextPlayTime);if(typeof source["start"]!=="undefined"){source["start"](playtime)}else if(typeof source["noteOn"]!=="undefined"){source["noteOn"](playtime)}SDL.audio.nextPlayTime=playtime+SDL.audio.bufferDurationSecs}catch(e){console.log("Web Audio API error playing back audio: "+e.toString())}};if(obtained){HEAP32[obtained>>2]=SDL.audio.freq;HEAP16[obtained+4>>1]=SDL.audio.format;HEAP8[obtained+6>>0]=SDL.audio.channels;HEAP8[obtained+7>>0]=SDL.audio.silence;HEAP16[obtained+8>>1]=SDL.audio.samples;HEAP32[obtained+16>>2]=SDL.audio.callback;HEAP32[obtained+20>>2]=SDL.audio.userdata}SDL.allocateChannels(32)}catch(e){console.log('Initializing SDL audio threw an exception: "'+e.toString()+'"! Continuing without audio.');SDL.audio=null;SDL.allocateChannels(0);if(obtained){HEAP32[obtained>>2]=0;HEAP16[obtained+4>>1]=0;HEAP8[obtained+6>>0]=0;HEAP8[obtained+7>>0]=0;HEAP16[obtained+8>>1]=0;HEAP32[obtained+16>>2]=0;HEAP32[obtained+20>>2]=0}}if(!SDL.audio){return-1}return 0}function _SDL_PauseAudio(pauseOn){if(!SDL.audio){return}if(pauseOn){if(SDL.audio.timer!==undefined){clearTimeout(SDL.audio.timer);SDL.audio.numAudioTimersPending=0;SDL.audio.timer=undefined}}else if(!SDL.audio.timer){SDL.audio.numAudioTimersPending=1;SDL.audio.timer=Browser.safeSetTimeout(SDL.audio.caller,1)}SDL.audio.paused=pauseOn}function _SDL_PollEvent(ptr){return SDL.pollEvent(ptr)}var GL={counter:1,lastError:0,buffers:[],mappedBuffers:{},programs:[],framebuffers:[],renderbuffers:[],textures:[],uniforms:[],shaders:[],vaos:[],contexts:{},currentContext:null,offscreenCanvases:{},timerQueriesEXT:[],programInfos:{},stringCache:{},unpackAlignment:4,init:function(){GL.miniTempBuffer=new Float32Array(GL.MINI_TEMP_BUFFER_SIZE);for(var i=0;i<GL.MINI_TEMP_BUFFER_SIZE;i++){GL.miniTempBufferViews[i]=GL.miniTempBuffer.subarray(0,i+1)}},recordError:function recordError(errorCode){if(!GL.lastError){GL.lastError=errorCode}},getNewId:function(table){var ret=GL.counter++;for(var i=table.length;i<ret;i++){table[i]=null}return ret},MINI_TEMP_BUFFER_SIZE:256,miniTempBuffer:null,miniTempBufferViews:[0],getSource:function(shader,count,string,length){var source="";for(var i=0;i<count;++i){var len=length?HEAP32[length+i*4>>2]:-1;source+=UTF8ToString(HEAP32[string+i*4>>2],len<0?undefined:len)}return source},createContext:function(canvas,webGLContextAttributes){var ctx=canvas.getContext("webgl",webGLContextAttributes)||canvas.getContext("experimental-webgl",webGLContextAttributes);if(!ctx)return 0;var handle=GL.registerContext(ctx,webGLContextAttributes);return handle},registerContext:function(ctx,webGLContextAttributes){var handle=_malloc(8);var context={handle:handle,attributes:webGLContextAttributes,version:webGLContextAttributes.majorVersion,GLctx:ctx};if(ctx.canvas)ctx.canvas.GLctxObject=context;GL.contexts[handle]=context;if(typeof webGLContextAttributes.enableExtensionsByDefault==="undefined"||webGLContextAttributes.enableExtensionsByDefault){GL.initExtensions(context)}return handle},makeContextCurrent:function(contextHandle){GL.currentContext=GL.contexts[contextHandle];Module.ctx=GLctx=GL.currentContext&&GL.currentContext.GLctx;return!(contextHandle&&!GLctx)},getContext:function(contextHandle){return GL.contexts[contextHandle]},deleteContext:function(contextHandle){if(GL.currentContext===GL.contexts[contextHandle])GL.currentContext=null;if(typeof JSEvents==="object")JSEvents.removeAllHandlersOnTarget(GL.contexts[contextHandle].GLctx.canvas);if(GL.contexts[contextHandle]&&GL.contexts[contextHandle].GLctx.canvas)GL.contexts[contextHandle].GLctx.canvas.GLctxObject=undefined;_free(GL.contexts[contextHandle]);GL.contexts[contextHandle]=null},acquireInstancedArraysExtension:function(ctx){var ext=ctx.getExtension("ANGLE_instanced_arrays");if(ext){ctx["vertexAttribDivisor"]=function(index,divisor){ext["vertexAttribDivisorANGLE"](index,divisor)};ctx["drawArraysInstanced"]=function(mode,first,count,primcount){ext["drawArraysInstancedANGLE"](mode,first,count,primcount)};ctx["drawElementsInstanced"]=function(mode,count,type,indices,primcount){ext["drawElementsInstancedANGLE"](mode,count,type,indices,primcount)}}},acquireVertexArrayObjectExtension:function(ctx){var ext=ctx.getExtension("OES_vertex_array_object");if(ext){ctx["createVertexArray"]=function(){return ext["createVertexArrayOES"]()};ctx["deleteVertexArray"]=function(vao){ext["deleteVertexArrayOES"](vao)};ctx["bindVertexArray"]=function(vao){ext["bindVertexArrayOES"](vao)};ctx["isVertexArray"]=function(vao){return ext["isVertexArrayOES"](vao)}}},acquireDrawBuffersExtension:function(ctx){var ext=ctx.getExtension("WEBGL_draw_buffers");if(ext){ctx["drawBuffers"]=function(n,bufs){ext["drawBuffersWEBGL"](n,bufs)}}},initExtensions:function(context){if(!context)context=GL.currentContext;if(context.initExtensionsDone)return;context.initExtensionsDone=true;var GLctx=context.GLctx;if(context.version<2){GL.acquireInstancedArraysExtension(GLctx);GL.acquireVertexArrayObjectExtension(GLctx);GL.acquireDrawBuffersExtension(GLctx)}GLctx.disjointTimerQueryExt=GLctx.getExtension("EXT_disjoint_timer_query");var automaticallyEnabledExtensions=["OES_texture_float","OES_texture_half_float","OES_standard_derivatives","OES_vertex_array_object","WEBGL_compressed_texture_s3tc","WEBGL_depth_texture","OES_element_index_uint","EXT_texture_filter_anisotropic","EXT_frag_depth","WEBGL_draw_buffers","ANGLE_instanced_arrays","OES_texture_float_linear","OES_texture_half_float_linear","EXT_blend_minmax","EXT_shader_texture_lod","WEBGL_compressed_texture_pvrtc","EXT_color_buffer_half_float","WEBGL_color_buffer_float","EXT_sRGB","WEBGL_compressed_texture_etc1","EXT_disjoint_timer_query","WEBGL_compressed_texture_etc","WEBGL_compressed_texture_astc","EXT_color_buffer_float","WEBGL_compressed_texture_s3tc_srgb","EXT_disjoint_timer_query_webgl2"];var exts=GLctx.getSupportedExtensions()||[];exts.forEach(function(ext){if(automaticallyEnabledExtensions.indexOf(ext)!=-1){GLctx.getExtension(ext)}})},populateUniformTable:function(program){var p=GL.programs[program];var ptable=GL.programInfos[program]={uniforms:{},maxUniformLength:0,maxAttributeLength:-1,maxUniformBlockNameLength:-1};var utable=ptable.uniforms;var numUniforms=GLctx.getProgramParameter(p,35718);for(var i=0;i<numUniforms;++i){var u=GLctx.getActiveUniform(p,i);var name=u.name;ptable.maxUniformLength=Math.max(ptable.maxUniformLength,name.length+1);if(name.slice(-1)=="]"){name=name.slice(0,name.lastIndexOf("["))}var loc=GLctx.getUniformLocation(p,name);if(loc){var id=GL.getNewId(GL.uniforms);utable[name]=[u.size,id];GL.uniforms[id]=loc;for(var j=1;j<u.size;++j){var n=name+"["+j+"]";loc=GLctx.getUniformLocation(p,n);id=GL.getNewId(GL.uniforms);GL.uniforms[id]=loc}}}}};function _SDL_SetVideoMode(width,height,depth,flags){["touchstart","touchend","touchmove","mousedown","mouseup","mousemove","DOMMouseScroll","mousewheel","wheel","mouseout"].forEach(function(event){Module["canvas"].addEventListener(event,SDL.receiveEvent,true)});var canvas=Module["canvas"];if(width==0&&height==0){width=canvas.width;height=canvas.height}if(!SDL.addedResizeListener){SDL.addedResizeListener=true;Browser.resizeListeners.push(function(w,h){if(!SDL.settingVideoMode){SDL.receiveEvent({type:"resize",w:w,h:h})}})}if(width!==canvas.width||height!==canvas.height){SDL.settingVideoMode=true;Browser.setCanvasSize(width,height);SDL.settingVideoMode=false}if(SDL.screen){SDL.freeSurface(SDL.screen);assert(!SDL.screen)}if(SDL.GL)flags=flags|67108864;SDL.screen=SDL.makeSurface(width,height,flags,true,"screen");return SDL.screen}function _SDL_WM_ToggleFullScreen(surf){console.log("SDL_WM_ToggleFullScreen");if((document["webkitFullScreenElement"]||document["webkitFullscreenElement"]||document["mozFullScreenElement"]||document["mozFullscreenElement"]||document["fullScreenElement"]||document["fullscreenElement"]||document["msFullScreenElement"]||document["msFullscreenElement"]||document["webkitCurrentFullScreenElement"])===Module["canvas"]){var canvas=Module["canvas"];canvas.cancelFullScreen=document["cancelFullScreen"]||document["mozCancelFullScreen"]||document["webkitCancelFullScreen"]||document["msExitFullscreen"]||document["exitFullscreen"]||function(){};canvas.cancelFullScreen=canvas.cancelFullScreen.bind(document);canvas.cancelFullScreen();return 1}else{if(!SDL.canRequestFullscreen){return 0}SDL.isRequestingFullscreen=true;return 1}}var ENV={};function ___buildEnvironment(environ){var MAX_ENV_VALUES=64;var TOTAL_ENV_SIZE=1024;var poolPtr;var envPtr;if(!___buildEnvironment.called){___buildEnvironment.called=true;ENV["USER"]="web_user";ENV["LOGNAME"]="web_user";ENV["PATH"]="/";ENV["PWD"]="/";ENV["HOME"]="/home/web_user";ENV["LANG"]=(typeof navigator==="object"&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8";ENV["_"]=thisProgram;poolPtr=getMemory(TOTAL_ENV_SIZE);envPtr=getMemory(MAX_ENV_VALUES*4);HEAP32[envPtr>>2]=poolPtr;HEAP32[environ>>2]=envPtr}else{envPtr=HEAP32[environ>>2];poolPtr=HEAP32[envPtr>>2]}var strings=[];var totalSize=0;for(var key in ENV){if(typeof ENV[key]==="string"){var line=key+"="+ENV[key];strings.push(line);totalSize+=line.length}}if(totalSize>TOTAL_ENV_SIZE){throw new Error("Environment size exceeded TOTAL_ENV_SIZE!")}var ptrSize=4;for(var i=0;i<strings.length;i++){var line=strings[i];writeAsciiToMemory(line,poolPtr);HEAP32[envPtr+i*ptrSize>>2]=poolPtr;poolPtr+=line.length+1}HEAP32[envPtr+strings.length*ptrSize>>2]=0}function _emscripten_get_now_is_monotonic(){return 0||ENVIRONMENT_IS_NODE||typeof dateNow!=="undefined"||typeof performance==="object"&&performance&&typeof performance["now"]==="function"}function _clock_gettime(clk_id,tp){var now;if(clk_id===0){now=Date.now()}else if(clk_id===1&&_emscripten_get_now_is_monotonic()){now=_emscripten_get_now()}else{___setErrNo(28);return-1}HEAP32[tp>>2]=now/1e3|0;HEAP32[tp+4>>2]=now%1e3*1e3*1e3|0;return 0}function ___clock_gettime(a0,a1){return _clock_gettime(a0,a1)}function ___cxa_allocate_exception(size){return _malloc(size)}var ___exception_infos={};var ___exception_last=0;function ___cxa_throw(ptr,type,destructor){___exception_infos[ptr]={ptr:ptr,adjusted:[ptr],type:type,destructor:destructor,refcount:0,caught:false,rethrown:false};___exception_last=ptr;if(!("uncaught_exception"in __ZSt18uncaught_exceptionv)){__ZSt18uncaught_exceptionv.uncaught_exceptions=1}else{__ZSt18uncaught_exceptionv.uncaught_exceptions++}throw ptr}function ___gxx_personality_v0(){}function ___lock(){}function ___map_file(pathname,size){___setErrNo(63);return-1}var SYSCALLS={DEFAULT_POLLMASK:5,mappings:{},umask:511,calculateAt:function(dirfd,path){if(path[0]!=="/"){var dir;if(dirfd===-100){dir=FS.cwd()}else{var dirstream=FS.getStream(dirfd);if(!dirstream)throw new FS.ErrnoError(8);dir=dirstream.path}path=PATH.join2(dir,path)}return path},doStat:function(func,path,buf){try{var stat=func(path)}catch(e){if(e&&e.node&&PATH.normalize(path)!==PATH.normalize(FS.getPath(e.node))){return-54}throw e}HEAP32[buf>>2]=stat.dev;HEAP32[buf+4>>2]=0;HEAP32[buf+8>>2]=stat.ino;HEAP32[buf+12>>2]=stat.mode;HEAP32[buf+16>>2]=stat.nlink;HEAP32[buf+20>>2]=stat.uid;HEAP32[buf+24>>2]=stat.gid;HEAP32[buf+28>>2]=stat.rdev;HEAP32[buf+32>>2]=0;tempI64=[stat.size>>>0,(tempDouble=stat.size,+Math_abs(tempDouble)>=1?tempDouble>0?(Math_min(+Math_floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math_ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+40>>2]=tempI64[0],HEAP32[buf+44>>2]=tempI64[1];HEAP32[buf+48>>2]=4096;HEAP32[buf+52>>2]=stat.blocks;HEAP32[buf+56>>2]=stat.atime.getTime()/1e3|0;HEAP32[buf+60>>2]=0;HEAP32[buf+64>>2]=stat.mtime.getTime()/1e3|0;HEAP32[buf+68>>2]=0;HEAP32[buf+72>>2]=stat.ctime.getTime()/1e3|0;HEAP32[buf+76>>2]=0;tempI64=[stat.ino>>>0,(tempDouble=stat.ino,+Math_abs(tempDouble)>=1?tempDouble>0?(Math_min(+Math_floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math_ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+80>>2]=tempI64[0],HEAP32[buf+84>>2]=tempI64[1];return 0},doMsync:function(addr,stream,len,flags){var buffer=new Uint8Array(HEAPU8.subarray(addr,addr+len));FS.msync(stream,buffer,0,len,flags)},doMkdir:function(path,mode){path=PATH.normalize(path);if(path[path.length-1]==="/")path=path.substr(0,path.length-1);FS.mkdir(path,mode,0);return 0},doMknod:function(path,mode,dev){switch(mode&61440){case 32768:case 8192:case 24576:case 4096:case 49152:break;default:return-28}FS.mknod(path,mode,dev);return 0},doReadlink:function(path,buf,bufsize){if(bufsize<=0)return-28;var ret=FS.readlink(path);var len=Math.min(bufsize,lengthBytesUTF8(ret));var endChar=HEAP8[buf+len];stringToUTF8(ret,buf,bufsize+1);HEAP8[buf+len]=endChar;return len},doAccess:function(path,amode){if(amode&~7){return-28}var node;var lookup=FS.lookupPath(path,{follow:true});node=lookup.node;if(!node){return-44}var perms="";if(amode&4)perms+="r";if(amode&2)perms+="w";if(amode&1)perms+="x";if(perms&&FS.nodePermissions(node,perms)){return-2}return 0},doDup:function(path,flags,suggestFD){var suggest=FS.getStream(suggestFD);if(suggest)FS.close(suggest);return FS.open(path,flags,0,suggestFD,suggestFD).fd},doReadv:function(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i<iovcnt;i++){var ptr=HEAP32[iov+i*8>>2];var len=HEAP32[iov+(i*8+4)>>2];var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr<len)break}return ret},doWritev:function(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i<iovcnt;i++){var ptr=HEAP32[iov+i*8>>2];var len=HEAP32[iov+(i*8+4)>>2];var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr}return ret},varargs:0,get:function(varargs){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(){var ret=UTF8ToString(SYSCALLS.get());return ret},getStreamFromFD:function(fd){if(fd===undefined)fd=SYSCALLS.get();var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);return stream},get64:function(){var low=SYSCALLS.get(),high=SYSCALLS.get();return low},getZero:function(){SYSCALLS.get()}};function ___syscall10(which,varargs){SYSCALLS.varargs=varargs;try{var path=SYSCALLS.getStr();FS.unlink(path);return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___syscall196(which,varargs){SYSCALLS.varargs=varargs;try{var path=SYSCALLS.getStr(),buf=SYSCALLS.get();return SYSCALLS.doStat(FS.lstat,path,buf)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___syscall221(which,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(),cmd=SYSCALLS.get();switch(cmd){case 0:{var arg=SYSCALLS.get();if(arg<0){return-28}var newStream;newStream=FS.open(stream.path,stream.flags,0,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=SYSCALLS.get();stream.flags|=arg;return 0}case 12:{var arg=SYSCALLS.get();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 13:case 14:return 0;case 16:case 8:return-28;case 9:___setErrNo(28);return-1;default:{return-28}}}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___syscall330(which,varargs){SYSCALLS.varargs=varargs;try{var old=SYSCALLS.getStreamFromFD(),suggestFD=SYSCALLS.get(),flags=SYSCALLS.get();if(old.fd===suggestFD)return-28;return SYSCALLS.doDup(old.path,old.flags,suggestFD)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___syscall38(which,varargs){SYSCALLS.varargs=varargs;try{var old_path=SYSCALLS.getStr(),new_path=SYSCALLS.getStr();FS.rename(old_path,new_path);return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___syscall40(which,varargs){SYSCALLS.varargs=varargs;try{var path=SYSCALLS.getStr();FS.rmdir(path);return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___syscall5(which,varargs){SYSCALLS.varargs=varargs;try{var pathname=SYSCALLS.getStr(),flags=SYSCALLS.get(),mode=SYSCALLS.get();var stream=FS.open(pathname,flags,mode);return stream.fd}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___syscall54(which,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(),op=SYSCALLS.get();switch(op){case 21509:case 21505:{if(!stream.tty)return-59;return 0}case 21510:case 21511:case 21512:case 21506:case 21507:case 21508:{if(!stream.tty)return-59;return 0}case 21519:{if(!stream.tty)return-59;var argp=SYSCALLS.get();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21531:{var argp=SYSCALLS.get();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;return 0}case 21524:{if(!stream.tty)return-59;return 0}default:abort("bad ioctl syscall "+op)}}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___syscall63(which,varargs){SYSCALLS.varargs=varargs;try{var old=SYSCALLS.getStreamFromFD(),suggestFD=SYSCALLS.get();if(old.fd===suggestFD)return suggestFD;return SYSCALLS.doDup(old.path,old.flags,suggestFD)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function __emscripten_syscall_munmap(addr,len){if(addr===-1||len===0){return-28}var info=SYSCALLS.mappings[addr];if(!info)return 0;if(len===info.len){var stream=FS.getStream(info.fd);SYSCALLS.doMsync(addr,stream,len,info.flags);FS.munmap(stream);SYSCALLS.mappings[addr]=null;if(info.allocated){_free(info.malloc)}}return 0}function ___syscall91(which,varargs){SYSCALLS.varargs=varargs;try{var addr=SYSCALLS.get(),len=SYSCALLS.get();return __emscripten_syscall_munmap(addr,len)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___unlock(){}function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function ___wasi_fd_close(){return _fd_close.apply(null,arguments)}function _fd_read(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=SYSCALLS.doReadv(stream,iov,iovcnt);HEAP32[pnum>>2]=num;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function ___wasi_fd_read(){return _fd_read.apply(null,arguments)}function _fd_seek(fd,offset_low,offset_high,whence,newOffset){try{var stream=SYSCALLS.getStreamFromFD(fd);var HIGH_OFFSET=4294967296;var offset=offset_high*HIGH_OFFSET+(offset_low>>>0);var DOUBLE_LIMIT=9007199254740992;if(offset<=-DOUBLE_LIMIT||offset>=DOUBLE_LIMIT){return-61}FS.llseek(stream,offset,whence);tempI64=[stream.position>>>0,(tempDouble=stream.position,+Math_abs(tempDouble)>=1?tempDouble>0?(Math_min(+Math_floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math_ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[newOffset>>2]=tempI64[0],HEAP32[newOffset+4>>2]=tempI64[1];if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function ___wasi_fd_seek(){return _fd_seek.apply(null,arguments)}function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=SYSCALLS.doWritev(stream,iov,iovcnt);HEAP32[pnum>>2]=num;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function ___wasi_fd_write(){return _fd_write.apply(null,arguments)}function _abort(){abort()}function _clock(){if(_clock.start===undefined)_clock.start=Date.now();return(Date.now()-_clock.start)*(1e6/1e3)|0}function _difftime(time1,time0){return time1-time0}function _emscripten_async_wget2(url,file,request,param,arg,onload,onerror,onprogress){noExitRuntime=true;var _url=UTF8ToString(url);var _file=UTF8ToString(file);_file=PATH_FS.resolve(_file);var _request=UTF8ToString(request);var _param=UTF8ToString(param);var index=_file.lastIndexOf("/");var http=new XMLHttpRequest;http.open(_request,_url,true);http.responseType="arraybuffer";var handle=Browser.getNextWgetRequestHandle();var destinationDirectory=PATH.dirname(_file);http.onload=function http_onload(e){if(http.status>=200&&http.status<300){try{FS.unlink(_file)}catch(e){}FS.mkdirTree(destinationDirectory);FS.createDataFile(_file.substr(0,index),_file.substr(index+1),new Uint8Array(http.response),true,true,false);if(onload){var stack=stackSave();dynCall_viii(onload,handle,arg,allocate(intArrayFromString(_file),"i8",ALLOC_STACK));stackRestore(stack)}}else{if(onerror)dynCall_viii(onerror,handle,arg,http.status)}delete Browser.wgetRequests[handle]};http.onerror=function http_onerror(e){if(onerror)dynCall_viii(onerror,handle,arg,http.status);delete Browser.wgetRequests[handle]};http.onprogress=function http_onprogress(e){if(e.lengthComputable||e.lengthComputable===undefined&&e.total!=0){var percentComplete=e.loaded/e.total*100;if(onprogress)dynCall_viii(onprogress,handle,arg,percentComplete)}};http.onabort=function http_onabort(e){delete Browser.wgetRequests[handle]};if(_request=="POST"){http.setRequestHeader("Content-type","application/x-www-form-urlencoded");http.send(_param)}else{http.send(null)}Browser.wgetRequests[handle]=http;return handle}function _emscripten_get_heap_size(){return HEAP8.length}function abortOnCannotGrowMemory(requestedSize){abort("OOM")}function emscripten_realloc_buffer(size){try{wasmMemory.grow(size-buffer.byteLength+65535>>16);updateGlobalBufferAndViews(wasmMemory.buffer);return 1}catch(e){}}function _emscripten_resize_heap(requestedSize){var oldSize=_emscripten_get_heap_size();var PAGE_MULTIPLE=65536;var LIMIT=2147483648-PAGE_MULTIPLE;if(requestedSize>LIMIT){return false}var MIN_TOTAL_MEMORY=16777216;var newSize=Math.max(oldSize,MIN_TOTAL_MEMORY);while(newSize<requestedSize){if(newSize<=536870912){newSize=alignUp(2*newSize,PAGE_MULTIPLE)}else{newSize=Math.min(alignUp((3*newSize+2147483648)/4,PAGE_MULTIPLE),LIMIT)}}var replacement=emscripten_realloc_buffer(newSize);if(!replacement){return false}return true}function _exit(status){exit(status)}function _getenv(name){if(name===0)return 0;name=UTF8ToString(name);if(!ENV.hasOwnProperty(name))return 0;if(_getenv.ret)_free(_getenv.ret);_getenv.ret=allocateUTF8(ENV[name]);return _getenv.ret}function _glActiveTexture(x0){GLctx["activeTexture"](x0)}function _glAttachShader(program,shader){GLctx.attachShader(GL.programs[program],GL.shaders[shader])}function _glBindBuffer(target,buffer){GLctx.bindBuffer(target,GL.buffers[buffer])}function _glBindFramebuffer(target,framebuffer){GLctx.bindFramebuffer(target,GL.framebuffers[framebuffer])}function _glBindRenderbuffer(target,renderbuffer){GLctx.bindRenderbuffer(target,GL.renderbuffers[renderbuffer])}function _glBindTexture(target,texture){GLctx.bindTexture(target,GL.textures[texture])}function _glBlendColor(x0,x1,x2,x3){GLctx["blendColor"](x0,x1,x2,x3)}function _glBlendEquation(x0){GLctx["blendEquation"](x0)}function _glBlendEquationSeparate(x0,x1){GLctx["blendEquationSeparate"](x0,x1)}function _glBlendFunc(x0,x1){GLctx["blendFunc"](x0,x1)}function _glBlendFuncSeparate(x0,x1,x2,x3){GLctx["blendFuncSeparate"](x0,x1,x2,x3)}function _glBufferData(target,size,data,usage){GLctx.bufferData(target,data?HEAPU8.subarray(data,data+size):size,usage)}function _glBufferSubData(target,offset,size,data){GLctx.bufferSubData(target,offset,HEAPU8.subarray(data,data+size))}function _glCheckFramebufferStatus(x0){return GLctx["checkFramebufferStatus"](x0)}function _glClear(x0){GLctx["clear"](x0)}function _glClearColor(x0,x1,x2,x3){GLctx["clearColor"](x0,x1,x2,x3)}function _glClearStencil(x0){GLctx["clearStencil"](x0)}function _glColorMask(red,green,blue,alpha){GLctx.colorMask(!!red,!!green,!!blue,!!alpha)}function _glCompileShader(shader){GLctx.compileShader(GL.shaders[shader])}function _glCreateProgram(){var id=GL.getNewId(GL.programs);var program=GLctx.createProgram();program.name=id;GL.programs[id]=program;return id}function _glCreateShader(shaderType){var id=GL.getNewId(GL.shaders);GL.shaders[id]=GLctx.createShader(shaderType);return id}function _glCullFace(x0){GLctx["cullFace"](x0)}function _glDeleteBuffers(n,buffers){for(var i=0;i<n;i++){var id=HEAP32[buffers+i*4>>2];var buffer=GL.buffers[id];if(!buffer)continue;GLctx.deleteBuffer(buffer);buffer.name=0;GL.buffers[id]=null;if(id==GL.currArrayBuffer)GL.currArrayBuffer=0;if(id==GL.currElementArrayBuffer)GL.currElementArrayBuffer=0}}function _glDeleteFramebuffers(n,framebuffers){for(var i=0;i<n;++i){var id=HEAP32[framebuffers+i*4>>2];var framebuffer=GL.framebuffers[id];if(!framebuffer)continue;GLctx.deleteFramebuffer(framebuffer);framebuffer.name=0;GL.framebuffers[id]=null}}function _glDeleteProgram(id){if(!id)return;var program=GL.programs[id];if(!program){GL.recordError(1281);return}GLctx.deleteProgram(program);program.name=0;GL.programs[id]=null;GL.programInfos[id]=null}function _glDeleteRenderbuffers(n,renderbuffers){for(var i=0;i<n;i++){var id=HEAP32[renderbuffers+i*4>>2];var renderbuffer=GL.renderbuffers[id];if(!renderbuffer)continue;GLctx.deleteRenderbuffer(renderbuffer);renderbuffer.name=0;GL.renderbuffers[id]=null}}function _glDeleteShader(id){if(!id)return;var shader=GL.shaders[id];if(!shader){GL.recordError(1281);return}GLctx.deleteShader(shader);GL.shaders[id]=null}function _glDeleteTextures(n,textures){for(var i=0;i<n;i++){var id=HEAP32[textures+i*4>>2];var texture=GL.textures[id];if(!texture)continue;GLctx.deleteTexture(texture);texture.name=0;GL.textures[id]=null}}function _glDepthFunc(x0){GLctx["depthFunc"](x0)}function _glDepthMask(flag){GLctx.depthMask(!!flag)}function _glDetachShader(program,shader){GLctx.detachShader(GL.programs[program],GL.shaders[shader])}function _glDisable(x0){GLctx["disable"](x0)}function _glDisableVertexAttribArray(index){GLctx.disableVertexAttribArray(index)}function _glDrawArrays(mode,first,count){GLctx.drawArrays(mode,first,count)}function _glDrawElements(mode,count,type,indices){GLctx.drawElements(mode,count,type,indices)}function _glEnable(x0){GLctx["enable"](x0)}function _glEnableVertexAttribArray(index){GLctx.enableVertexAttribArray(index)}function _glFramebufferRenderbuffer(target,attachment,renderbuffertarget,renderbuffer){GLctx.framebufferRenderbuffer(target,attachment,renderbuffertarget,GL.renderbuffers[renderbuffer])}function _glFramebufferTexture2D(target,attachment,textarget,texture,level){GLctx.framebufferTexture2D(target,attachment,textarget,GL.textures[texture],level)}function _glFrontFace(x0){GLctx["frontFace"](x0)}function __glGenObject(n,buffers,createFunction,objectTable){for(var i=0;i<n;i++){var buffer=GLctx[createFunction]();var id=buffer&&GL.getNewId(objectTable);if(buffer){buffer.name=id;objectTable[id]=buffer}else{GL.recordError(1282)}HEAP32[buffers+i*4>>2]=id}}function _glGenBuffers(n,buffers){__glGenObject(n,buffers,"createBuffer",GL.buffers)}function _glGenFramebuffers(n,ids){__glGenObject(n,ids,"createFramebuffer",GL.framebuffers)}function _glGenRenderbuffers(n,renderbuffers){__glGenObject(n,renderbuffers,"createRenderbuffer",GL.renderbuffers)}function _glGenTextures(n,textures){__glGenObject(n,textures,"createTexture",GL.textures)}function _glGenerateMipmap(x0){GLctx["generateMipmap"](x0)}function _glGetActiveAttrib(program,index,bufSize,length,size,type,name){program=GL.programs[program];var info=GLctx.getActiveAttrib(program,index);if(!info)return;var numBytesWrittenExclNull=bufSize>0&&name?stringToUTF8(info.name,name,bufSize):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull;if(size)HEAP32[size>>2]=info.size;if(type)HEAP32[type>>2]=info.type}function _glGetActiveUniform(program,index,bufSize,length,size,type,name){program=GL.programs[program];var info=GLctx.getActiveUniform(program,index);if(!info)return;var numBytesWrittenExclNull=bufSize>0&&name?stringToUTF8(info.name,name,bufSize):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull;if(size)HEAP32[size>>2]=info.size;if(type)HEAP32[type>>2]=info.type}function _glGetAttribLocation(program,name){return GLctx.getAttribLocation(GL.programs[program],UTF8ToString(name))}function _glGetError(){var error=GLctx.getError()||GL.lastError;GL.lastError=0;return error}function emscriptenWebGLGet(name_,p,type){if(!p){GL.recordError(1281);return}var ret=undefined;switch(name_){case 36346:ret=1;break;case 36344:if(type!=0&&type!=1){GL.recordError(1280)}return;case 36345:ret=0;break;case 34466:var formats=GLctx.getParameter(34467);ret=formats?formats.length:0;break}if(ret===undefined){var result=GLctx.getParameter(name_);switch(typeof result){case"number":ret=result;break;case"boolean":ret=result?1:0;break;case"string":GL.recordError(1280);return;case"object":if(result===null){switch(name_){case 34964:case 35725:case 34965:case 36006:case 36007:case 32873:case 34229:case 34068:{ret=0;break}default:{GL.recordError(1280);return}}}else if(result instanceof Float32Array||result instanceof Uint32Array||result instanceof Int32Array||result instanceof Array){for(var i=0;i<result.length;++i){switch(type){case 0:HEAP32[p+i*4>>2]=result[i];break;case 2:HEAPF32[p+i*4>>2]=result[i];break;case 4:HEAP8[p+i>>0]=result[i]?1:0;break}}return}else{try{ret=result.name|0}catch(e){GL.recordError(1280);err("GL_INVALID_ENUM in glGet"+type+"v: Unknown object returned from WebGL getParameter("+name_+")! (error: "+e+")");return}}break;default:GL.recordError(1280);err("GL_INVALID_ENUM in glGet"+type+"v: Native code calling glGet"+type+"v("+name_+") and it returns "+result+" of type "+typeof result+"!");return}}switch(type){case 1:tempI64=[ret>>>0,(tempDouble=ret,+Math_abs(tempDouble)>=1?tempDouble>0?(Math_min(+Math_floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math_ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[p>>2]=tempI64[0],HEAP32[p+4>>2]=tempI64[1];break;case 0:HEAP32[p>>2]=ret;break;case 2:HEAPF32[p>>2]=ret;break;case 4:HEAP8[p>>0]=ret?1:0;break}}function _glGetIntegerv(name_,p){emscriptenWebGLGet(name_,p,0)}function _glGetProgramInfoLog(program,maxLength,length,infoLog){var log=GLctx.getProgramInfoLog(GL.programs[program]);if(log===null)log="(unknown error)";var numBytesWrittenExclNull=maxLength>0&&infoLog?stringToUTF8(log,infoLog,maxLength):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull}function _glGetProgramiv(program,pname,p){if(!p){GL.recordError(1281);return}if(program>=GL.counter){GL.recordError(1281);return}var ptable=GL.programInfos[program];if(!ptable){GL.recordError(1282);return}if(pname==35716){var log=GLctx.getProgramInfoLog(GL.programs[program]);if(log===null)log="(unknown error)";HEAP32[p>>2]=log.length+1}else if(pname==35719){HEAP32[p>>2]=ptable.maxUniformLength}else if(pname==35722){if(ptable.maxAttributeLength==-1){program=GL.programs[program];var numAttribs=GLctx.getProgramParameter(program,35721);ptable.maxAttributeLength=0;for(var i=0;i<numAttribs;++i){var activeAttrib=GLctx.getActiveAttrib(program,i);ptable.maxAttributeLength=Math.max(ptable.maxAttributeLength,activeAttrib.name.length+1)}}HEAP32[p>>2]=ptable.maxAttributeLength}else if(pname==35381){if(ptable.maxUniformBlockNameLength==-1){program=GL.programs[program];var numBlocks=GLctx.getProgramParameter(program,35382);ptable.maxUniformBlockNameLength=0;for(var i=0;i<numBlocks;++i){var activeBlockName=GLctx.getActiveUniformBlockName(program,i);ptable.maxUniformBlockNameLength=Math.max(ptable.maxUniformBlockNameLength,activeBlockName.length+1)}}HEAP32[p>>2]=ptable.maxUniformBlockNameLength}else{HEAP32[p>>2]=GLctx.getProgramParameter(GL.programs[program],pname)}}function _glGetShaderInfoLog(shader,maxLength,length,infoLog){var log=GLctx.getShaderInfoLog(GL.shaders[shader]);if(log===null)log="(unknown error)";var numBytesWrittenExclNull=maxLength>0&&infoLog?stringToUTF8(log,infoLog,maxLength):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull}function _glGetShaderiv(shader,pname,p){if(!p){GL.recordError(1281);return}if(pname==35716){var log=GLctx.getShaderInfoLog(GL.shaders[shader]);if(log===null)log="(unknown error)";HEAP32[p>>2]=log.length+1}else if(pname==35720){var source=GLctx.getShaderSource(GL.shaders[shader]);var sourceLength=source===null||source.length==0?0:source.length+1;HEAP32[p>>2]=sourceLength}else{HEAP32[p>>2]=GLctx.getShaderParameter(GL.shaders[shader],pname)}}function _glGetUniformLocation(program,name){name=UTF8ToString(name);var arrayIndex=0;if(name[name.length-1]=="]"){var leftBrace=name.lastIndexOf("[");arrayIndex=name[leftBrace+1]!="]"?parseInt(name.slice(leftBrace+1)):0;name=name.slice(0,leftBrace)}var uniformInfo=GL.programInfos[program]&&GL.programInfos[program].uniforms[name];if(uniformInfo&&arrayIndex>=0&&arrayIndex<uniformInfo[0]){return uniformInfo[1]+arrayIndex}else{return-1}}function _glHint(x0,x1){GLctx["hint"](x0,x1)}function _glLinkProgram(program){GLctx.linkProgram(GL.programs[program]);GL.populateUniformTable(program)}function _glPixelStorei(pname,param){if(pname==3317){GL.unpackAlignment=param}GLctx.pixelStorei(pname,param)}function __computeUnpackAlignedImageSize(width,height,sizePerPixel,alignment){function roundedToNextMultipleOf(x,y){return x+y-1&-y}var plainRowSize=width*sizePerPixel;var alignedRowSize=roundedToNextMultipleOf(plainRowSize,alignment);return height*alignedRowSize}var __colorChannelsInGlTextureFormat={6402:1,6406:1,6407:3,6408:4,6409:1,6410:2,35904:3,35906:4};var __sizeOfGlTextureElementType={5121:1,5123:2,5125:4,5126:4,32819:2,32820:2,33635:2,34042:4,36193:2};function emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,internalFormat){var sizePerPixel=__colorChannelsInGlTextureFormat[format]*__sizeOfGlTextureElementType[type];if(!sizePerPixel){GL.recordError(1280);return}var bytes=__computeUnpackAlignedImageSize(width,height,sizePerPixel,GL.unpackAlignment);var end=pixels+bytes;switch(type){case 5121:return HEAPU8.subarray(pixels,end);case 5126:return HEAPF32.subarray(pixels>>2,end>>2);case 5125:case 34042:return HEAPU32.subarray(pixels>>2,end>>2);case 5123:case 33635:case 32819:case 32820:case 36193:return HEAPU16.subarray(pixels>>1,end>>1);default:GL.recordError(1280)}}function _glReadPixels(x,y,width,height,format,type,pixels){var pixelData=emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,format);if(!pixelData){GL.recordError(1280);return}GLctx.readPixels(x,y,width,height,format,type,pixelData)}function _glRenderbufferStorage(x0,x1,x2,x3){GLctx["renderbufferStorage"](x0,x1,x2,x3)}function _glScissor(x0,x1,x2,x3){GLctx["scissor"](x0,x1,x2,x3)}function _glShaderSource(shader,count,string,length){var source=GL.getSource(shader,count,string,length);GLctx.shaderSource(GL.shaders[shader],source)}function _glStencilFuncSeparate(x0,x1,x2,x3){GLctx["stencilFuncSeparate"](x0,x1,x2,x3)}function _glStencilMask(x0){GLctx["stencilMask"](x0)}function _glStencilOpSeparate(x0,x1,x2,x3){GLctx["stencilOpSeparate"](x0,x1,x2,x3)}function _glTexImage2D(target,level,internalFormat,width,height,border,format,type,pixels){GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,pixels?emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,internalFormat):null)}function _glTexParameteri(x0,x1,x2){GLctx["texParameteri"](x0,x1,x2)}function _glTexSubImage2D(target,level,xoffset,yoffset,width,height,format,type,pixels){var pixelData=null;if(pixels)pixelData=emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,0);GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,pixelData)}function _glUniform1fv(location,count,value){if(count<=GL.MINI_TEMP_BUFFER_SIZE){var view=GL.miniTempBufferViews[count-1];for(var i=0;i<count;++i){view[i]=HEAPF32[value+4*i>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*4>>2)}GLctx.uniform1fv(GL.uniforms[location],view)}function _glUniform1iv(location,count,value){GLctx.uniform1iv(GL.uniforms[location],HEAP32.subarray(value>>2,value+count*4>>2))}function _glUniform2fv(location,count,value){if(2*count<=GL.MINI_TEMP_BUFFER_SIZE){var view=GL.miniTempBufferViews[2*count-1];for(var i=0;i<2*count;i+=2){view[i]=HEAPF32[value+4*i>>2];view[i+1]=HEAPF32[value+(4*i+4)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*8>>2)}GLctx.uniform2fv(GL.uniforms[location],view)}function _glUniform3fv(location,count,value){if(3*count<=GL.MINI_TEMP_BUFFER_SIZE){var view=GL.miniTempBufferViews[3*count-1];for(var i=0;i<3*count;i+=3){view[i]=HEAPF32[value+4*i>>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*12>>2)}GLctx.uniform3fv(GL.uniforms[location],view)}function _glUniform4fv(location,count,value){if(4*count<=GL.MINI_TEMP_BUFFER_SIZE){var view=GL.miniTempBufferViews[4*count-1];for(var i=0;i<4*count;i+=4){view[i]=HEAPF32[value+4*i>>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2];view[i+3]=HEAPF32[value+(4*i+12)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*16>>2)}GLctx.uniform4fv(GL.uniforms[location],view)}function _glUniformMatrix2fv(location,count,transpose,value){if(4*count<=GL.MINI_TEMP_BUFFER_SIZE){var view=GL.miniTempBufferViews[4*count-1];for(var i=0;i<4*count;i+=4){view[i]=HEAPF32[value+4*i>>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2];view[i+3]=HEAPF32[value+(4*i+12)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*16>>2)}GLctx.uniformMatrix2fv(GL.uniforms[location],!!transpose,view)}function _glUniformMatrix3fv(location,count,transpose,value){if(9*count<=GL.MINI_TEMP_BUFFER_SIZE){var view=GL.miniTempBufferViews[9*count-1];for(var i=0;i<9*count;i+=9){view[i]=HEAPF32[value+4*i>>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2];view[i+3]=HEAPF32[value+(4*i+12)>>2];view[i+4]=HEAPF32[value+(4*i+16)>>2];view[i+5]=HEAPF32[value+(4*i+20)>>2];view[i+6]=HEAPF32[value+(4*i+24)>>2];view[i+7]=HEAPF32[value+(4*i+28)>>2];view[i+8]=HEAPF32[value+(4*i+32)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*36>>2)}GLctx.uniformMatrix3fv(GL.uniforms[location],!!transpose,view)}function _glUniformMatrix4fv(location,count,transpose,value){if(16*count<=GL.MINI_TEMP_BUFFER_SIZE){var view=GL.miniTempBufferViews[16*count-1];for(var i=0;i<16*count;i+=16){view[i]=HEAPF32[value+4*i>>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2];view[i+3]=HEAPF32[value+(4*i+12)>>2];view[i+4]=HEAPF32[value+(4*i+16)>>2];view[i+5]=HEAPF32[value+(4*i+20)>>2];view[i+6]=HEAPF32[value+(4*i+24)>>2];view[i+7]=HEAPF32[value+(4*i+28)>>2];view[i+8]=HEAPF32[value+(4*i+32)>>2];view[i+9]=HEAPF32[value+(4*i+36)>>2];view[i+10]=HEAPF32[value+(4*i+40)>>2];view[i+11]=HEAPF32[value+(4*i+44)>>2];view[i+12]=HEAPF32[value+(4*i+48)>>2];view[i+13]=HEAPF32[value+(4*i+52)>>2];view[i+14]=HEAPF32[value+(4*i+56)>>2];view[i+15]=HEAPF32[value+(4*i+60)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*64>>2)}GLctx.uniformMatrix4fv(GL.uniforms[location],!!transpose,view)}function _glUseProgram(program){GLctx.useProgram(GL.programs[program])}function _glValidateProgram(program){GLctx.validateProgram(GL.programs[program])}function _glVertexAttribPointer(index,size,type,normalized,stride,ptr){GLctx.vertexAttribPointer(index,size,type,!!normalized,stride,ptr)}function _glViewport(x0,x1,x2,x3){GLctx["viewport"](x0,x1,x2,x3)}var ___tm_current=201344;var ___tm_timezone=(stringToUTF8("GMT",201392,4),201392);function _gmtime_r(time,tmPtr){var date=new Date(HEAP32[time>>2]*1e3);HEAP32[tmPtr>>2]=date.getUTCSeconds();HEAP32[tmPtr+4>>2]=date.getUTCMinutes();HEAP32[tmPtr+8>>2]=date.getUTCHours();HEAP32[tmPtr+12>>2]=date.getUTCDate();HEAP32[tmPtr+16>>2]=date.getUTCMonth();HEAP32[tmPtr+20>>2]=date.getUTCFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getUTCDay();HEAP32[tmPtr+36>>2]=0;HEAP32[tmPtr+32>>2]=0;var start=Date.UTC(date.getUTCFullYear(),0,1,0,0,0,0);var yday=(date.getTime()-start)/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr+40>>2]=___tm_timezone;return tmPtr}function _gmtime(time){return _gmtime_r(time,___tm_current)}function _llvm_log10_f32(x){return Math.log(x)/Math.LN10}function _llvm_log10_f64(a0){return _llvm_log10_f32(a0)}function _tzset(){if(_tzset.called)return;_tzset.called=true;HEAP32[__get_timezone()>>2]=(new Date).getTimezoneOffset()*60;var currentYear=(new Date).getFullYear();var winter=new Date(currentYear,0,1);var summer=new Date(currentYear,6,1);HEAP32[__get_daylight()>>2]=Number(winter.getTimezoneOffset()!=summer.getTimezoneOffset());function extractZone(date){var match=date.toTimeString().match(/\(([A-Za-z ]+)\)$/);return match?match[1]:"GMT"}var winterName=extractZone(winter);var summerName=extractZone(summer);var winterNamePtr=allocate(intArrayFromString(winterName),"i8",ALLOC_NORMAL);var summerNamePtr=allocate(intArrayFromString(summerName),"i8",ALLOC_NORMAL);if(summer.getTimezoneOffset()<winter.getTimezoneOffset()){HEAP32[__get_tzname()>>2]=winterNamePtr;HEAP32[__get_tzname()+4>>2]=summerNamePtr}else{HEAP32[__get_tzname()>>2]=summerNamePtr;HEAP32[__get_tzname()+4>>2]=winterNamePtr}}function _localtime_r(time,tmPtr){_tzset();var date=new Date(HEAP32[time>>2]*1e3);HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getDay();var start=new Date(date.getFullYear(),0,1);var yday=(date.getTime()-start.getTime())/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr+36>>2]=-(date.getTimezoneOffset()*60);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dst=(summerOffset!=winterOffset&&date.getTimezoneOffset()==Math.min(winterOffset,summerOffset))|0;HEAP32[tmPtr+32>>2]=dst;var zonePtr=HEAP32[__get_tzname()+(dst?4:0)>>2];HEAP32[tmPtr+40>>2]=zonePtr;return tmPtr}function _localtime(time){return _localtime_r(time,___tm_current)}function _longjmp(env,value){_setThrew(env,value||1);throw"longjmp"}function _emscripten_memcpy_big(dest,src,num){HEAPU8.set(HEAPU8.subarray(src,src+num),dest)}function _mktime(tmPtr){_tzset();var date=new Date(HEAP32[tmPtr+20>>2]+1900,HEAP32[tmPtr+16>>2],HEAP32[tmPtr+12>>2],HEAP32[tmPtr+8>>2],HEAP32[tmPtr+4>>2],HEAP32[tmPtr>>2],0);var dst=HEAP32[tmPtr+32>>2];var guessedOffset=date.getTimezoneOffset();var start=new Date(date.getFullYear(),0,1);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dstOffset=Math.min(winterOffset,summerOffset);if(dst<0){HEAP32[tmPtr+32>>2]=Number(summerOffset!=winterOffset&&dstOffset==guessedOffset)}else if(dst>0!=(dstOffset==guessedOffset)){var nonDstOffset=Math.max(winterOffset,summerOffset);var trueOffset=dst>0?dstOffset:nonDstOffset;date.setTime(date.getTime()+(trueOffset-guessedOffset)*6e4)}HEAP32[tmPtr+24>>2]=date.getDay();var yday=(date.getTime()-start.getTime())/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday;return date.getTime()/1e3|0}function __isLeapYear(year){return year%4===0&&(year%100!==0||year%400===0)}function __arraySum(array,index){var sum=0;for(var i=0;i<=index;sum+=array[i++]);return sum}var __MONTH_DAYS_LEAP=[31,29,31,30,31,30,31,31,30,31,30,31];var __MONTH_DAYS_REGULAR=[31,28,31,30,31,30,31,31,30,31,30,31];function __addDays(date,days){var newDate=new Date(date.getTime());while(days>0){var leap=__isLeapYear(newDate.getFullYear());var currentMonth=newDate.getMonth();var daysInCurrentMonth=(leap?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR)[currentMonth];if(days>daysInCurrentMonth-newDate.getDate()){days-=daysInCurrentMonth-newDate.getDate()+1;newDate.setDate(1);if(currentMonth<11){newDate.setMonth(currentMonth+1)}else{newDate.setMonth(0);newDate.setFullYear(newDate.getFullYear()+1)}}else{newDate.setDate(newDate.getDate()+days);return newDate}}return newDate}function _strftime(s,maxsize,format,tm){var tm_zone=HEAP32[tm+40>>2];var date={tm_sec:HEAP32[tm>>2],tm_min:HEAP32[tm+4>>2],tm_hour:HEAP32[tm+8>>2],tm_mday:HEAP32[tm+12>>2],tm_mon:HEAP32[tm+16>>2],tm_year:HEAP32[tm+20>>2],tm_wday:HEAP32[tm+24>>2],tm_yday:HEAP32[tm+28>>2],tm_isdst:HEAP32[tm+32>>2],tm_gmtoff:HEAP32[tm+36>>2],tm_zone:tm_zone?UTF8ToString(tm_zone):""};var pattern=UTF8ToString(format);var EXPANSION_RULES_1={"%c":"%a %b %d %H:%M:%S %Y","%D":"%m/%d/%y","%F":"%Y-%m-%d","%h":"%b","%r":"%I:%M:%S %p","%R":"%H:%M","%T":"%H:%M:%S","%x":"%m/%d/%y","%X":"%H:%M:%S","%Ec":"%c","%EC":"%C","%Ex":"%m/%d/%y","%EX":"%H:%M:%S","%Ey":"%y","%EY":"%Y","%Od":"%d","%Oe":"%e","%OH":"%H","%OI":"%I","%Om":"%m","%OM":"%M","%OS":"%S","%Ou":"%u","%OU":"%U","%OV":"%V","%Ow":"%w","%OW":"%W","%Oy":"%y"};for(var rule in EXPANSION_RULES_1){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_1[rule])}var WEEKDAYS=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];var MONTHS=["January","February","March","April","May","June","July","August","September","October","November","December"];function leadingSomething(value,digits,character){var str=typeof value==="number"?value.toString():value||"";while(str.length<digits){str=character[0]+str}return str}function leadingNulls(value,digits){return leadingSomething(value,digits,"0")}function compareByDay(date1,date2){function sgn(value){return value<0?-1:value>0?1:0}var compare;if((compare=sgn(date1.getFullYear()-date2.getFullYear()))===0){if((compare=sgn(date1.getMonth()-date2.getMonth()))===0){compare=sgn(date1.getDate()-date2.getDate())}}return compare}function getFirstWeekStartDate(janFourth){switch(janFourth.getDay()){case 0:return new Date(janFourth.getFullYear()-1,11,29);case 1:return janFourth;case 2:return new Date(janFourth.getFullYear(),0,3);case 3:return new Date(janFourth.getFullYear(),0,2);case 4:return new Date(janFourth.getFullYear(),0,1);case 5:return new Date(janFourth.getFullYear()-1,11,31);case 6:return new Date(janFourth.getFullYear()-1,11,30)}}function getWeekBasedYear(date){var thisDate=__addDays(new Date(date.tm_year+1900,0,1),date.tm_yday);var janFourthThisYear=new Date(thisDate.getFullYear(),0,4);var janFourthNextYear=new Date(thisDate.getFullYear()+1,0,4);var firstWeekStartThisYear=getFirstWeekStartDate(janFourthThisYear);var firstWeekStartNextYear=getFirstWeekStartDate(janFourthNextYear);if(compareByDay(firstWeekStartThisYear,thisDate)<=0){if(compareByDay(firstWeekStartNextYear,thisDate)<=0){return thisDate.getFullYear()+1}else{return thisDate.getFullYear()}}else{return thisDate.getFullYear()-1}}var EXPANSION_RULES_2={"%a":function(date){return WEEKDAYS[date.tm_wday].substring(0,3)},"%A":function(date){return WEEKDAYS[date.tm_wday]},"%b":function(date){return MONTHS[date.tm_mon].substring(0,3)},"%B":function(date){return MONTHS[date.tm_mon]},"%C":function(date){var year=date.tm_year+1900;return leadingNulls(year/100|0,2)},"%d":function(date){return leadingNulls(date.tm_mday,2)},"%e":function(date){return leadingSomething(date.tm_mday,2," ")},"%g":function(date){return getWeekBasedYear(date).toString().substring(2)},"%G":function(date){return getWeekBasedYear(date)},"%H":function(date){return leadingNulls(date.tm_hour,2)},"%I":function(date){var twelveHour=date.tm_hour;if(twelveHour==0)twelveHour=12;else if(twelveHour>12)twelveHour-=12;return leadingNulls(twelveHour,2)},"%j":function(date){return leadingNulls(date.tm_mday+__arraySum(__isLeapYear(date.tm_year+1900)?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR,date.tm_mon-1),3)},"%m":function(date){return leadingNulls(date.tm_mon+1,2)},"%M":function(date){return leadingNulls(date.tm_min,2)},"%n":function(){return"\n"},"%p":function(date){if(date.tm_hour>=0&&date.tm_hour<12){return"AM"}else{return"PM"}},"%S":function(date){return leadingNulls(date.tm_sec,2)},"%t":function(){return"\t"},"%u":function(date){return date.tm_wday||7},"%U":function(date){var janFirst=new Date(date.tm_year+1900,0,1);var firstSunday=janFirst.getDay()===0?janFirst:__addDays(janFirst,7-janFirst.getDay());var endDate=new Date(date.tm_year+1900,date.tm_mon,date.tm_mday);if(compareByDay(firstSunday,endDate)<0){var februaryFirstUntilEndMonth=__arraySum(__isLeapYear(endDate.getFullYear())?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR,endDate.getMonth()-1)-31;var firstSundayUntilEndJanuary=31-firstSunday.getDate();var days=firstSundayUntilEndJanuary+februaryFirstUntilEndMonth+endDate.getDate();return leadingNulls(Math.ceil(days/7),2)}return compareByDay(firstSunday,janFirst)===0?"01":"00"},"%V":function(date){var janFourthThisYear=new Date(date.tm_year+1900,0,4);var janFourthNextYear=new Date(date.tm_year+1901,0,4);var firstWeekStartThisYear=getFirstWeekStartDate(janFourthThisYear);var firstWeekStartNextYear=getFirstWeekStartDate(janFourthNextYear);var endDate=__addDays(new Date(date.tm_year+1900,0,1),date.tm_yday);if(compareByDay(endDate,firstWeekStartThisYear)<0){return"53"}if(compareByDay(firstWeekStartNextYear,endDate)<=0){return"01"}var daysDifference;if(firstWeekStartThisYear.getFullYear()<date.tm_year+1900){daysDifference=date.tm_yday+32-firstWeekStartThisYear.getDate()}else{daysDifference=date.tm_yday+1-firstWeekStartThisYear.getDate()}return leadingNulls(Math.ceil(daysDifference/7),2)},"%w":function(date){return date.tm_wday},"%W":function(date){var janFirst=new Date(date.tm_year,0,1);var firstMonday=janFirst.getDay()===1?janFirst:__addDays(janFirst,janFirst.getDay()===0?1:7-janFirst.getDay()+1);var endDate=new Date(date.tm_year+1900,date.tm_mon,date.tm_mday);if(compareByDay(firstMonday,endDate)<0){var februaryFirstUntilEndMonth=__arraySum(__isLeapYear(endDate.getFullYear())?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR,endDate.getMonth()-1)-31;var firstMondayUntilEndJanuary=31-firstMonday.getDate();var days=firstMondayUntilEndJanuary+februaryFirstUntilEndMonth+endDate.getDate();return leadingNulls(Math.ceil(days/7),2)}return compareByDay(firstMonday,janFirst)===0?"01":"00"},"%y":function(date){return(date.tm_year+1900).toString().substring(2)},"%Y":function(date){return date.tm_year+1900},"%z":function(date){var off=date.tm_gmtoff;var ahead=off>=0;off=Math.abs(off)/60;off=off/60*100+off%60;return(ahead?"+":"-")+String("0000"+off).slice(-4)},"%Z":function(date){return date.tm_zone},"%%":function(){return"%"}};for(var rule in EXPANSION_RULES_2){if(pattern.indexOf(rule)>=0){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_2[rule](date))}}var bytes=intArrayFromString(pattern,false);if(bytes.length>maxsize){return 0}writeArrayToMemory(bytes,s);return bytes.length-1}function _system(command){___setErrNo(6);return-1}function _time(ptr){var ret=Date.now()/1e3|0;if(ptr){HEAP32[ptr>>2]=ret}return ret}FS.staticInit();if(ENVIRONMENT_HAS_NODE){var fs=require("fs");var NODEJS_PATH=require("path");NODEFS.staticInit()}Module["requestFullScreen"]=function Module_requestFullScreen(lockPointer,resizeCanvas,vrDevice){err("Module.requestFullScreen is deprecated. Please call Module.requestFullscreen instead.");Module["requestFullScreen"]=Module["requestFullscreen"];Browser.requestFullScreen(lockPointer,resizeCanvas,vrDevice)};Module["requestFullscreen"]=function Module_requestFullscreen(lockPointer,resizeCanvas,vrDevice){Browser.requestFullscreen(lockPointer,resizeCanvas,vrDevice)};Module["requestAnimationFrame"]=function Module_requestAnimationFrame(func){Browser.requestAnimationFrame(func)};Module["setCanvasSize"]=function Module_setCanvasSize(width,height,noUpdates){Browser.setCanvasSize(width,height,noUpdates)};Module["pauseMainLoop"]=function Module_pauseMainLoop(){Browser.mainLoop.pause()};Module["resumeMainLoop"]=function Module_resumeMainLoop(){Browser.mainLoop.resume()};Module["getUserMedia"]=function Module_getUserMedia(){Browser.getUserMedia()};Module["createContext"]=function Module_createContext(canvas,useWebGL,setInModule,webGLContextAttributes){return Browser.createContext(canvas,useWebGL,setInModule,webGLContextAttributes)};if(ENVIRONMENT_IS_NODE){_emscripten_get_now=function _emscripten_get_now_actual(){var t=process["hrtime"]();return t[0]*1e3+t[1]/1e6}}else if(typeof dateNow!=="undefined"){_emscripten_get_now=dateNow}else if(typeof performance==="object"&&performance&&typeof performance["now"]==="function"){_emscripten_get_now=function(){return performance["now"]()}}else{_emscripten_get_now=Date.now}var GLctx;GL.init();var ASSERTIONS=false;function intArrayFromString(stringy,dontAddNull,length){var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}function invoke_vii(index,a1,a2){var sp=stackSave();try{dynCall_vii(index,a1,a2)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}var asmGlobalArg={};var asmLibraryArg={"_SDL_GL_SetAttribute":_SDL_GL_SetAttribute,"_SDL_GL_SwapBuffers":_SDL_GL_SwapBuffers,"_SDL_GetModState":_SDL_GetModState,"_SDL_GetMouseState":_SDL_GetMouseState,"_SDL_GetTicks":_SDL_GetTicks,"_SDL_GetWindowSize":_SDL_GetWindowSize,"_SDL_Init":_SDL_Init,"_SDL_OpenAudio":_SDL_OpenAudio,"_SDL_PauseAudio":_SDL_PauseAudio,"_SDL_PollEvent":_SDL_PollEvent,"_SDL_SetVideoMode":_SDL_SetVideoMode,"_SDL_WM_ToggleFullScreen":_SDL_WM_ToggleFullScreen,"___buildEnvironment":___buildEnvironment,"___clock_gettime":___clock_gettime,"___cxa_allocate_exception":___cxa_allocate_exception,"___cxa_throw":___cxa_throw,"___gxx_personality_v0":___gxx_personality_v0,"___lock":___lock,"___map_file":___map_file,"___setErrNo":___setErrNo,"___syscall10":___syscall10,"___syscall196":___syscall196,"___syscall221":___syscall221,"___syscall330":___syscall330,"___syscall38":___syscall38,"___syscall40":___syscall40,"___syscall5":___syscall5,"___syscall54":___syscall54,"___syscall63":___syscall63,"___syscall91":___syscall91,"___unlock":___unlock,"___wasi_fd_close":___wasi_fd_close,"___wasi_fd_read":___wasi_fd_read,"___wasi_fd_seek":___wasi_fd_seek,"___wasi_fd_write":___wasi_fd_write,"__addDays":__addDays,"__arraySum":__arraySum,"__computeUnpackAlignedImageSize":__computeUnpackAlignedImageSize,"__emscripten_syscall_munmap":__emscripten_syscall_munmap,"__glGenObject":__glGenObject,"__isLeapYear":__isLeapYear,"__memory_base":1024,"__table_base":0,"_abort":_abort,"_clock":_clock,"_clock_gettime":_clock_gettime,"_difftime":_difftime,"_emscripten_asm_const_i":_emscripten_asm_const_i,"_emscripten_asm_const_ii":_emscripten_asm_const_ii,"_emscripten_asm_const_iiii":_emscripten_asm_const_iiii,"_emscripten_async_wget2":_emscripten_async_wget2,"_emscripten_get_heap_size":_emscripten_get_heap_size,"_emscripten_get_now":_emscripten_get_now,"_emscripten_get_now_is_monotonic":_emscripten_get_now_is_monotonic,"_emscripten_memcpy_big":_emscripten_memcpy_big,"_emscripten_resize_heap":_emscripten_resize_heap,"_emscripten_set_main_loop":_emscripten_set_main_loop,"_emscripten_set_main_loop_timing":_emscripten_set_main_loop_timing,"_exit":_exit,"_fd_close":_fd_close,"_fd_read":_fd_read,"_fd_seek":_fd_seek,"_fd_write":_fd_write,"_getenv":_getenv,"_glActiveTexture":_glActiveTexture,"_glAttachShader":_glAttachShader,"_glBindBuffer":_glBindBuffer,"_glBindFramebuffer":_glBindFramebuffer,"_glBindRenderbuffer":_glBindRenderbuffer,"_glBindTexture":_glBindTexture,"_glBlendColor":_glBlendColor,"_glBlendEquation":_glBlendEquation,"_glBlendEquationSeparate":_glBlendEquationSeparate,"_glBlendFunc":_glBlendFunc,"_glBlendFuncSeparate":_glBlendFuncSeparate,"_glBufferData":_glBufferData,"_glBufferSubData":_glBufferSubData,"_glCheckFramebufferStatus":_glCheckFramebufferStatus,"_glClear":_glClear,"_glClearColor":_glClearColor,"_glClearStencil":_glClearStencil,"_glColorMask":_glColorMask,"_glCompileShader":_glCompileShader,"_glCreateProgram":_glCreateProgram,"_glCreateShader":_glCreateShader,"_glCullFace":_glCullFace,"_glDeleteBuffers":_glDeleteBuffers,"_glDeleteFramebuffers":_glDeleteFramebuffers,"_glDeleteProgram":_glDeleteProgram,"_glDeleteRenderbuffers":_glDeleteRenderbuffers,"_glDeleteShader":_glDeleteShader,"_glDeleteTextures":_glDeleteTextures,"_glDepthFunc":_glDepthFunc,"_glDepthMask":_glDepthMask,"_glDetachShader":_glDetachShader,"_glDisable":_glDisable,"_glDisableVertexAttribArray":_glDisableVertexAttribArray,"_glDrawArrays":_glDrawArrays,"_glDrawElements":_glDrawElements,"_glEnable":_glEnable,"_glEnableVertexAttribArray":_glEnableVertexAttribArray,"_glFramebufferRenderbuffer":_glFramebufferRenderbuffer,"_glFramebufferTexture2D":_glFramebufferTexture2D,"_glFrontFace":_glFrontFace,"_glGenBuffers":_glGenBuffers,"_glGenFramebuffers":_glGenFramebuffers,"_glGenRenderbuffers":_glGenRenderbuffers,"_glGenTextures":_glGenTextures,"_glGenerateMipmap":_glGenerateMipmap,"_glGetActiveAttrib":_glGetActiveAttrib,"_glGetActiveUniform":_glGetActiveUniform,"_glGetAttribLocation":_glGetAttribLocation,"_glGetError":_glGetError,"_glGetIntegerv":_glGetIntegerv,"_glGetProgramInfoLog":_glGetProgramInfoLog,"_glGetProgramiv":_glGetProgramiv,"_glGetShaderInfoLog":_glGetShaderInfoLog,"_glGetShaderiv":_glGetShaderiv,"_glGetUniformLocation":_glGetUniformLocation,"_glHint":_glHint,"_glLinkProgram":_glLinkProgram,"_glPixelStorei":_glPixelStorei,"_glReadPixels":_glReadPixels,"_glRenderbufferStorage":_glRenderbufferStorage,"_glScissor":_glScissor,"_glShaderSource":_glShaderSource,"_glStencilFuncSeparate":_glStencilFuncSeparate,"_glStencilMask":_glStencilMask,"_glStencilOpSeparate":_glStencilOpSeparate,"_glTexImage2D":_glTexImage2D,"_glTexParameteri":_glTexParameteri,"_glTexSubImage2D":_glTexSubImage2D,"_glUniform1fv":_glUniform1fv,"_glUniform1iv":_glUniform1iv,"_glUniform2fv":_glUniform2fv,"_glUniform3fv":_glUniform3fv,"_glUniform4fv":_glUniform4fv,"_glUniformMatrix2fv":_glUniformMatrix2fv,"_glUniformMatrix3fv":_glUniformMatrix3fv,"_glUniformMatrix4fv":_glUniformMatrix4fv,"_glUseProgram":_glUseProgram,"_glValidateProgram":_glValidateProgram,"_glVertexAttribPointer":_glVertexAttribPointer,"_glViewport":_glViewport,"_gmtime":_gmtime,"_gmtime_r":_gmtime_r,"_llvm_log10_f32":_llvm_log10_f32,"_llvm_log10_f64":_llvm_log10_f64,"_localtime":_localtime,"_localtime_r":_localtime_r,"_longjmp":_longjmp,"_mktime":_mktime,"_strftime":_strftime,"_system":_system,"_time":_time,"_tzset":_tzset,"abort":abort,"abortOnCannotGrowMemory":abortOnCannotGrowMemory,"demangle":demangle,"demangleAll":demangleAll,"emscriptenWebGLGet":emscriptenWebGLGet,"emscriptenWebGLGetTexPixelData":emscriptenWebGLGetTexPixelData,"emscripten_realloc_buffer":emscripten_realloc_buffer,"getTempRet0":getTempRet0,"invoke_vii":invoke_vii,"jsStackTrace":jsStackTrace,"memory":wasmMemory,"setTempRet0":setTempRet0,"stackTrace":stackTrace,"table":wasmTable,"tempDoublePtr":tempDoublePtr};var asm=Module["asm"](asmGlobalArg,asmLibraryArg,buffer);Module["asm"]=asm;var __ZSt18uncaught_exceptionv=Module["__ZSt18uncaught_exceptionv"]=function(){return Module["asm"]["__ZSt18uncaught_exceptionv"].apply(null,arguments)};var ___cxa_can_catch=Module["___cxa_can_catch"]=function(){return Module["asm"]["___cxa_can_catch"].apply(null,arguments)};var ___cxa_is_pointer_type=Module["___cxa_is_pointer_type"]=function(){return Module["asm"]["___cxa_is_pointer_type"].apply(null,arguments)};var ___errno_location=Module["___errno_location"]=function(){return Module["asm"]["___errno_location"].apply(null,arguments)};var __get_daylight=Module["__get_daylight"]=function(){return Module["asm"]["__get_daylight"].apply(null,arguments)};var __get_environ=Module["__get_environ"]=function(){return Module["asm"]["__get_environ"].apply(null,arguments)};var __get_timezone=Module["__get_timezone"]=function(){return Module["asm"]["__get_timezone"].apply(null,arguments)};var __get_tzname=Module["__get_tzname"]=function(){return Module["asm"]["__get_tzname"].apply(null,arguments)};var _am_emscripten_pause=Module["_am_emscripten_pause"]=function(){return Module["asm"]["_am_emscripten_pause"].apply(null,arguments)};var _am_emscripten_resize=Module["_am_emscripten_resize"]=function(){return Module["asm"]["_am_emscripten_resize"].apply(null,arguments)};var _am_emscripten_resume=Module["_am_emscripten_resume"]=function(){return Module["asm"]["_am_emscripten_resume"].apply(null,arguments)};var _am_emscripten_run=Module["_am_emscripten_run"]=function(){return Module["asm"]["_am_emscripten_run"].apply(null,arguments)};var _am_emscripten_run_waiting=Module["_am_emscripten_run_waiting"]=function(){return Module["asm"]["_am_emscripten_run_waiting"].apply(null,arguments)};var _emscripten_get_sbrk_ptr=Module["_emscripten_get_sbrk_ptr"]=function(){return Module["asm"]["_emscripten_get_sbrk_ptr"].apply(null,arguments)};var _emscripten_replace_memory=Module["_emscripten_replace_memory"]=function(){return Module["asm"]["_emscripten_replace_memory"].apply(null,arguments)};var _free=Module["_free"]=function(){return Module["asm"]["_free"].apply(null,arguments)};var _llvm_bswap_i32=Module["_llvm_bswap_i32"]=function(){return Module["asm"]["_llvm_bswap_i32"].apply(null,arguments)};var _llvm_maxnum_f64=Module["_llvm_maxnum_f64"]=function(){return Module["asm"]["_llvm_maxnum_f64"].apply(null,arguments)};var _llvm_minnum_f64=Module["_llvm_minnum_f64"]=function(){return Module["asm"]["_llvm_minnum_f64"].apply(null,arguments)};var _main=Module["_main"]=function(){return Module["asm"]["_main"].apply(null,arguments)};var _malloc=Module["_malloc"]=function(){return Module["asm"]["_malloc"].apply(null,arguments)};var _memcpy=Module["_memcpy"]=function(){return Module["asm"]["_memcpy"].apply(null,arguments)};var _memmove=Module["_memmove"]=function(){return Module["asm"]["_memmove"].apply(null,arguments)};var _memset=Module["_memset"]=function(){return Module["asm"]["_memset"].apply(null,arguments)};var _realloc=Module["_realloc"]=function(){return Module["asm"]["_realloc"].apply(null,arguments)};var _saveSetjmp=Module["_saveSetjmp"]=function(){return Module["asm"]["_saveSetjmp"].apply(null,arguments)};var _setThrew=Module["_setThrew"]=function(){return Module["asm"]["_setThrew"].apply(null,arguments)};var _testSetjmp=Module["_testSetjmp"]=function(){return Module["asm"]["_testSetjmp"].apply(null,arguments)};var establishStackSpace=Module["establishStackSpace"]=function(){return Module["asm"]["establishStackSpace"].apply(null,arguments)};var globalCtors=Module["globalCtors"]=function(){return Module["asm"]["globalCtors"].apply(null,arguments)};var stackAlloc=Module["stackAlloc"]=function(){return Module["asm"]["stackAlloc"].apply(null,arguments)};var stackRestore=Module["stackRestore"]=function(){return Module["asm"]["stackRestore"].apply(null,arguments)};var stackSave=Module["stackSave"]=function(){return Module["asm"]["stackSave"].apply(null,arguments)};var dynCall_di=Module["dynCall_di"]=function(){return Module["asm"]["dynCall_di"].apply(null,arguments)};var dynCall_ii=Module["dynCall_ii"]=function(){return Module["asm"]["dynCall_ii"].apply(null,arguments)};var dynCall_iidiiii=Module["dynCall_iidiiii"]=function(){return Module["asm"]["dynCall_iidiiii"].apply(null,arguments)};var dynCall_iii=Module["dynCall_iii"]=function(){return Module["asm"]["dynCall_iii"].apply(null,arguments)};var dynCall_iiii=Module["dynCall_iiii"]=function(){return Module["asm"]["dynCall_iiii"].apply(null,arguments)};var dynCall_iiiii=Module["dynCall_iiiii"]=function(){return Module["asm"]["dynCall_iiiii"].apply(null,arguments)};var dynCall_iiiiii=Module["dynCall_iiiiii"]=function(){return Module["asm"]["dynCall_iiiiii"].apply(null,arguments)};var dynCall_iijii=Module["dynCall_iijii"]=function(){return Module["asm"]["dynCall_iijii"].apply(null,arguments)};var dynCall_jiji=Module["dynCall_jiji"]=function(){return Module["asm"]["dynCall_jiji"].apply(null,arguments)};var dynCall_v=Module["dynCall_v"]=function(){return Module["asm"]["dynCall_v"].apply(null,arguments)};var dynCall_vi=Module["dynCall_vi"]=function(){return Module["asm"]["dynCall_vi"].apply(null,arguments)};var dynCall_vii=Module["dynCall_vii"]=function(){return Module["asm"]["dynCall_vii"].apply(null,arguments)};var dynCall_viii=Module["dynCall_viii"]=function(){return Module["asm"]["dynCall_viii"].apply(null,arguments)};var dynCall_viiii=Module["dynCall_viiii"]=function(){return Module["asm"]["dynCall_viiii"].apply(null,arguments)};var dynCall_viiiii=Module["dynCall_viiiii"]=function(){return Module["asm"]["dynCall_viiiii"].apply(null,arguments)};var dynCall_viiiiii=Module["dynCall_viiiiii"]=function(){return Module["asm"]["dynCall_viiiiii"].apply(null,arguments)};Module["asm"]=asm;Module["ccall"]=ccall;Module["UTF8ToString"]=UTF8ToString;Module["stringToUTF8"]=stringToUTF8;Module["lengthBytesUTF8"]=lengthBytesUTF8;var calledRun;function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}var calledMain=false;dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function callMain(args){args=args||[];var argc=args.length+1;var argv=stackAlloc((argc+1)*4);HEAP32[argv>>2]=allocateUTF8OnStack(thisProgram);for(var i=1;i<argc;i++){HEAP32[(argv>>2)+i]=allocateUTF8OnStack(args[i-1])}HEAP32[(argv>>2)+argc]=0;try{var ret=Module["_main"](argc,argv);exit(ret,true)}catch(e){if(e instanceof ExitStatus){return}else if(e=="SimulateInfiniteLoop"){noExitRuntime=true;return}else{var toLog=e;if(e&&typeof e==="object"&&e.stack){toLog=[e,e.stack]}err("exception thrown: "+toLog);quit_(1,e)}}finally{calledMain=true}}function run(args){args=args||arguments_;if(runDependencies>0){return}preRun();if(runDependencies>0)return;function doRun(){if(calledRun)return;calledRun=true;if(ABORT)return;initRuntime();preMain();if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();if(shouldRunNow)callMain(args);postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}Module["run"]=run;function exit(status,implicit){if(implicit&&noExitRuntime&&status===0){return}if(noExitRuntime){}else{ABORT=true;EXITSTATUS=status;exitRuntime();if(Module["onExit"])Module["onExit"](status)}quit_(status,new ExitStatus(status))}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}var shouldRunNow=true;if(Module["noInitialRun"])shouldRunNow=false;noExitRuntime=true;run(); diff --git a/ggj26/amulet.wasm b/ggj26/amulet.wasm Binary files differnew file mode 100644 index 0000000..9105c0f --- /dev/null +++ b/ggj26/amulet.wasm diff --git a/ggj26/amulet_license.txt b/ggj26/amulet_license.txt new file mode 100644 index 0000000..fb5fccb --- /dev/null +++ b/ggj26/amulet_license.txt @@ -0,0 +1,695 @@ +This work uses the Amulet engine (http://www.amulet.xyz). + +THE FOLLOWING LICENSE APPLIES ONLY TO THE +AMULET ENGINE EXECUTABLE DISTRIBUTED WITH THIS WORK! +IT DOES NOT APPLY TO ANY OTHER FILES, INCLUDING +ANY SCRIPTS, IMAGES OR AUDIO ASSETS USED IN THE WORK. + +Amulet license +============== + +Copyright (C) 2014-2020 Ian MacLarty + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +(end of Amulet license) + +############################################################################### + +The following is a list of third party software used by Amulet and the +relevant licenses. + +############################################################################### + +Lua (http://lua.org) +==================== + +Lua License +----------- + +Lua is licensed under the terms of the MIT license reproduced below. +This means that Lua is free software and can be used for both academic +and commercial purposes at absolutely no cost. + +For details and rationale, see http://www.lua.org/license.html . + +=============================================================================== + +Copyright (C) 1994-2012 Lua.org, PUC-Rio. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +=============================================================================== + +(end of COPYRIGHT) + +############################################################################### + +LuaJIT (http://luajit.org/) +=========================== + +LuaJIT -- a Just-In-Time Compiler for Lua. http://luajit.org/ + +Copyright (C) 2005-2015 Mike Pall. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +[ MIT license: http://www.opensource.org/licenses/mit-license.php ] + +=============================================================================== +[ LuaJIT includes code from Lua 5.1/5.2, which has this license statement: ] + +Copyright (C) 1994-2012 Lua.org, PUC-Rio. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +=============================================================================== +[ LuaJIT includes code from dlmalloc, which has this license statement: ] + +This is a version (aka dlmalloc) of malloc/free/realloc written by +Doug Lea and released to the public domain, as explained at +http://creativecommons.org/licenses/publicdomain + +=============================================================================== + +############################################################################### + +SDL2 (https://www.libsdl.org) +============================= + +Please distribute this file with the SDL runtime environment: + +The Simple DirectMedia Layer (SDL for short) is a cross-platform library +designed to make it easy to write multi-media software, such as games and +emulators. + +The Simple DirectMedia Layer library source code is available from: +http://www.libsdl.org/ + +This library is distributed under the terms of the zlib license: +http://www.zlib.net/zlib_license.html + +---------------------------------- + +Simple DirectMedia Layer +Copyright (C) 1997-2014 Sam Lantinga <slouken@libsdl.org> + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + +############################################################################### + +Sean's Tool Box (https://github.com/nothings/stb) +================================================= + +Public Domain. + +############################################################################### + +KissFFT (http://sourceforge.net/projects/kissfft/) +================================================== + +Copyright (c) 2003-2010 Mark Borgerding + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +############################################################################### + +Blink (http://www.chromium.org/blink) +===================================== + +Amulet uses a few modified pieces of code from Blink's web audio +implementation. The applicable license is: + +Copyright (C) 2010 Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +############################################################################### + +ANGLE (https://code.google.com/p/angleproject/) +=============================================== + +Copyright (C) 2002-2013 The ANGLE Project Authors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + Neither the name of TransGaming Inc., Google Inc., 3DLabs Inc. + Ltd., nor the names of their contributors may be used to endorse + or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +############################################################################### + +FreeType (http://www.freetype.org/) +=================================== + + The FreeType Project LICENSE + ---------------------------- + + 2006-Jan-27 + + Copyright 1996-2002, 2006 by + David Turner, Robert Wilhelm, and Werner Lemberg + + + +Introduction +============ + + The FreeType Project is distributed in several archive packages; + some of them may contain, in addition to the FreeType font engine, + various tools and contributions which rely on, or relate to, the + FreeType Project. + + This license applies to all files found in such packages, and + which do not fall under their own explicit license. The license + affects thus the FreeType font engine, the test programs, + documentation and makefiles, at the very least. + + This license was inspired by the BSD, Artistic, and IJG + (Independent JPEG Group) licenses, which all encourage inclusion + and use of free software in commercial and freeware products + alike. As a consequence, its main points are that: + + o We don't promise that this software works. However, we will be + interested in any kind of bug reports. (`as is' distribution) + + o You can use this software for whatever you want, in parts or + full form, without having to pay us. (`royalty-free' usage) + + o You may not pretend that you wrote this software. If you use + it, or only parts of it, in a program, you must acknowledge + somewhere in your documentation that you have used the + FreeType code. (`credits') + + We specifically permit and encourage the inclusion of this + software, with or without modifications, in commercial products. + We disclaim all warranties covering The FreeType Project and + assume no liability related to The FreeType Project. + + + Finally, many people asked us for a preferred form for a + credit/disclaimer to use in compliance with this license. We thus + encourage you to use the following text: + + """ + Portions of this software are copyright © <year> The FreeType + Project (www.freetype.org). All rights reserved. + """ + + Please replace <year> with the value from the FreeType version you + actually use. + + +Legal Terms +=========== + +0. Definitions +-------------- + + Throughout this license, the terms `package', `FreeType Project', + and `FreeType archive' refer to the set of files originally + distributed by the authors (David Turner, Robert Wilhelm, and + Werner Lemberg) as the `FreeType Project', be they named as alpha, + beta or final release. + + `You' refers to the licensee, or person using the project, where + `using' is a generic term including compiling the project's source + code as well as linking it to form a `program' or `executable'. + This program is referred to as `a program using the FreeType + engine'. + + This license applies to all files distributed in the original + FreeType Project, including all source code, binaries and + documentation, unless otherwise stated in the file in its + original, unmodified form as distributed in the original archive. + If you are unsure whether or not a particular file is covered by + this license, you must contact us to verify this. + + The FreeType Project is copyright (C) 1996-2000 by David Turner, + Robert Wilhelm, and Werner Lemberg. All rights reserved except as + specified below. + +1. No Warranty +-------------- + + THE FREETYPE PROJECT IS PROVIDED `AS IS' WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. IN NO EVENT WILL ANY OF THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY DAMAGES CAUSED BY THE USE OR THE INABILITY TO + USE, OF THE FREETYPE PROJECT. + +2. Redistribution +----------------- + + This license grants a worldwide, royalty-free, perpetual and + irrevocable right and license to use, execute, perform, compile, + display, copy, create derivative works of, distribute and + sublicense the FreeType Project (in both source and object code + forms) and derivative works thereof for any purpose; and to + authorize others to exercise some or all of the rights granted + herein, subject to the following conditions: + + o Redistribution of source code must retain this license file + (`FTL.TXT') unaltered; any additions, deletions or changes to + the original files must be clearly indicated in accompanying + documentation. The copyright notices of the unaltered, + original files must be preserved in all copies of source + files. + + o Redistribution in binary form must provide a disclaimer that + states that the software is based in part of the work of the + FreeType Team, in the distribution documentation. We also + encourage you to put an URL to the FreeType web page in your + documentation, though this isn't mandatory. + + These conditions apply to any software derived from or based on + the FreeType Project, not just the unmodified files. If you use + our work, you must acknowledge us. However, no fee need be paid + to us. + +3. Advertising +-------------- + + Neither the FreeType authors and contributors nor you shall use + the name of the other for commercial, advertising, or promotional + purposes without specific prior written permission. + + We suggest, but do not require, that you use one or more of the + following phrases to refer to this software in your documentation + or advertising materials: `FreeType Project', `FreeType Engine', + `FreeType library', or `FreeType Distribution'. + + As you have not signed this license, you are not required to + accept it. However, as the FreeType Project is copyrighted + material, only this license, or another one contracted with the + authors, grants you the right to use, distribute, and modify it. + Therefore, by using, distributing, or modifying the FreeType + Project, you indicate that you understand and accept all the terms + of this license. + +4. Contacts +----------- + + There are two mailing lists related to FreeType: + + o freetype@nongnu.org + + Discusses general use and applications of FreeType, as well as + future and wanted additions to the library and distribution. + If you are looking for support, start in this list if you + haven't found anything to help you in the documentation. + + o freetype-devel@nongnu.org + + Discusses bugs, as well as engine internals, design issues, + specific licenses, porting, etc. + + Our home page can be found at + + http://www.freetype.org + + +--- end of FTL.TXT --- + +############################################################################### + +GLM (https://github.com/g-truc/glm) +=================================== + +================================================================================ +OpenGL Mathematics (GLM) +-------------------------------------------------------------------------------- +GLM can be distributed and/or modified under the terms of either +a) The Happy Bunny License, or b) the MIT License. + +================================================================================ +The Happy Bunny License (Modified MIT License) +-------------------------------------------------------------------------------- +Copyright (c) 2005 - 2015 G-Truc Creation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +Restrictions: By making use of the Software for military purposes, you choose +to make a Bunny unhappy. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +================================================================================ +The MIT License +-------------------------------------------------------------------------------- +Copyright (c) 2005 - 2015 G-Truc Creation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +############################################################################### + +Emscripten (http://emscripten.org) +================================== + +Copyright (c) 2010-2014 Emscripten authors, see AUTHORS file. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +############################################################################### + +OpenCV (http://opencv.org/) +=========================== + +Amulet uses some code from OpenCV for video capture on Macs. + +By downloading, copying, installing or using the software you agree to this license. +If you do not agree to this license, do not download, install, +copy or use the software. + + + License Agreement + For Open Source Computer Vision Library + (3-clause BSD License) + +Copyright (C) 2000-2015, Intel Corporation, all rights reserved. +Copyright (C) 2009-2011, Willow Garage Inc., all rights reserved. +Copyright (C) 2009-2015, NVIDIA Corporation, all rights reserved. +Copyright (C) 2010-2013, Advanced Micro Devices, Inc., all rights reserved. +Copyright (C) 2015, OpenCV Foundation, all rights reserved. +Copyright (C) 2015, Itseez Inc., all rights reserved. +Third party copyrights are property of their respective owners. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the names of the copyright holders nor the names of the contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +This software is provided by the copyright holders and contributors "as is" and +any express or implied warranties, including, but not limited to, the implied +warranties of merchantability and fitness for a particular purpose are disclaimed. +In no event shall copyright holders or contributors be liable for any direct, +indirect, incidental, special, exemplary, or consequential damages +(including, but not limited to, procurement of substitute goods or services; +loss of use, data, or profits; or business interruption) however caused +and on any theory of liability, whether in contract, strict liability, +or tort (including negligence or otherwise) arising in any way out of +the use of this software, even if advised of the possibility of such damage. + +############################################################################### + +SimpleGlob (https://github.com/brofield/simpleopt) +================================================== + +Copyright (c) 2006-2013, Brodie Thiesfield + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +############################################################################### + +TinyMT (https://github.com/MersenneTwister-Lab/TinyMT) +====================================================== + +Copyright (c) 2011, 2013 Mutsuo Saito, Makoto Matsumoto, +Hiroshima University and The University of Tokyo. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of the Hiroshima University nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +############################################################################### + +sfxr (http://www.drpetter.se/project_sfxr.html) +=============================================== + +Copyright (c) 2007 Tomas Pettersson <drpetter@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +############################################################################### + +glsl-optimizer (https://github.com/aras-p/glsl-optimizer) +========================================================= + +GLSL Optimizer is licensed according to the terms of the MIT license: + +Copyright (C) 1999-2007 Brian Paul All Rights Reserved. +Copyright (C) 2010-2013 Unity Technologies All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +############################################################################### diff --git a/ggj26/data.pak b/ggj26/data.pak Binary files differnew file mode 100644 index 0000000..58e4a07 --- /dev/null +++ b/ggj26/data.pak diff --git a/ggj26/index.html b/ggj26/index.html new file mode 100644 index 0000000..957ad55 --- /dev/null +++ b/ggj26/index.html @@ -0,0 +1,405 @@ +<!doctype html> +<!-- + This work uses Amulet (http://www.amulet.xyz). + For the Amulet license, please see amulet_license.txt + which should be in the same directory as this file. +--> +<html lang="en-us"> + <head> + <meta charset="utf-8"> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + <title id="title">Amulet</title> + <style type="text/css"> +html, body { + height: 100%; + margin: 0; + padding: 0; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 15px; + overflow-x: hidden; + overflow-y: hidden; + background-color: #000; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAAECAIAAAArjXluAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AEOFx8uD8pZBQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAfSURBVAjXY1RUVGRgYGBiYGBgYGBgZmFh+fXrF5QHACWWA2ZdfnF2AAAAAElFTkSuQmCC'); +} +#status-overlay { + margin: 0; + padding: 0; + width: 100%; + height: 100%; + background-color: #000; + z-index: 1000; +} +#status-positioner { + position: absolute; + left: 50%; + bottom: 20%; +} +#status-box { + width: 400px; + height: 16px; + margin-left: -200px; + border: 2px solid #fff; + padding: 2px; +} +#status-bar { + width: 1%; + height: 100%; + margin: 0; + padding: 0; + background-color: #fff; +} +#status-bar.error { + background-color: #f00; +} +#status-box.error { + border: 2px solid #f00; +} +#canvas { + margin: 0; + padding: 0; + width: 100%; + height: 100%; +} +#video { + display: none; +} +#status { + color: #ff0000; + margin: 0 0 10px -200px; + font-size: 16px; + font-weight: bold; +} +.undecorated-container { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} +.decorated-container { + position: absolute; + top: 14px; + left: 14px; + right: 14px; + bottom: 24px; +} +#footer { + display: none; + position: absolute; + left: 0; + right: 0; + bottom: 0; + height: 20px; + text-align: center; + font-size: 15px; + color: #e2dedf; + padding: 4px 0 0 0; +} +#footer a, +#footer a:visited { + color: #ff00ff; +} +#hacklink, +#hacklink:visited { + color: #29a3c8 !important; +} + </style> + </head> + <body> + <div id="container" class="undecorated-container"> + <video id="video" autoplay></video> + <div id="status-overlay"> + <div id="status-positioner"> + <div id="status"></div> + <div id="status-box"> + <div id="status-bar"></div> + </div> + </div> + </div> + <canvas id="canvas" draggable="false" width="100" height="100"></canvas> + </div> + <div id="footer">Made with <a target="_blank" href="http://www.amulet.xyz">Amulet</a> | <a id="hacklink" href="" target="_blank">Hack</a></div> + <script type="text/javascript"> +var status_timer; +var is_in_editor = typeof in_editor !== "undefined"; +if (typeof log_output === "undefined") { + log_output = function(msg) {}; +} + +function getParameterByName(name) { + name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]"); + var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), + results = regex.exec(location.search); + return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); +} + +var pointerLockElementName = null; +if ('pointerLockElement' in document) { + pointerLockElementName = 'pointerLockElement'; +} else if ('mozPointerLockElement' in document) { + pointerLockElementName = 'mozPointerLockElement'; +} else if ('webkitPointerLockElement' in document) { + pointerLockElementName = 'webkitPointerLockElement'; +} + +var havePointerLock = pointerLockElementName != null; + +navigator.getUserMedia = navigator.getUserMedia || + navigator.webkitGetUserMedia || + navigator.mozGetUserMedia || + navigator.msGetUserMedia; + +window.amulet = {}; + +function run(script) { + var request = new XMLHttpRequest(); + request.open("GET", script, true); + request.onload = function() { + if (this.status >= 200 && this.status < 400) { + var data = this.responseText; + load(data); + } else { + console.log("error loading script "+script+": "+this.statusText); + } + }; + request.onerror = function() { + console.log("error loading script "+script); + }; + request.send(); +} + +function init_canvas() { + var canvas = document.getElementById("canvas"); + window.amulet.have_pointer_lock = function() { + return canvas === document[pointerLockElementName]; + }; + // Pop up an alert when webgl context is lost. + // TODO: handle webgl context restore event. + // See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2 + canvas.addEventListener("webglcontextlost", function(e) { + alert('WebGL context lost. You will need to reload the page or, failing that, restart your browser).'); + e.preventDefault(); + }, false); + canvas.addEventListener("contextmenu", function(e) { + e.preventDefault(); + return false; + }); + if (havePointerLock) { + canvas.addEventListener("click", function() { + if (window.amulet.pointer_lock_requested) { + canvas.requestPointerLock = + canvas.requestPointerLock || + canvas.mozRequestPointerLock || + canvas.webkitRequestPointerLock; + canvas.requestPointerLock(); + } + }); + document.exitPointerLock = document.exitPointerLock || document.mozExitPointerLock || document.webkitExitPointerLock; + } +} + +function set_error_text(text) { + document.getElementById("status").innerHTML = text; + document.getElementById("status-box").classList.add("error"); + document.getElementById("status-bar").classList.add("error"); +} + +function set_progress(perc) { + document.getElementById("status-bar").style.width = perc + "%"; +} + +var perc = 1; +var delta = 0.65; +var phase1_limit = 33; +function phase1() { + perc = Math.min(phase1_limit, perc + delta); + delta = delta * 0.98; + set_progress(perc); + //console.log(perc); +} +function phase2() { + var prog = window.amulet.load_progress || 0; + set_progress(prog * ((100-phase1_limit)/100) + phase1_limit); +} +var phase = phase1; +function animate_loading_status() { + if (phase === phase1 && document.readyState == "complete") { + phase = phase2; + set_progress(phase1_limit); + } + phase(); +} + +status_timer = setInterval(animate_loading_status, 100); +function clear_status_timer() { + clearInterval(status_timer); + status_timer = null; +} + +function remove_status_overlay() { + document.getElementById("status-overlay").style.display = 'none'; + clear_status_timer(); +} + +var pending_load = null; +var started = false; + +function load(script) { + if (!started) { + pending_load = script; + } else { + Module.ccall('am_emscripten_run', null, ['string'], [script]); + } +} + +function load_gist(id) { + console.log("loading gist " + id); + var url = 'https://api.github.com/gists/'+id; + var request = new XMLHttpRequest(); + request.open("GET", url, true); + request.onload = function() { + if (this.status >= 200 && this.status < 400) { + console.log("received gist"); + var data = this.responseText; + var res = JSON.parse(data); + var code = res.files["main.lua"].content; + load(code); + remove_status_overlay(); + } else { + console.log("error loading gist "+url+": "+this.statusText); + } + }; + request.onerror = function() { + console.log("error loading gist "+url); + }; + request.send(); +} + +window.amulet.ready = function() { + var gist = getParameterByName("gist"); + try { + init_canvas(); + if (!gist || is_in_editor) { + remove_status_overlay(); + } + } catch (e) { + clear_status_timer(); + set_error_text('Something went wrong.<br>See JavaScript console for details.'); + throw e; + } + started = true; + if (gist && !is_in_editor) { + document.getElementById("container").classList.remove("undecorated-container"); + document.getElementById("container").classList.add("decorated-container"); + document.getElementById("footer").style.display = "block"; + document.getElementById("hacklink").href = "http://www.amulet.xyz/editor.html?gist=" + gist; + pending_load = null; + load_gist(gist); + } else if (pending_load) { + console.log("loading: " + pending_load); + load(pending_load); + pending_load = null; + } + setup_audio_resume(); +} + +window.amulet.error = function(msg) { + set_error_text(msg); +} + +function set_visibility_handler() { + var hidden, visibilityChange; + if (typeof document.hidden !== "undefined") { + hidden = "hidden"; + visibilityChange = "visibilitychange"; + } else if (typeof document.mozHidden !== "undefined") { + hidden = "mozHidden"; + visibilityChange = "mozvisibilitychange"; + } else if (typeof document.msHidden !== "undefined") { + hidden = "msHidden"; + visibilityChange = "msvisibilitychange"; + } else if (typeof document.webkitHidden !== "undefined") { + hidden = "webkitHidden"; + visibilityChange = "webkitvisibilitychange"; + } + window.amulet.window_hidden = 0; + window.amulet.window_has_focus = 1; + document.addEventListener(visibilityChange, function(event) { + window.amulet.window_hidden = document[hidden] ? 1 : 0; + }, false); + window.addEventListener('blur', function(event) { + window.amulet.window_has_focus = 0; + }, false); + window.addEventListener('focus', function(event) { + window.amulet.window_has_focus = 1; + }, false); +} +set_visibility_handler(); + +function setup_audio_resume() { + // Modern browsers disable audio until the user interacts with the page. + // See: https://developers.google.com/web/updates/2017/09/autoplay-policy-changes#webaudio + window.addEventListener('click', enable_audio, { passive: true, once: true }); + window.addEventListener('keydown', enable_audio, { passive: true, once: true }); + window.addEventListener('touch', enable_audio, { passive: true, once: true }); +} +function enable_audio() { + SDL.audioContext.resume().then(() => { + console.log('Playback resumed successfully'); + }); +} + +function run_waiting() { + Module.ccall('am_emscripten_run_waiting', null, [], []); +} +function pause() { + Module.ccall('am_emscripten_pause', null, [], []); +} +function resume() { + Module.ccall('am_emscripten_resume', null, [], []); +} + +window.amulet.run_waiting = is_in_editor ? 1 : 0; +window.amulet.no_load_data = getParameterByName("gist") ? 1 : 0; + +//------------------------------------------------------------------------ + +var Module = { + preRun: [], + postRun: [], + print: function(text) { + console.log(text); + if (text == "ERROR") { + if (handle_error) handle_error(); + } else { + log_output(text); + } + }, + printErr: function(text) { + text = Array.prototype.slice.call(arguments).join(' '); + console.error(text); + log_output(text); + }, + canvas: document.getElementById("canvas"), + keyboardListeningElement: document.getElementById("player-panel") || document, + setStatus: function(text) { + console.log(text); + }, + totalDependencies: 0, + monitorRunDependencies: function(left) { + this.totalDependencies = Math.max(this.totalDependencies, left); + Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.'); + } +}; +Module.setStatus('Downloading...'); +window.onerror = function(event) { + // TODO: do not warn on ok events like simulating an infinite loop or exitStatus + set_error_text('Something went wrong.<br>See JavaScript console for details.'); + Module.setStatus = function(text) { + if (text) Module.printErr('[post-exception status] ' + text); + }; +}; + </script> + <script async type="text/javascript" src="amulet.js"></script> + </body> +</html> diff --git a/spec/ecs_spec.lua b/spec/ecs_spec.lua new file mode 100644 index 0000000..d757a0c --- /dev/null +++ b/spec/ecs_spec.lua @@ -0,0 +1,12 @@ + +describe("ecs module", function() + it("should require without errors",function() + require("ecs") + end) + it("should serialize correctly",function() + local ecs = require("ecs") + ent = ecs.Entity(nil,{ + + }) + end) +end) diff --git a/spec/log_spec.lua b/spec/log_spec.lua new file mode 100644 index 0000000..2120c17 --- /dev/null +++ b/spec/log_spec.lua @@ -0,0 +1,53 @@ +describe("log module", function() + before_each(function() + local succ, log= pcall(require,"log") + if succ then log.reset() end + end) + it("should load",function() + require("log") + end) + it("should ingest a log",function() + local log = require("log") + log.log("test",nil,"blah") + end) + it("should return ingested logs from levels",function() + local log = require("log") + local ret = {} + log.log("test",nil,"blah") + log.of_level("blah",function(msg) + table.insert(ret, msg) + end) + assert(#ret == 1) + assert(ret[1].message == "test") + end) + it("should return ingested logs from tags", function() + local log = require("log") + local ret = {} + log.info("one",{"one"}) + log.info("two",{"two"}) + log.info("one two",{"one","two"}) + log.of_tags({"one"},function(msg) + table.insert(ret,msg) + end) + assert(#ret == 2) + assert(ret[1].message == "one") + assert(ret[2].message == "one two") + end) + it("should allow observers to see the message stream", function() + local log = require("log") + local ret = {} + log.listen(function(chunk) + if chunk.level == "panic" then + table.insert(ret,chunk) + end + end) + log.info("one") + log.info("two") + assert(#ret == 0) + log.panic("three") + assert(#ret == 1) + log.info("four") + assert(#ret == 1) + assert(ret[1].message == "three") + end) +end) diff --git a/src/abilities.moon b/src/abilities.moon new file mode 100644 index 0000000..d2178fa --- /dev/null +++ b/src/abilities.moon @@ -0,0 +1,5 @@ +x = {} + +x["a pawn"] = "You may not reveal" + +x diff --git a/src/channel.moon b/src/channel.moon new file mode 100644 index 0000000..6f87d46 --- /dev/null +++ b/src/channel.moon @@ -0,0 +1,78 @@ + +-- Implements a channels, an abstraction for sending data + +class Channel + new: (settings) => + settings = settings or {} + --Every channel has at least a buffer, the messages to be read on recv() + @buffer = {} + for setting_name, setting_value in pairs settings + @[setting_name] = setting_value + poll: => + error("Channel must implement poll") + send: (message) => + error("Channel must implement message") + recv: => + error("Channel must implement recv") + +class SimpleChannel extends Channel + @time = 0 + new: (settings) => + super(settings) + poll: => + #@buffer > 0 + send: (message) => + table.insert(@buffer, message) + recv: => + table.remove(@buffer, 1) + +class FaultyChannel extends Channel + -- Mock channel for testing + @time = 0 + new: (settings) => + @to_deliver = {} + @avg_latency = 0 -- in ms + @latency_std = 0 + -- Latency can never be below 0, but can go up as much as it likes + @loss = 0.1 -- between 0 (never) and 1 (always) + super(settings) + @normal_at: (avg, std, n) -> + assert(avg and std and n, string.format("normal(avg, std, n) called with %q %q %q", tostring(avg), tostring(std), tostring(n))) + -- Normal curve probability at N + (1/ (math.sqrt(2*math.pi) * avg)) * math.exp(-(n - avg)^2 / (2 * (std^2))) + @normal: (avg, std) => + -- Box-Muller transform + bm = math.sqrt(-2 * math.log(math.random())) * math.cos(2 * math.pi * math.random()) + -- Box-Muller gives us std = e^-0.5 , avg = 0 + ((bm / math.exp(-1/2)) * std) + avg + poll: => + @pump! + #@buffer > 0 + send: (message) => + -- Do we deliver? + rng = math.random() + if @loss > rng + return + -- How long does it take? + -- Only uses the positive half of the normal distribution, double the standard deviation? + time = @@normal(@avg_latency, @latency_std * 2, math.random()) + if time < 0 then + time = 0 -- We can't deliver messages in the past + table.insert(@to_deliver, {message,@@time + time}) + recv: => + @pump! + table.remove(@buffer, 1) + pump: => + defrag = 1 + deliver_len = #@to_deliver + for k,tbl in ipairs(@to_deliver) + {m, t} = tbl + @to_deliver[defrag] = tbl + if @@time > t + table.insert(@buffer, m) + else + defrag = defrag + 1 + for i = defrag, deliver_len do + @to_deliver[i] = nil + +{:Channel, :SimpleChannel, :FaultyChannel} diff --git a/src/client.moon b/src/client.moon new file mode 100644 index 0000000..6228ff8 --- /dev/null +++ b/src/client.moon @@ -0,0 +1,197 @@ +-- Hub-and-spoke networking client +-- Connects to hub and provides router registration for message handling + +net = require "net" +log = require "log" +world = require "world" + +-- Register message types for hub->client communication + +net.register_message("to_client", { + required: { + target: "string" + message_type: "string" + } + optional: { + data: "table" + } +}) + +net.register_message("to_many_clients", { + required: { + targets: "table" + message_type: "string" + } + optional: { + data: "table" + } +}) + +net.register_message("broadcast", { + required: { + message_type: "string" + } + optional: { + data: "table" + } +}) + +class Client + new: (name) => + @name = name or "anonymous" + @peer = nil + @hub_connection = nil + @hub_id = nil + @connected = false + @routes = {} -- message_type -> handler function + @on_connect_callbacks = {} + @on_disconnect_callbacks = {} + @initialized = false + + initialize: => + if @initialized + return + @peer = net.Peer! + log.info("Client peer created: #{@peer.id}", {"client", "net"}) + @initialized = true + + connect_to_hub: (hub_id) => + if not @initialized + @initialize! + + @hub_id = hub_id + @hub_connection = @peer\connect(hub_id) + + -- Set up connection handlers + @hub_connection\on("open", -> + @connected = true + log.info("Connected to hub: #{hub_id}", {"client", "net"}) + + -- Send registration message using Connection:send(msgname, msg) + @hub_connection\send("Join", {name: @name}) + + -- Surface client connect/join to the browser for integration tests. + if am and am.eval_js and am.to_json + js = string.format("window._clientConnectedToHub = true; window._clientJoinPayload = %s;", am.to_json({name: @name})) + am.eval_js(js) + + -- Notify connection callbacks + for callback in *@on_connect_callbacks + callback! + ) + + @hub_connection\on("data", (msgname, data) -> + @handle_message(msgname, data) + ) + + @hub_connection\on("close", -> + @connected = false + log.info("Disconnected from hub", {"client", "net"}) + + -- Notify disconnect callbacks + for callback in *@on_disconnect_callbacks + callback! + ) + while not @connected + coroutine.yield! + + handle_message: (callback_id, message_data) => + log.info("Client handle_message callback_id=" .. tostring(callback_id) .. " message_data=" .. tostring(message_data), {"net", "client", "debug"}) + -- message_data is the array [message_type, data] sent by hub + if type(message_data) ~= "table" or #message_data < 1 + log.warn("Received invalid message format: " .. tostring(message_data), {"client", "net"}) + return + if type(message_data[1][1]) != "string" + log.warn("Received invalid mesage type: " .. tostring(message_data[1][1]), {"client","net"}) + + msg_type = message_data[1][1] + msg_data = message_data[1][2] or {} + + log.info("Message type: #{msg_type}", {"net", "client"}) + if msg_type == "Join" + log.error("Client saw Join message in handle_message; this should be hub-only", {"client", "net", "debug"}) + + if not msg_type or type(msg_type) ~= "string" + log.warn("Received message without valid type:" .. tostring(msg_type), {"client", "net"}) + return + + world.domain = "client" + if @routes[msg_type] + -- Route to registered handlers + callbacks = @routes[msg_type] + for _, callback in pairs(callbacks) + callback(@hub_id, msg_data) + else + log.warn("No handler for message type: " .. tostring(msg_type), {"client", "net"}) + msg_types = [key for key, _ in pairs(@routes)] + if #msg_types > 0 + log.warn("Registered message types: " .. table.concat(msg_types, ","), {"client", "net"}) + + -- Register a router for a specific message type + -- callback is a (server-id:string, data:tbl) -> nil + listen: (message_type, id, callback) => + assert(type(callback) == "function", "Listened with something that is not a function") + @routes[message_type] = @routes[message_type] or {} + id = id or #@routes[message_type] + 1 + @routes[message_type][id] = callback + log.info("Router registered for #{message_type}", {"client", "net"}) + id + + -- Unregister a router + defen: (message_type, id) => + if not @routes[message_type] or @routes[message_type][id] == nil + log.warn("Removing listener that doesn't exist: #{message_type}", {"client", "net"}) + return + @routes[message_type][id] = nil + log.info("Listener removed for #{message_type}", {"client", "net"}) + + -- Send message to hub + send: (message_type, data) => + if not @connected + log.error("Cannot send - not connected to hub", {"client", "net"}) + return false + + log.info("Client sending #{message_type}", {"net", "client"}) + @hub_connection\send(message_type, data or {}) + true + + on_connect: (callback) => + table.insert(@on_connect_callbacks, callback) + + on_disconnect: (callback) => + table.insert(@on_disconnect_callbacks, callback) + + -- Synchronus request/response for use in coroutines. + sync: (request, request_data, response) => + returned = nil + lid = @listen(response, nil, (peer, data) -> + returned = data + ) + @send(request, request_data) + tries = 1 + start = am.current_time! + while not returned and tries < 4 + log.info("Awaiting synchronus response to " .. request, {"net","client"}) + coroutine.yield! + if am.current_time! - start > 4 + log.info("Async response timeout, requesting again...",{"net","client"}) + @send(request, request_data) + start = am.current_time! + tries += 1 + if tries == 4 + error("Failed in sync request after 4 tries") + @defen(response, lid) + return returned + + is_connected: => + @connected + + disconnect: => + if @hub_connection + @hub_connection\close! + @connected = false + + pump: => + net.pump! + +{:Client} diff --git a/src/client/init.moon b/src/client/init.moon new file mode 100644 index 0000000..5aae780 --- /dev/null +++ b/src/client/init.moon @@ -0,0 +1,140 @@ + +net = require("net") +world = require("world") +ui = require("ui") +log = require("log") +ClientNetworkedComponent = require("ecs.client_networked") +GraphicsComponent = require("ecs.graphics") +PlayerGraphicComponent = require("ecs.player_graphic") +CharacterControllerComponent = require("ecs.char_controller") +PredictedComponent = require("ecs.predicted") +ScriptComponent = require("ecs.script") +player_movement = require("shared.player_movement") +game_menu = require("menu.game") + +ecs = require("ecs") +sprites =require("sprites") + +net.register_message("CreatePawn",{ + peerid: "string" +}) +x = {} + +net.register_message("Notify",{ + required: { + message: "string" + } +}) + +create_entity = (id, chunk) -> + log.info("Creating entity of type" .. tostring(chunk.data.type), {"ecs","net","client"}) + ent = nil + switch chunk.data.type + when "level" + --ent = ecs.Entity(id) + log.info("TODO: What do we refresh with level data?",{"ecs","net","client"}) + --nents = #world.level.entities + --net_comp = ClientNetworkedComponent("net",chunk.data) + --ent\add(net_comp,"net") + require(chunk.data.level_name).create(unpack(chunk.data.level_data)) + --assert(#world.level.entities == nents, "Entities created in level loading") + when "player" + ent = ecs.Entity(id) + log.info("Creating player",{"ecs","net","client","player"}) + net_comp = ClientNetworkedComponent("net",chunk.data) + ent\add(net_comp,"net") + graphic = GraphicsComponent("graphic",{graphic: sprites.player}) + ent\add(graphic,"graphic") + predicted = PredictedComponent("pred",{acc: {0,0,0}, vel: {0,0,0}, pos:{0,0,0}}, "net", player_movement) + ent\add(predicted,"pred") + -- If this is "our" pawn, move our view with it + if chunk.data.peerid == world.network.peer.id + controller = CharacterControllerComponent("controller",player_movement,"net") + ent\add(controller,"controller") + move_view = ScriptComponent("move_view",{ + script: () -> + loc = predicted.properties.pos + world.world_x = loc[1] + world.world_y = loc[2] + --graphic\moveto(vec3(loc[1],loc[2],loc[3])) + }) + ent\add(move_view,"move_view") + move_graphic = ScriptComponent("move_graphic", { + script: () -> + loc = predicted.properties.pos + graphic\moveto(vec3(loc[1],loc[2],loc[3])) + }) + ent\add(move_graphic, "move_graphic") + + else + error("Tring to create unknown entity:" .. tostring(id) .. ":" .. tostring(data)) + --assert(ent.components.net, "Failed to find net component") + return ent + +x.initialize = () -> + assert(world, "World must be available") + assert(world.network, "Network must be initialized") + localpawns = {} + net = nil + world.network\listen("CreatePawn", nil, (_, data) -> + log.info("Creating pawn:" .. tostring(data), {"net","ecs","client"}) + unpacked = am.parse_json(data) + ent_id = unpacked.id + create_entity(ent_id, unpacked) + --ent = ecs.Entity(ent_id) + --net = ClientNetworkedComponent("net",unpacked.data) + --ent\add(net,"net") + --graphic = GraphicsComponent("graphic",{ + --graphic: sprites.player + --}) + --ent\add(graphic,"graphic") + --pred = PredictedComponent("pred",{acc:{0,0,0}, vel: {0,0,0}, pos: {0,0,0}}, "net", player_movement) + --ent\add(pred, "pred") + ) + world.network\listen("update","client update",(_, data) -> + log.info("Updating:" .. tostring(data.id), {"net","ecs","client"}) + --log.info("Entities were:" .. tostring(world.level.entities),{"net","client","ecs"}) + log.debug("World entities are:" .. tostring(world.level.entities), {"ecs","client","net"}) + if not world.level.entities[data.id] + log.error("Client instructed to update id " .. data.id .. " but no entity exists! Entities were:" .. tostring(world.level.entities),{"net","client","ecs"}) + ent = world.level.entities[data.id] + net_component = ent\get(data.name) + if not net_component + error("Client instructed to update id " .. data.id .. " with network component named " .. tostring(data.name) .. " but no such component exists, entity was:" .. tostring(ent)) + net_component.properties = data + if net_component == net + print("Updating pawn net component") + log.info("Successfully updated entity" .. tostring(data.id), {"ecs","client","net"}) + ) + entities_responded = false + world.network\listen("RespondEntities","Respond entities",(_, data) -> + -- this is a full refresh, we can wipe entities and build them from scratch. + for _, ent in pairs(world.level.entities) + ent\destroy! + log.info("Wiping and re-creating entities:" .. tostring(data), {"ecs","net","client"}) + assert(type(data) == "table") + for id, properties in pairs(data) + assert(type(id) == "number") + assert(type(properties) == "string") + create_entity(id, am.parse_json(properties)) + log.info("Successfully created entity" .. tostring(id), {"ecs","net","client"}) + assert(#world.level.entities == #data, "Failed to create entities correctly") + ) + world.network\send("RequestEntities", {}) + world.network\listen("RespondRole", "Respond role", (_, data) -> + -- By this time we're in the game, destory any entities laying around. + for _, ent in pairs(world.level.entities) + ent\destroy! + log.info("Got role from server:" .. tostring(data), {"net","client"}) + game_menu.create_graphic(data) + am.save_state("gameplay", data) + -- Kinda hacky, but if we have a role, just disable net pumping + -- to prevent errors on net.create() + error("Preventing furhter network pumping!") + net.pump = () -> + log.info("Tried to pump net!",{"net","client"}) + ) + --world.network\send("RequestRole",{}) + log.info("Client fully initialized",{"net","client","graphic"}) + true +x diff --git a/src/clipboard_bridge.js b/src/clipboard_bridge.js new file mode 100644 index 0000000..6542fe5 --- /dev/null +++ b/src/clipboard_bridge.js @@ -0,0 +1,14 @@ + +window.CLIPBOARD = { + get_params: function(){ + var params = new URLSearchParams(window.location.search); + var tbl = {}; + params.forEach(function(value, key) { + tbl[key] = value; + }); + return tbl; + }, + get_path: function(){ + return window.location.origin + window.location.pathname; + } +}; diff --git a/src/color.moon b/src/color.moon new file mode 100644 index 0000000..5bebd20 --- /dev/null +++ b/src/color.moon @@ -0,0 +1,36 @@ + +color = + background: {116, 78, 68} + foreground: {231, 235, 197} + shadow: {78, 68, 55} + highlight: {145, 174, 134} + +am_color = {k, vec4(v[1]/255,v[2]/255,v[3]/255,1) for k,v in pairs(color)} +--error("Colors were:" .. tostring(am_color)) + +am.ascii_color_map = { + b: am_color.background + m: am_color.midground + f: am_color.foreground + s: am_color.shadow + h: am_color.highlight + x: am_color.black + o: am_color.outline +} + +lake_color = + highlight: color.highlight + foreground: {113, 224, 214} + midground: {90, 172, 188} + background: {75, 120, 156} + shadow: {51, 78, 120} + black: color.black + +am_lake_color = {k, vec4(v[1]/255, v[2]/255, v[3]/255,1) for k,v in pairs(lake_color)} + +{ + :color + :am_color + :lake_color + :am_lake_color +} diff --git a/src/conf.lua b/src/conf.lua new file mode 100644 index 0000000..f99a4dd --- /dev/null +++ b/src/conf.lua @@ -0,0 +1 @@ +shortname="ggj26" diff --git a/src/controller_bridge.js b/src/controller_bridge.js new file mode 100644 index 0000000..3e559f4 --- /dev/null +++ b/src/controller_bridge.js @@ -0,0 +1,38 @@ + +window.addEventListener("gamepadconnected", function(e) { + console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.", + e.gamepad.index, + e.gamepad.id, + e.gamepad.buttons.length, + e.gamepad.axes.length + ); + var i; + for(i = 0; i < e.gamepad.buttons.length; i++){ + CONT.last_state.buttons[i] = false; + } + for(i = 0; i < e.gamepad.axes.length; i++){ + CONT.last_state.axes[i] = 0; + } + CONT.gp = navigator.getGamepads()[0]; +}); + +window.CONT = { + messages: [], + gp: null, + last_state: { + on: false, + buttons: [], + axes: [] + }, + loop: function() { + var i; + if(CONT.gp == null) return; + for(i = 0; i < CONT.gp.buttons.length; i++){ + CONT.last_state.buttons[i] = CONT.gp.buttons[i].pressed; + } + for(i = 0; i< CONT.gp.axes.length; i++){ + CONT.last_state.axes[i] = CONT.gp.axes[i]; + } + CONT.last_state.on = true; + } +}; diff --git a/src/ecs.moon b/src/ecs.moon new file mode 100644 index 0000000..61b73a5 --- /dev/null +++ b/src/ecs.moon @@ -0,0 +1,184 @@ +win = require("window") +log = require("log") +world = require("world") +Component = require("ecs.component") +GraphicsComponent = require("ecs.graphics") +NetworkedComponent = require("ecs.networked") +PredictedComponent = require("ecs.predicted") +ScriptComponent = require("ecs.script") +CharacterControllerComponent = require("ecs.char_controller") +shader_world = require("shaders.world") + +--win.scene:append(shader_world) +--pp = am.postprocess({ + --clear_color: vec4(0.2, 0.2, 0.3, 1), + --depth_buffer: true, + ----stencil_buffer: true, + --width: win.width, + --height: win.height, +--}) +pp = am.group! +pp\append(shader_world.node) +GraphicsComponent.node = pp +ScriptComponent.node = pp +PredictedComponent.node = pp +NetworkedComponent.node = pp + +-- Base entity of our ECS +class Entity + id: 1 + new: (id, components) => -- [id], [components] + ent_reg = world.domain == "client" and world.level.entities or world.level_sync.ents + id_cur = id or #ent_reg + 1 + @created_at = debug.traceback! + assert(id_cur < 10, "Got more then 10 entities") + while ent_reg[id_cur] != nil + id_cur += 1 + log.debug("Want to create an entity with id" .. id_cur .. ", registry is:" .. tostring(ent_reg) .. "\n" .. debug.traceback(), {"ecs"}) + assert(ent_reg[id_cur] == nil, "Attempted to create entity with the same id as one that already exists: " .. tostring(id_cur) .. ", entities were:" .. tostring(ent_reg)) + + log.info("Creating entity " .. id_cur .. " domain is: " .. world.domain, {"ecs"}) + ent_reg[id_cur] = @ + @reg = ent_reg + @id = id_cur + -- Bookkeeping for O(1) access for components + @c_by_type = {} + @c_by_name = {} + --Entity is responsible for the component -> entity link + @components = components or {} + for name, component in pairs(@components) + component.entity = @ + @c_by_type[component.__class] = @c_by_type[component.__class] or {} + @c_by_type[component.__class][name] = component + for name, component in pairs(@components) + assert(component.join, "Component " .. name .. " does not have a join method.") + component\join(@) + component\post_join(@) + add: (component, cid) => + assert(component, "Failed to find component:" .. tostring(component)) + assert(type(component) == "table", "Component must be a table") + assert(component.__class, "Component must have a class") + component.entity = @ + component\join(@, cid) + component\post_join(@, cid) + if cid == nil + cid = #@components + 1 + while @components[cid] + cid += 1 + assert(@components[cid] == nil, "Already had a component with id" .. tostring(cid)) + @components[cid] = component + @c_by_type[component.__class] = @c_by_type[component.__class] or {} + @c_by_type[component.__class][cid] = component + assert(@c_by_name[component.name] == nil, "Duplicate components named" .. component.name) + @c_by_name[component.name] = component + @ + remove: (cid) => + component = @components[cid] + component.entity = nil + component\leave(@) + component + @components[cid] = nil + @c_by_type[component.__class][cid] = nil + @c_by_name[component.name] = nil + destroy: () => + for name, component in pairs(@components) + @remove(name) + @reg[@id] = nil + + get: (cid) => + @components[cid] + + serialize: () => + components = {} + for i, component in pairs(@components) + component_name = component.__class.__name + if not component.serialize + log.warn("Component:" .. component_name .. " does not have a .serialize()", {"ecs"}) + continue + component_data = component\serialize! + components[i] = string.format( + "%d\0%s%d\0%s", + #component_name, component_name, + #component_data, component_data + ) + return table.concat(components, "\0") + + --@deserialize: (data) => + --error("Deserialize called") + --log.info("Deserializing entity from data of length " .. tostring(data and #data or 0), {"ecs"}) + --ent = @! + ---- Empty payload -> empty entity + --if not data or #data == 0 + --return ent + ---- Map serialized component type names back to their classes + --component_types = { + --[GraphicsComponent.__name]: GraphicsComponent + --[NetworkedComponent.__name]: NetworkedComponent + --[PredictedComponent.__name]: PredictedComponent + --[ScriptComponent.__name]: ScriptComponent + --[CharacterControllerComponent.__name]: CharacterControllerComponent + --[PhysicsComponent.__name]: PhysicsComponent + --} + --pointer = 1 + --len = #data + --while pointer <= len + ---- Read component name length (digits up to first NUL) + --name_len_end = string.find(data, "\0", pointer) + --if not name_len_end + --break + --name_len_str = string.sub(data, pointer, name_len_end - 1) + --name_len = tonumber(name_len_str) + --if not name_len or name_len <= 0 + --break + --pointer = name_len_end + 1 + ---- Read component type name + --component_name = string.sub(data, pointer, pointer + name_len - 1) + --pointer += name_len + ---- Read component data length (digits up to next NUL) + --data_len_end = string.find(data, "\0", pointer) + --if not data_len_end + --break + --data_len_str = string.sub(data, pointer, data_len_end - 1) + --data_len = tonumber(data_len_str) + --if not data_len or data_len < 0 + --break + --pointer = data_len_end + 1 + ---- Read component payload + --component_data = string.sub(data, pointer, pointer + data_len - 1) + --pointer += data_len + ---- Skip inter-component separator if present + --if string.sub(data, pointer, pointer) == "\0" + --pointer += 1 + --ctype = component_types[component_name] + --if not ctype + --log.warn("Unknown component type during deserialize: " .. tostring(component_name), {"ecs"}) + --continue + --if not ctype.deserialize + --log.warn("Component type " .. tostring(component_name) .. " does not implement .deserialize()", {"ecs"}) + --continue + ---- Let the component class decide how to interpret its payload + --component = ctype.deserialize(component_data) + --if component + --ent\add(component) + --return ent + + __tostring: () => + components_str = tostring(@components) + return string.format("%s : {\n%s\n}\t%q",@@__name, components_str\gsub("^","\t"), @created_at) + +class PhysicsComponent extends Component + new: (name, properties) => + assert(properties and properties.shape, "Failed to find a shape for physics component") + super(name, properties) + +{ + :Component + :Entity + :NetworkedComponent + :PredictedComponent + :GraphicsComponent + :PhysicsComponent + :ScriptComponent + :CharacterControllerComponent + node: pp +} diff --git a/src/ecs/char_controller.moon b/src/ecs/char_controller.moon new file mode 100644 index 0000000..e5e3db7 --- /dev/null +++ b/src/ecs/char_controller.moon @@ -0,0 +1,70 @@ +ScriptComponent = require("ecs.script") +win = require("window") +log = require("log") +world = require("world") + +class CharacterControllerComponent extends ScriptComponent + new: (name, properties, netc_name) => + log.info("Creating new character controller", {"ecs"}) + properties.script = () -> + --log.info("Character controller running", {"ecs"}) + any_change = false + assert(@net.__class.__name == "ClientNetworkedComponent", "Wrong net component, was a " .. @net.__class.__name) + assert(@pred,"No predicted component") + assert(world.network, "Network must be created before CharacterControllerComponent starts running") + if win\key_pressed("w") + log.info("Key down w",{"net","ecs","client","player"}) + @net.properties.acc[2] = 1 + any_change = true + elseif win\key_released("w") + log.info("Key up w",{"net","ecs","client","player"}) + any_change = true + @net.properties.acc[2] = 0 + if win\key_pressed("s") + log.info("Key down s",{"net","ecs","client","player"}) + any_change = true + @net.properties.acc[2] = -1 + elseif win\key_released("s") + log.info("Key up s",{"net","ecs","client","player"}) + any_change = true + @net.properties.acc[2] = 0 + if win\key_pressed("a") + log.info("Key down a", {"net","ecs","client","player"}) + any_change = true + @net.properties.acc[1] = -1 + elseif win\key_released("a") + log.info("Key up a", {"net","ecs","client","player"}) + any_change = true + @net.properties.acc[1] = 0 + if win\key_pressed("d") + log.info("Key down d", {"net","ecs","client","player"}) + any_change = true + @net.properties.acc[1] = 1 + elseif win\key_released("d") + log.info("Key up d", {"net","ecs","client","player"}) + any_change = true + @net.properties.acc[1] = 0 + + -- We can't actually update the network on the client, + -- but we still want to do predicted movement + if any_change + @net.properties.last_update = am.eval_js("Date.now();") + @net.properties.pos = @pred.properties.pos + --@net.properties.vel = @pred.properties.vel + world.network\send("SuggestPlayerUpdate",{ + acc: @net.properties.acc + vel: @net.properties.vel + pos: @pred.properties.pos + }) + return false + --print("Running character controller",win\key_pressed("w")) + @netc_name = netc_name + super(name, properties) + join: (entity) => + log.debug("Components was:" .. tostring(entity.components), {"ecs"}) + @net = entity\get(@netc_name) + @pred = entity\get("pred") + assert(@net, "Must have added network component: " .. @netc_name .. ".") + super(entity) + +CharacterControllerComponent diff --git a/src/ecs/client_networked.moon b/src/ecs/client_networked.moon new file mode 100644 index 0000000..fc70dc2 --- /dev/null +++ b/src/ecs/client_networked.moon @@ -0,0 +1,19 @@ +world = require("world") +log = require("log") +NetworkedComponent = require("ecs.networked") +Component = require("ecs.component") + +class ClientNetworkedComponent extends Component + new: (name, properties) => + log.info("Creating client networked info", {"client"}) + super(name, properties) + listen_events: () -> + assert(wold.network, "world.network needs to be set before hooking events") + world.network\listen("create","client_network_component",(hubid, data) -> + error("ClientNetworkComponent create received:".. tostring(data)) + ) + world.network\listen("update","client_network_component",(hubid, data) -> + error("ClientNetworkComponent update received:".. tostring(data)) + ) + +ClientNetworkedComponent diff --git a/src/ecs/component.moon b/src/ecs/component.moon new file mode 100644 index 0000000..6ce16bd --- /dev/null +++ b/src/ecs/component.moon @@ -0,0 +1,16 @@ +-- Base component class of the ECS +class Component + depends: {} + new: (name, properties) => + @name = name + @properties = properties or {} + join: (e, cid) => + @ + post_join: (e, cid) => + @ + leave: (e) => + @ + __tostring: () => + return string.format("%s<%s> {\n%s\n}",@@__name,@name or "no name given",tostring(@properties)) + +Component diff --git a/src/ecs/graphics.moon b/src/ecs/graphics.moon new file mode 100644 index 0000000..66e5e80 --- /dev/null +++ b/src/ecs/graphics.moon @@ -0,0 +1,67 @@ +Component = require("ecs.component") +sprites = require("sprites") +gworld = require("shaders.world") +world = require("world") +log = require("log") + +sd = sprites.floor +w1 = sprites.wall1_diffuse + +panel = { + vec3(0, 1, 0.4), + vec3(1, 1, 0.4), + vec3(1, 0, 0.4), + vec3(1, 0, 0.4), + vec3(0, 0, 0.4), + vec3(0, 1, 0.4) +} + +class GraphicsComponent extends Component + new: (name, properties) => + --assert(properties and properties.node , "Failed to find node for graphics component") + --assert(@@node, ".node not set for GraphicsComponent") + assert(world.geom_view,".geom_view not set for world") + assert(world.uv_view, ".uv_view not set for world") + --@node = properties.node + super(name, properties) + static: () => + @@static + join: () => + --log.info("Joining with graphics component" .. tostring(@node), {"graphics"}) + --@@node\append(@node) + gworld.add(@) + leave: () => + gworld.remove(@) + --@@node\remove(@node) + node: () => + error("Tried to access graphic component's .node") + --@properties.node + tris: () => + 2 + populate_buf: (geom_view, uv_view, offset) => + assert(@properties.graphic, "Graphics component needs a graphic") + assert(offset == 1, "Offset was " .. tostring(offset)) + log.info("Populating:" .. tostring(offset) .. ":" .. tostring(@properties.graphic), {"graphic","ecs"}) + log.info(debug.traceback(), {"graphic","ecs"}) + log.info("Populating with:" .. tostring(panel),{"graphic","ecs"}) + geom_view\set(panel, offset, @tris! * 3) + @geom_view = geom_view + @uv_view = uv_view + @offset = offset + uv = @properties.graphic + uv_view[1] = vec4(uv.s1,uv.t1,1,1) + uv_view[2] = vec4(uv.s1,uv.t2,1,1) + uv_view[3] = vec4(uv.s2,uv.t2,1,1) + uv_view[4] = vec4(uv.s2,uv.t2,1,1) + uv_view[5] = vec4(uv.s2,uv.t1,1,1) + uv_view[6] = vec4(uv.s1,uv.t1,1,1) + --error("Graphics components must override .populate_buf()") + move: (move_off) => + for i, set in ipairs(panel) + @geom_view[@offset + i - 1] = @geom_view[@offset + i - 1] + move_off + moveto: (loc) => + assert(@offset, "Moveto called before populate_buf") + for i, set in ipairs(panel) + @geom_view[@offset + i - 1] = loc + set + +GraphicsComponent diff --git a/src/ecs/networked.moon b/src/ecs/networked.moon new file mode 100644 index 0000000..6673a7d --- /dev/null +++ b/src/ecs/networked.moon @@ -0,0 +1,71 @@ +Component = require("ecs.component") +world = require("world") +log = require("log") + +network_log = {} + +class NetworkedComponent extends Component + @q: {} + + new: (name, properties) => + assert(properties.id == nil, "networked component's id needs to be nil") + assert(properties.name == nil, "network component's name needs to be nil") + properties.id = world.level_sync.entid + world.level_sync.entid += 1 + nwc = @ + update = (key, value) => + if not world.hub + error("Tried to update network on the client") + return + if not properties[key] + error("Trying to set key not defined in properties:" .. key) + properties[key] = value + properties.last_update = am.eval_js("Date.now();") + nwc.__class.q[nwc] = true + -- Broadcast update to other peers through client + --world.hub\broadcast("update", properties) + --actually, we only want to update each network component *at most* once per frame. + + proxy = {} + setmetatable(proxy, { + __index: properties, + __newindex: update + }) + super(name, proxy) + @net_properties = properties + join: (e, cid) => + @@node\action("Send Updates", ()-> + @@update_dirty! + ) + @net_properties.id = e.id + @net_properties.name = cid + log.info("Added networked componenet name " .. @name .. " to entity id " .. e.id, {"net","server","ecs"}) + -- Send initial create message through client + --if world.hub -- Actually, entity creation is hard, just sync each entity individually. + --world.hub\broadcast("create", properties) + super(e) + + pack: () => + assert(@entity, "Tried to pack on a NetworkedComponent without an Entity") + log.info("Packing data from proxy:" .. tostring(@proxy), {"net","ecs"}) + return am.to_json({ + id: @entity.id + data: @net_properties + time: am.current_time! + }) + + unpack: () -> + assert(@entity, "Tried to unpack on a NEtworkedComponent without an Entity") + + @update_dirty: () => + for component, _ in pairs(@q) + world.hub\broadcast("update", component.net_properties) + @q = {} + + -- Each different kind of entity needs to have it's own create and listen network + -- hooks, it's way too complicated and heavy to push every entity with every componenet + -- over the network. + +assert(NetworkedComponent.q, "No queue found!") + +NetworkedComponent diff --git a/src/ecs/player_graphic.moon b/src/ecs/player_graphic.moon new file mode 100644 index 0000000..5ad9cab --- /dev/null +++ b/src/ecs/player_graphic.moon @@ -0,0 +1,66 @@ +GraphicsComponent = require("ecs.graphics") +sprites = require("sprites") + +geom_fac = { + {-1, -1, 1}, + {-1, 1, 1}, + {-1, -1, 1}, + {1, 1, 1}, + {1, -1, 1}, + {-1, -1, 1} +} + +class PlayerGraphicComponent extends GraphicsComponent + @player_size = 0.25 + @static = false + --new: (name, properties) => + --print("New PlayerGraphicsComponenet, super is", @@__init) + --print("But graphicsComponent's __init is:", GraphicsComponent.__init) + --GraphicsComponent.__init(@, name, properties) + --super(name, properties) + tris: () -> + 2 + populate_buf: (geom_view, uv_view, offset) => + @playerbuf = geom_view + h = @@player_size / 2 + for i, set in ipairs(geom_fac) + geom_view[i] = vec3(h*set[1], h*set[2], h*set[3]) + uv = sprites.player_normal + uv_view[1] = vec2(uv.s1,uv.t1) + uv_view[2] = vec2(uv.s1,uv.t2) + uv_view[3] = vec2(uv.s2,uv.t2) + uv_view[4] = vec2(uv.s2,uv.t2) + uv_view[5] = vec2(uv.s2,uv.t1) + uv_view[6] = vec2(uv.s1,uv.t1) + join: (entity) => + super(entity) + aspect = win.width / win.height + -- inject nodes into the scene graph + program = @.node("use_program") + pred_component = entity\get("pred") + graphic = entity\get("graphic") + @node\remove(program) + @node\append(am.blend("alpha")\append(am.depth_test("less", true)\append(program))) + @node\action(() => + pred_loc = pred_component.properties.pos + graphic\move(pred_loc.x, pred_loc.y) + ) + --@.node("bind").highlight = color.am_color.black + move: (x,y) => + assert(x, "x required") + assert(y, "y required") + world.level.move_lamp(@lamp, x + @lamp_offset.x, y + @lamp_offset.y) + h = @@player_size / 2 + z = 0.5 + @playerbuf[1] = vec3(x-h,y-h,z) + @playerbuf[2] = vec3(x-h,y+h,z) + @playerbuf[3] = vec3(x+h,y+h,z) + @playerbuf[4] = vec3(x+h,y+h,z) + @playerbuf[5] = vec3(x+h,y-h,z) + @playerbuf[6] = vec3(x-h,y-h,z) + --print("Move called", @playerbuf[1]) + face: (direction) => + --print("direction",direction) + @.node("bind").rot = (direction ) % (math.pi * 2) + +PlayerGraphicComponent diff --git a/src/ecs/predicted.moon b/src/ecs/predicted.moon new file mode 100644 index 0000000..b8bdd28 --- /dev/null +++ b/src/ecs/predicted.moon @@ -0,0 +1,25 @@ +Component = require("ecs.component") + +class PredictedComponent extends Component + new: (name, properties, netc_name, calculate) => + super(name, properties) + @netc_name = netc_name + assert(calculate and type(calculate) == "table", "Calculate must be a table, was " .. type(calculate)) + @calculate = calculate + join: (entity) => + @net = @entity\get(@netc_name) + @node = am.group! + @node\action(() -> + @forward! + ) + @@node\append(@node) + super(entity) + leave: (entity) => + @@node\remove(@node) + forward: () => + for property, calculation in pairs(@calculate) + @properties[property] = calculation(@) + + + +PredictedComponent diff --git a/src/ecs/script.moon b/src/ecs/script.moon new file mode 100644 index 0000000..05869c0 --- /dev/null +++ b/src/ecs/script.moon @@ -0,0 +1,16 @@ +Component = require("ecs.component") + +class ScriptComponent extends Component + new: (name, properties) => + print("Creating new script component") + assert(properties and properties.script, "Failed to find script name for script component") + super(name, properties) + join: (e) => + print("Script component is joining an entity") + @node = am.group! + @@node\append(@node) + @node\action(@properties.script) + + leave: (e) => + print("Script component is leaving an entity") + @@node\remove(@node) diff --git a/src/hub.moon b/src/hub.moon new file mode 100644 index 0000000..48fcec3 --- /dev/null +++ b/src/hub.moon @@ -0,0 +1,184 @@ +-- Hub-and-spoke networking hub +-- Manages client connections and routes messages between them + +net = require "net" +log = require "log" +world = require "world" + +-- Register message types that clients sends to the hub +-- Client -> Hub: RequestEntities has no payload (empty table is fine) +net.register_message("RequestEntities", {}) + +-- Envelope used for all logical messages routed through the hub +net.register_message("routed_message", { + required: { + message_type: "string" + from: "string" + } + optional: { + data: "table" + } +}) + +net.register_message("Join", { + required: { + name: "string" + } +}) + +-- Hub -> Client: RespondEntities sends the current entities list (array) +net.register_message("RespondEntities", { + optional: { + entities: "table" + } +}) + +-- Hub -> Client: CreateEntity announces a new entity for a given peerid +net.register_message("CreateEntity", { + required: { + peerid: "string" + } +}) + +class Hub + new: => + @peer = nil + @clients = {} -- client_id -> connection + @client_names = {} -- client_id -> name (optional) + @initialized = false + @routes = {} + @on_connect_callbacks = {} + @on_disconnect_callbacks = {} + + initialize: => + if @initialized + return + --@attempts = 0 + --create_peer = () -> + --@peer = net.Peer! + --fail_peer_creation = () -> + --@attempts += 1 + --while @attempts < 4 + --xpcall(create_peer, fail_peer_creation) + --log.info("Creating peer attempt " .. @attempts .. " / 4", {"net","server"}) + --if @peer == nil + --error("Failed to create peer 4 times...") + @peer = net.Peer! + log.info("Hub peer created: #{@peer.id}", {"server", "net"}) + -- Listen for incoming connections + @peer\on "connection", (peer, message) -> + client_id = message.data.dest -- the other side of the connection? + log.info("Client connected: " .. tostring(peer) .. ":" .. tostring(message), {"server", "net"}) + conn = message.data + @clients[client_id] = conn + -- Set up connection handlers + -- When the connection receives data, the callback is invoked with + -- (connection, message_array), where message_array = { msgname, msgdata }. + -- We only care about the logical client id and the logical message array, + -- so forward just those to handle_message. This keeps the JS bridge + -- signature Hub.handle_message(from_client, msg) where msg is the + -- array { msg_type, msg_data }. + conn\on "data", (conn, message) -> + log.info("On data got arguments:" .. tostring(conn) .. "," .. tostring(message), {"server","net"}) + @handle_message(client_id, message) + + conn\on "close", () -> + @handle_disconnect(client_id) + + -- Notify connection callbacks + for callback in *@on_connect_callbacks + callback(client_id) + + @initialized = true + require("server.init") + + handle_message: (from_client, message) => + -- message is the array {msg_type, msg_data} that was sent over the wire + log.info("Hub received message: #{message} from #{from_client}", {"net", "server"}) + msg_type = message[1] + msg_data = message[2] + + -- Build routed_message-style envelope for validation + routed = { + message_type: msg_type + from: from_client + data: msg_data + } + + -- Validate envelope + ok, err = pcall(net.validate, "routed_message", routed) + if not ok + log.error("Invalid routed_message from " .. tostring(from_client) .. ": " .. tostring(routed), {"net", "server"}) + return + + -- Validate payload for the specific message type + ok, err = pcall(net.validate, msg_type, msg_data) + if not ok + log.error("Invalid " .. tostring(msg_type) .. " payload from " .. tostring(from_client) .. ": " .. tostring(err), {"net", "server"}) + return + + if msg_type == "Join" + log.info("Hub handling Join from " .. tostring(from_client) .. " with data=" .. tostring(msg_data), {"net", "server", "debug"}) + world.domain = "server" + if @routes[msg_type] + -- routes[msg_type] is a flat map id -> callback(from_client, data) + for _, callback in pairs(@routes[msg_type]) + callback(from_client, msg_data) + else + log.warn("No routes for " .. tostring(msg_type), {"net","server"}) + world.domain = "client" + + listen: (message_type, id, callback) => + @routes[message_type] = @routes[message_type] or {} + id = id or "default" + @routes[message_type][id] = callback + log.info("Hub listening for #{message_type}", {"net", "server"}) + id + + defen: (message_type, id) => + @routes[message_type][id] = nil + + send: (client_id, message_type, data) => + conn = @clients[client_id] + if not conn + log.warn("Cannot send to unknown client: #{client_id}", {"server", "net"}) + return + log.info("Hub sending #{message_type} to #{client_id}", {"net", "server"}) + conn\send({message_type, data}) + + broadcast: (message_type, data) => + nclients = 0 + for _, _ in pairs(@clients) + nclients += 1 + log.info("Hub broadcasting #{message_type} to " .. tostring(nclients) .. " clients", {"net", "server"}) + for clientid, conn in pairs(@clients) + conn\send({message_type, data}) + + handle_disconnect: (client_id) => + log.info("Client disconnected: #{client_id}", {"server", "net"}) + @clients[client_id] = nil + @client_names[client_id] = nil + + -- Notify disconnect callbacks + for callback in *@on_disconnect_callbacks + callback(client_id) + + on_connect: (callback) => + table.insert(@on_connect_callbacks, callback) + + on_disconnect: (callback) => + table.insert(@on_disconnect_callbacks, callback) + + get_clients: => + clients = {} + for id, conn in pairs @clients + table.insert(clients, { + id: id + name: @client_names[id] + }) + clients + + pump: => + net.pump! + +{:Hub} diff --git a/src/js_bridge.js b/src/js_bridge.js new file mode 100644 index 0000000..f6a158b --- /dev/null +++ b/src/js_bridge.js @@ -0,0 +1,134 @@ +var s = document.createElement('script'); +s.setAttribute('src','https://unpkg.com/peerjs@1.5.5/dist/peerjs.min.js'); +document.body.appendChild(s); +function genRanHex(size) { + return Array.apply(null,Array(size)).map(function(){ + return Math.floor(Math.random() * 16).toString(16); + }).join(''); + +} +window.PEER = { + event_queue: [], + peers: {}, + peer_message_queue: [], + connection_message_queue: [], + creation_queue: [], + connections: {}, + to_connect: [], // Sometimes we have to wait a tick before the connection is ready. + create: function(_) { + var peer = new Peer({ + //"host": "cogarr.net", + //"path": "/stun/", + //"secure": true, + //"debug": 3 + }); + peer.on("open", function(){ + console.log("[JS] Open called on peer"); + PEER.peers[peer.id] = peer; + PEER.creation_queue.push(peer.id); + }); + peer.on("error", function(msg){ + //console.log("[JS ERROR] " + msg); + PEER.connection_message_queue.push({"message":msg, "data": { + "call": "create", + "e": "error", + "message": msg + }}); + }); + }, + delete_peer: function(tbl) { + var name = tbl.name; + //console.log("[JS] Deleting peer " + name); + delete PEER.peers[name]; + }, + on: function(tbl) { + var name = tbl.name; + var e = tbl.e; + var message = tbl.message; + //console.log("[JS] Setting hook for " + name + "," + e + "," + message); + PEER.peers[name].on(e, function(data) { + //console.log("[JS] Peer " + name + " received " + e); + if(e == "connection"){ + // Store the server-side DataConnection under directional key [server, client] + PEER.connections[[name,data.peer]] = data; + //console.log("[JS] Peer.connections is now"); + //console.log(PEER.connections); + // Rewrite for Lua so net.rewrite_events can build a Hub-side Connection + data = [name,data.peer]; // [server, client] + }else{ + //console.log("[JS] Not connection:" + e + ":" + data); + } + PEER.peer_message_queue.push({"message":message, "data":{ + "call": "on", + "peer": name, + "e": e, + "data": data + }}); + //console.log("[JS] Message queue is now"); + //console.log(PEER.peer_message_queue); + }); + }, + connect: function(tbl) { + var source = tbl.source; + var dest = tbl.dest; + //console.log("[JS] connecting " + source + " to " + dest); + var conn = PEER.peers[source].connect(dest); + // Store client-side DataConnection under directional key [client, hub] + PEER.connections[[source,dest]] = conn; + // Send a hello to always establish a connection + //console.log("[JS] sending hello");// doesn't seem to show up in the output, but its needed so we don't drop the first message. + conn.send("Hello"); + //console.log("[JS] Connect called, PEER.connections is"); + //console.log(PEER.connections); + return [source,dest]; + }, + disconnect: function(tbl) { + PEER.peers[tbl.name].disconnect(); + }, + reconnect: function(tbl){ + PEER.peers[tbl.name].reconnect(); + }, + destroy: function(tbl){ + PEER.peers[tbl.name].destroy(); + }, + send: function(tbl){ + var source = tbl.source; + var dest = tbl.dest; + var data = tbl.data; + var key = [source,dest]; + //console.log("[JS] sending " + data + " over " + key); + //console.log(PEER.connections[key]); + //console.log(data); + PEER.connections[key].send(data); + }, + close: function(tbl){ + var name = tbl.name; + var id = tbl.id; + PEER.connections[[name,id]].close(); + }, + conn_on: function(tbl){ + var source = tbl.source; + var dest = tbl.dest; + var e = tbl.e; + var message = tbl.message; + //console.log("[JS] Setting hook for [" + source + "," + dest + "] " + e + "," + message); + //console.log(PEER.connections[[source,dest]]); + //console.log(PEER.connections); + PEER.connections[[source,dest]].on(e, function(c) { + //console.log("[JS] connection between " + dest + " and " + source + " received " + e); + PEER.connection_message_queue.push({"message":message, "data":{ + "call": "on", + "peer": source, + "dest": dest, + "e": e, + "data": c + }}); + }); + }, + conn_field: function(tbl){ + var name = tbl.name; + var id = tbl.id; + var field = tbl.field; + return PEER.connections[[name,id]][field]; + } +}; diff --git a/src/levels/entmaker.moon b/src/levels/entmaker.moon new file mode 100644 index 0000000..da92651 --- /dev/null +++ b/src/levels/entmaker.moon @@ -0,0 +1,21 @@ +-- Creates entities from commited messages +world = require("world") +log = require("log") + +maker = {} + +maker.start_peer = () -> + -- All modes now use client interface (including host) + if world.network + -- Receive suggestions from hub + world.network\register_router("suggest", (from_id, data) -> + -- Handle suggestion + ) + +maker.start_elected = () -> + if world.network_mode == "host" + -- Hub can handle incoming suggestions from clients + -- (Handled inline when clients send messages) + log.info("Entity maker started in host mode", {"net"}) + +maker diff --git a/src/levels/game.moon b/src/levels/game.moon new file mode 100644 index 0000000..1e4899c --- /dev/null +++ b/src/levels/game.moon @@ -0,0 +1,7 @@ + +x = {} + +x.create = () -> + error("Creating level game") + +x diff --git a/src/levels/lobby.moon b/src/levels/lobby.moon new file mode 100644 index 0000000..7f2a27b --- /dev/null +++ b/src/levels/lobby.moon @@ -0,0 +1,111 @@ +ecs = require("ecs") +world = require("world") +shader = require("shaders.world") +sprites = require("sprites") +task = require("task") +LobbyGraphic = require("prefab.lobby") +GraphicsComponent = require("ecs.graphics") +ClientNetworkedComponent = require("ecs.client_networked") +NetworkedComponent = require("ecs.networked") +log = require("log") + +level = {} + +lobby = nil + +level.create = (code) -> + log.info("Creating level:" .. code,{"level"}) + -- We can set this even on the client, it just won't get used. + world.level_sync.ref = { + id: "Level from lobby.moon" + get_spawn_location: () -> + {0,0,0} + } + log.info("world.domain was" .. tostring(world.domain),{"level"}) + if world.domain == "client" + lobby = ecs.Entity! + lobby_graphic = LobbyGraphic("graphic",{}) + lobby\add(lobby_graphic,"grapic") + lobby\add(ClientNetworkedComponent("net",{ + type: "level", + level_name: "levels.lobby" + level_data: {code} + }),"net") + + code = world.level_sync.data[1] + path = am.eval_js("window.CLIPBOARD.get_path()") + url = string.format("%s?i=%s",path,code) + --todo: set qr code + am.eval_js([[ +var s = document.createElement('div'); +s.setAttribute("id","qrcode"); +s.setAttribute("style","z-index: 1; position:absolute; visibility:hidden;"); +var p = document.getElementById("container"); +p.prepend(s); +console.log("[JS] Added qrcode", s); +new QRCode(s, "]] .. url .. [["); +]]) + co = coroutine.create(() -> + print("Start of coroutine.") + imgsrc = "" + while imgsrc == "" + imgsrc = am.eval_js([[document.getElementById("qrcode").children[1].src;]]) + print("Waiting on imgsrc...") + coroutine.yield! + print("Got imgsrc.") + b64 = imgsrc\match("^data:image/png;base64,(.+)") + print("Found base64 png of qrcode.") + qrbuffer = am.base64_decode(b64) + print("Got qrbuffer") + qrimg = am.decode_png(qrbuffer) + print("Created img") + qrtex = am.texture2d(qrimg) + print("Created texture2d") + sprite = { + texture: qrtex + s1: 0 + t1: 0 + s2: 1 + t2: 1 + x1: 0 + y1: 0 + x2: qrimg.width + y2: qrimg.height + wdith: qrimg.width + height: qrimg.height + } + qrcode = ecs.Entity! + qrcode_texture = { + vec4(0,1,1,1), + vec4(1,1,1,1), + vec4(1,0,1,1), + vec4(1,0,1,1), + vec4(0,0,1,1), + vec4(0,1,1,1) + } + code_graphic = GraphicsComponent("graphic",{ + graphic: sprite + }) + qrcode\add(code_graphic) + code_graphic\moveto(vec3(-0.5,-0.25,0)) + ) + log.info("Creating qrcode coroutine for lobby",{"ui"}) + require("task").add(co) + elseif world.domain == "server" + lobby = ecs.Entity! + lobby\add(NetworkedComponent("net",{ + type: "level", + level_name: "levels.lobby" + level_data: {world.hub.peer.id} + }), "net") + + qrcode = ecs.Entity! + else + error("Unknown domain:" .. world.domain) + +level.destroy = () -> + if not lobby + error("Tried to destory lobby before it was built") + lobby\destroy! + +level diff --git a/src/log.moon b/src/log.moon new file mode 100644 index 0000000..cadfaec --- /dev/null +++ b/src/log.moon @@ -0,0 +1,37 @@ +-- Singleton object to deal with log messages +class log + log: (message, tags, level) -> + tags = tags or {} + if not level + error("Level is required") + tag_rev = {tag,true for tag in *tags} + chunk = + level: level + time: os.clock! + message: message + tags: tag_rev + for observer in *log.observers + observer(chunk) + + reset: -> + log.observers = {} + debug: (message, tags) -> + --log.log(message, tags, "debug") + info: (message, tags) -> + log.log(message, tags, "info") + warn: (message, tags) -> + log.log(message, tags, "warn") + error: (message, tags) -> + log.log(message, tags, "error") + -- We can't call error() here, see preload.lua + panic: (message, tags) -> + log.log(message, tags, "panic") + -- Can't call error() here either. + listen: (callback) -> + table.insert(log.observers, callback) + #log.observers + defen: (n) -> + table.remove(log.observers, n) + +log.reset() +log diff --git a/src/main.lua b/src/main.lua new file mode 100644 index 0000000..aebd83f --- /dev/null +++ b/src/main.lua @@ -0,0 +1,114 @@ +require("preload") +local color = require("color") +local win = require("window") + +local pp = am.postprocess({ + clear_color = color.am_color.background, + depth_buffer = true, + --stencil_buffer = true, + width = win.width, + height = win.height, +}) +local world = require("world") +world.node = pp +world.node:action(function() + if win:key_down("l") then + local log = require("log") + log.info("My network peerid is:" .. tostring(world.network.peer.id), {"net"}) + if world.hub then + log.info("I'm the host, host peerid is:" .. tostring(world.hub.peer.id), {"net"}) + end + log.info("Entities are:" .. tostring(world.level.entities), {"ecs"}) + end +end) +--local stars = require("shaders.stars") +--pp:append(stars) +local shader_world = require("shaders.world") +--win.scene:append(shader_world.node) +win.scene:append(shader_world.node) + +local ecs = require("ecs") +pp:append(ecs.node) + +win.scene:append(pp) + +local ui = require("ui") +local depth = am.depth_test("always") +depth:append(ui.node) +win.scene:append(depth) + +-- Initialize world.network, world.network_mode, and world.hub +-- These will be set by the menu when user chooses Host or Join +world.network = nil +world.network_mode = nil +world.hub = nil + +-- Network pump node - pumps net, world.hub, and world.network (client) +local net = require("net") +local net_node = am.group() +net_node:action(function() + net.pump() + if world.hub then + -- If we're hosting, pump the hub + world.hub:pump() + end + if world.network then + -- Pump the client (both host and regular clients have one) + world.network:pump() + end +end) +win.scene:append(net_node) -- Pumps the net state machine + +task = require("task") +win.scene:append(task.node) -- Pumps async tasks + +--input_menu = require("menu.input") +--input_menu.initialize() +--require("worldgen") +--require("world_test") +--require("net_test") +--require("ui_test") +--require("router_test") +--require("controller_test") +game = require("menu.game") +pp:append(game.node) +tutorial = require("menu.tutorial") +win.scene:append(tutorial.node) +require("menu.main").initialize() +require("log").listen(function(chunk) + --if chunk.tags.ui then + --return + --end + if not chunk.tags.net then + return + end + --if not chunk.tags.ecs then + --return + --end + --if not chunk.tags.graphic then + --return + --end + --if not (chunk.tags.net and chunk.tags.ecs and chunk.tags.client) then + --return + --end + --if not chunk.message:find("Want to create") then + --return + --end + data = {"[",chunk.level:upper(),"]"} + if chunk.tags.server then + table.insert(data,"[SERVER]") + elseif chunk.tags.client then + table.insert(data,"[CLIENT]") + end + table.insert(data, os.date()) + table.insert(data," > ") + table.insert(data,chunk.message) + if chunk.level == "error" then + print(debug.traceback(table.concat(data))) + else + print(table.concat(data)) + end +end) +pp.clear_color = require("color").am_color.background +--am.eval_js(require("js_bridge")) +--local a,b = pcall(am.eval_js, require("js_bridge")) diff --git a/src/menu/game.moon b/src/menu/game.moon new file mode 100644 index 0000000..9b871a4 --- /dev/null +++ b/src/menu/game.moon @@ -0,0 +1,208 @@ +settings = require("settings") +poems = require("poems") +net = require("net") +world = require("world") +log = require("log") +ui = require("ui") +win = require("window") +task = require("task") +abilities = require("abilities") + +x = {} + +x.node = am.group! +net.register_message("RequestRole", {}) +net.register_message("RespondRole", { + required: { + youare: "string" + start: "number" + time: "number" + } + optional: { + hint: "string" + poem: "string" + } +}) +x.slide_and_fade = (node, delay) -> + delay = delay or 0 + tween = am.ease.sine + ease_color = (node) -> + end_color = node.color + node.color = end_color({a:0}) + node\action(am.series({ + am.delay(delay), + am.tween( + 0.5, + { + color: end_color + }, + tween + ) + })) + for _, text in pairs(node\all("text",true)) + ease_color(text) + for _, sprite in pairs(node\all("sprites",true)) + ease_color(sprite) + + translate = node\all("translate", true) + for _, translate_node in pairs(translate) + print("Examining translate node:", translate_node) + end_y = translate_node.y + translate_node.y += 50 + translate_node\action(am.series({ + am.delay(delay), + am.tween( + 0.5, + { + y: end_y + }, + tween + ) + })) + +-- data contains +-- > data.youare --("pawn" or "unmasked") +-- and then either +-- > data.hint -- (string) +-- or +-- > data.poem -- (text) +x.create_graphic = (data) -> + print("Creating mask on screen for data:" .. tostring(data)) + timer = ui.text(0,400,win.width-40, 100, "00:00") + timer.node\tag("timer") + alert_minute = false + alert_oot = false + timer.node\action(() -> + now = am.eval_js("Date.now()") + end_time = data.start + (data.time * 1000) + countdown = math.floor((end_time - now) / 1000) + minutes = math.floor(countdown/60) + seconds = countdown % 60 + time_txt = string.format("%02d : %02d",minutes, seconds) + timer.node("text").text = time_txt -- assume only 1 line? + if countdown < 60 and not alert_minute-- 1 minute + log.info("1 minute alert", {"client"}) + alert_minute = true + timer.node\action(am.play(17962709,false,1,1)) + if countdown < 0 and not alert_oot + -- Alert sound + log.info("out of time alert", {"client"}) + alert_oot = true + timer.node\action(am.play(96446209,false,1,1)) + return + ) + click = (n) -> + return () -> + log.info("Click " .. tostring(n), {"client"}) + return true + timer.node\action(am.series({ + am.parallel({ + am.play(68962308,false,1,1) + am.delay(1) + }), + am.parallel({ + am.play(68962308,false,1,0.75) + am.delay(1) + }), + am.parallel({ + am.play(68962308,false,1,0.25) + am.delay(1) + }) + })) + if data.youare == "unmasked" + -- Do unmasked stuff + youare = ui.text(0,300,win.width-40, 100, "You wear no mask") + x.slide_and_fade(youare.node) + fools = ui.text(0,200,win.width-40, 100, "These fools, they created an order based on ") + x.slide_and_fade(fools.node,0.5) + hint = ui.text(0,0,win.width-40, 100, data.hint) + x.slide_and_fade(hint.node, 1) + keep_looking = ui.text(0,-200,win.width-40,100,"Keep looking 5") + keep_looking.node\tag("keep_looking") + keep_looking.node\action(() -> + now = am.eval_js("Date.now()") + end_time = data.start + (5 * 1000) + if now < end_time + keep_looking.node("text").text = "Keep looking " .. math.ceil((end_time - now) / 1000) + else + ui.delete(keep_looking) + + ) + else + assert(abilities[data.youare], "No ability hint for role: " .. tostring(data.youare)) + print("Going into else branch for create_graphic") + print("ui.text was", ui.text) + print(debug.getinfo(ui.text)) + youare = ui.text(0,332,win.width-40,100,"You are " .. data.youare) + x.slide_and_fade(youare.node) + --ability = ui.text(0,264,win.width-40,100,abilities[data.youare]) + --x.slide_and_fade(ability.node) + assert(youare, "Failed to get a node from ui.text") + text = ui.text(0,200,win.width-40,100,"You remember the words we spoke at the founding, they were:") + x.slide_and_fade(text.node, 0.5) + poem = ui.text(0,0,win.width-40,100,data.poem) + x.slide_and_fade(poem.node, 1) + log.info("Finished creating graphic",{"client"}) + + +x.create = () -> + if world.hub + all_peers = world.hub.clients + peers = {} -- masked players + for clientid,connection in pairs(all_peers) + table.insert(peers, clientid) + unmasked = {} -- unmasked players + for i = 1, settings.n_unmasked + rng = math.random(#peers) + table.insert(unmasked, table.remove(peers, rng)) + poem = poems[math.random(#poems)] + hint = poem.hints[math.random(#poem.hints)] + start_time = am.eval_js("Date.now()") + client_data = {} + for _, clientid in ipairs(peers) + client_data[clientid] = { + youare: "a pawn" + poem: poem.text + start: start_time + time: settings.game_time + } + --world.hub\send(clientid, "Begin", { + --youare: "a pawn" + --poem: poem.text + --start: start_time + --time: settings.game_time + --}) + for _, clientid in ipairs(unmasked) + client_data[clientid] = { + youare: "unmasked" + hint: hint + start: start_time + time: settings.game_time + } + --world.hub\send(clientid, "Begin", { + --youare: "unmasked" + --hint: hint + --start: start_time + --time: settings.game_time + --}) + world.level_sync.client_data = client_data + --world.hub\listen("RequestRole","Request role", (clientid, _) -> + --log.info("Responding with role:" .. tostring(client_data[clientid]), {"net","server"}) + --world.hub\send(clientid, "RespondRole", client_data[clientid]) + --) + world.network\listen("RespondRole", "Respond role", (_, data) -> + log.info("Got role from server:" .. tostring(data), {"net","client"}) + x.create_graphic(data) + am.save_state("gameplay", data) + ) + world.network\send("RequestRole",{}) + --world.network\listen("Begin","Begin game", (hubid, data) -> + --log.info("Staring game, data: " .. tostring(data), {"net","client"}) + --role = data.youare + --time_pos = am.translate(vec2(100,20)) + --x.create_graphic(data) + --am.save_state("gameplay", data) + --log.info("Finished saving data:" .. tostring(am.load_state("gameplay")), {"net","client"}) + --) + +x diff --git a/src/menu/input.moon b/src/menu/input.moon new file mode 100644 index 0000000..f943e6d --- /dev/null +++ b/src/menu/input.moon @@ -0,0 +1,30 @@ +ui = require("ui") +world = require("world") +main_menu = require("menu.main") +input = {} + +buttons = {} +input.initialize = () -> + button_mk = ui.button(-630,-100,300,200,"Mouse\nand\nKeyboard") + button_touch = ui.button(-300,-250,500,500,"Touch") + button_controller = ui.button(250,-64,380,128,"Controller") + button_touch.on = () => + world.controller = require("controllers.touch") + input.remove! + button_mk.on = () => + print("setting mk controller") + world.controller = require("controllers.mouse_keyboard") + input.remove! + require("menu.main").initialize! + button_controller.on = () => + world.controller = require("controllers.controller") + input.remove! + buttons = {button_mk, button_touch, button_controller} + +input.remove = () -> + print("Removing buttons", buttons) + for button in *buttons + print("Deleting button",button) + ui.delete(button) + +input diff --git a/src/menu/lobby.moon b/src/menu/lobby.moon new file mode 100644 index 0000000..c20face --- /dev/null +++ b/src/menu/lobby.moon @@ -0,0 +1,103 @@ +ui = require("ui") +world = require("world") +log = require("log") +util = require("util") +task = require("task") +net = require("net") +game = require("menu.game") + +menu = {} +net.register_message("RespondLevel",{ + name: "string" -- name of the level e.g. "levels.lobby" + data: "table" -- sequence to initalize the level +}) +net.register_message("StartGame",{}) +start_game = nil +lobby_url = nil +menu.initialize = () -> + log.info("Initializing lobby", {"ui"}) + game_data = am.load_state("gameplay") + now = am.eval_js("Date.now()") + --if game_data and (not world.hub) and (now - game_data.start > game_data.time * 1000) + --error("Looks like we have a game in progress:" .. tostring(game_data)) + --menu.destroy! + --game.create! + --return + log.info("Got game data", {"ui","net"}) + ready = false + if world.network_mode == "host" + start_game = ui.button(-150,400-128,300,128,"Start!") + start_game.on = (e) => + log.info("Starting game!",{"net","server"}) + world.level_sync.name = "levels.game" + for _, ent in pairs(world.level.entities) + ent\destroy! + -- Actually send the message to start the game + log.info("Connected peers were:" .. tostring(world.hub.clients), {"net","server"}) + world.hub\broadcast("StartGame",{}) + log.info("Finished creating game, level_sync.name is" .. world.level_sync.name,{"net","server"}) + else + start_game = ui.text(0, 400-64,300,128,"Waiting...") + code = nil + if world.network_mode == "host" + -- For host, use the hub's peer ID (not the local client's peer ID) + code = util.peer_to_code(world.hub.peer.id) + elseif world.network_mode == "client" + params = am.eval_js("window.CLIPBOARD.get_params()") + code = params.i + else + error("world.network must be initialized before creating lobby menu") + world.network\listen("StartGame","Lobby start game",() -> + log.info("Starting game!",{"net","client"}) + for _, ent in pairs(world.level.entities) + ent\destroy! + menu.destroy! + game.create! + log.info("Finished creating game", {"net","client"}) + ) + world.level_sync.data[1] = code + path = am.eval_js("window.CLIPBOARD.get_path()") + url = string.format("%s?i=%s", path, code) + --url_display = string.format("%s\n?i=%s",path,code) + url_display = "Copy URL" + lobby_url = ui.button(-180,-400+64,360,84,url_display) + lobby_url.on = () => + log.info("Clicked button, copying text",{"ui"}) + transform = am.translate(0,-400+128) + copied_text = am.text("Coppied!", vec4(1,1,1,1)) + transform\append(copied_text) + copied_text\action(coroutine.create(() -> + i = 1 + while i > 0 + i = i - (2/255) + copied_text.color = vec4(1,1,1,i) + transform.y += i * 3 + coroutine.yield! + lobby_url.node\remove(transform) + )) + lobby_url.node\append(transform) + -- This HAS to be the last action in this function, or else + -- javascript thinks this is happening outside of a user interaction. + am.eval_js("navigator.clipboard.writeText('" .. url .. "');") + log.info("Created lobby buttons", {"ui"}) + level_loader = coroutine.create(() -> + while not world.network.connected + log.info("Waiting for network to load level...",{"net","client"}) + coroutine.yield! + --level = world.network\sync("RequestLevel",{},"RespondLevel") + --log.info("Got information back from sync" .. tostring(level), {"net","client"}) + --world.level_sync.name = level.name + --world.level_sync.data = level.data + --log.info("Loading " .. level.name .. " with data " .. tostring(level.data), {"net","client","level"}) + --level_mod = assert(require(level.name)) + --assert(level_mod.create, "Level " .. level.name .. " had no .create()") + --level_mod.create(unpack(level.data)) + ) + task.add(level_loader) + +menu.destroy = () -> + ui.delete(lobby_url) + if start_game + ui.delete(start_game) + +menu diff --git a/src/menu/main.moon b/src/menu/main.moon new file mode 100644 index 0000000..04f53b0 --- /dev/null +++ b/src/menu/main.moon @@ -0,0 +1,137 @@ +ui = require("ui") +hub_mod = require("hub") +client_mod = require("client") +world = require("world") +log = require("log") +util = require("util") +task = require("task") +server_init = require("server.init") +client_init = require("client.init") +menu_lobby = require("menu.lobby") +NetworkedComponent = require("ecs.networked") +ecs = require("ecs") +ScriptComponent = require("ecs.script") +GraphicsComponent = require("ecs.graphics") +menu = {} +am.eval_js(require("party.qrcodejs.qrcode")) +am.eval_js(require("clipboard_bridge")) +params = am.eval_js("window.CLIPBOARD.get_params()") +win = require("window") +tutorial = require("menu.tutorial") + +buttons = {} +menu.creating = false +buttons_data = { + { + text: "Tutorial" + on: () => + menu.destroy! + tutorial.create! + }, + { + text: "Settings" + on: () => + log.info("Menu pressed") + --menu.destroy! + --require("menu.settings").initialize! + }, + { + text: "Host" + on: () => + log.info("Host pressed") + if menu.creating + return false-- don't allow the user to click twice + menu.creating = true + listener = log.listen((chunk) -> + if chunk.tags.net or chunk.tags.server + @.text.text = chunk.message + --s = s .. chunk.message + --@.text.text = s + ) + co = coroutine.create(() -> + -- Create and initialize the hub + hub = hub_mod.Hub! + hub\initialize! + assert(hub, "Hub was nil") + world.hub = hub + assert(world.hub, "Failed to set hub correctly") + server_init.initialize! + + -- Create and connect the host's client to the hub + client = client_mod.Client("host") + client\initialize! + hub_id = hub.peer.id + log.info("Connecting host client to hub: " .. hub_id, {"net"}) + client\connect_to_hub(hub_id) + while not client.connected + log.info("Connecting to hub",{"net"}) + coroutine.yield! + -- For integration tests: expose a synthetic Join event to JS so + -- the hub/Join flow can be asserted without depending on the + -- underlying WebRTC transport. + if am and am.eval_js and am.to_json + js = string.format("window._hubJoinReceived = true; window._hubJoinData = %s;", am.to_json({name: client.name or "host"})) + am.eval_js(js) + + world.network = client + world.network_mode = "host" + log.info("Hub created with ID: " .. hub_id, {"net"}) + log.defen(listener) + menu_lobby.initialize! + client_init.initialize! + menu.destroy() + menu.creating = false + ) + @.node\action(co) + + } + +} +menu.initialize = () -> + if params.i + peerid = util.code_to_peer(params.i) + co = coroutine.create(() -> + while am.eval_js('typeof(Peer) === "undefined"') + coroutine.yield! + log.info("Found invite param:" .. params.i, {"net"}) + log.info("Got peer id:" .. tostring(peerid), {"net"}) + client = client_mod.Client("player") + client\initialize! + client\connect_to_hub(peerid) + world.network = client + world.network_mode = "client" + log.info("Connected to hub",{"net"}) + menu.destroy! + menu_lobby.initialize! + client_init.initialize! + ) + task.add(co) + elseif params.dev + log.info("Doing game...",{"client"}) + game = require("menu.game") + poems = require("poems") + game.create_graphic({ + youare: "a pawn" + poem: poems[2] + }) + --game.create_graphic({ + --youare: "masked" + --poem: "Roses are red, violets are blue, here's a little game for you" + --}) + game.create_graphic({ + youare: "unmasked" + hint: "Roses are red, violets are blue, here's a little game for you" + }) + else + print("Creating buttons") + starty = 0 + for i = starty, ((#buttons_data-1) * (82 + 32)) + starty, 64 + 32 + buttons[#buttons + 1] = ui.button(-150,i,300,82,buttons_data[#buttons + 1].text) + buttons[#buttons].on = buttons_data[#buttons].on + +menu.destroy = () -> + for button in *buttons + ui.delete(button) + buttons = {} + +menu diff --git a/src/menu/playername.moon b/src/menu/playername.moon new file mode 100644 index 0000000..82a25f1 --- /dev/null +++ b/src/menu/playername.moon @@ -0,0 +1,23 @@ +ui = require("ui") +world = require("world") + +menu = {} + +buttons = {} +menu.initialize = (is_host) -> + text = ui.textbox(-100,16,200,32) + submit = ui.button(-100,-16,200,32,"Use alias") + submit.on = () => + menu.destroy! + print("Got name:", text.text.text) -- <textbox class>.<am.text node>.<actual text lookup> + if is_host + require("worldgen") + print("Building player with", world.network, world.controller, text.text.text) + player = require("player").Player(world.network, world.controller ,text.text.text) + buttons = {text, submit} + +menu.destroy = () -> + for button in *buttons + ui.delete(button) + +menu diff --git a/src/menu/settings.moon b/src/menu/settings.moon new file mode 100644 index 0000000..009fe82 --- /dev/null +++ b/src/menu/settings.moon @@ -0,0 +1,58 @@ +ui = require("ui") +router = require("router") +world = require("world") +settings = require("settings") +menu = {} + +buttons = {} +buttons_data = { + { + text: "Done" + on: () => + menu.destroy! + require("menu.main").initialize! + type: "button" + } + { + text: "Streamer" + on: (depressed) => + --error("depressed:" .. depressed) + settings.streamer = depressed and 0 or 1 + print("streamer is now:", settings.streamer) + type: "boolean" + } + { + text: "Volume" + on: (e) => + --require("worldgen") + --require("menu.join").initialize! + settings.volume = tonumber(@text.text) + type: "slider" + + } +} +menu.initialize = () -> + starty = -200 + for i = starty, ((#buttons_data-1) * (64 + 32)) + starty, 64 + 32 + button_data = buttons_data[#buttons + 1] + if button_data.type == "boolean" + buttons[#buttons + 1] = ui.checkbox(-200,i,400,64,button_data.text) + buttons[#buttons].on = button_data.on + elseif button_data.type == "slider" + buttons[#buttons + 1] = ui.textbox(-200,i,400,64,settings.volume, "volume") + buttons[#buttons].on = button_data.on + elseif button_data.type == "button" + buttons[#buttons + 1] = ui.button(-200,i,400,64,button_data.text) + buttons[#buttons].on = button_data.on + else + error("Unknown button type:" .. button_data.type) + print("making button", #buttons + 1) + + print("intalize") + +menu.destroy = () -> + for button in *buttons + ui.delete(button) + buttons = {} + +menu diff --git a/src/menu/tutorial.moon b/src/menu/tutorial.moon new file mode 100644 index 0000000..58a15d7 --- /dev/null +++ b/src/menu/tutorial.moon @@ -0,0 +1,106 @@ +log = require("log") +ui = require("ui") +sprites = require("sprites") +game = require("menu.game") +window = require("window") +color = require("color") + +x = {} + +screens = { +"This is a game of deception", +"You remember joining the cult, right? +Of course you do, and more importantly, +you remember the words spoken at our founding.", +"We have an uninvited guest here tonight, +they do not know our phrase, +but they do have some idea of what it might be.", +"Our time is short and we must begin +our work, talk with your fellows to find our +uninvited guest.", +"Your host can modify time, roles, and +the number of uninvited guests in the settings", +} +x.node = am.group! +next_but = nil +render_frame = () -> + outline = am.group! + bg = am.rect((-window.width / 2) - 8,(-window.height / 2) - 8, (window.width / 2) + 8, (window.height / 2) + 8, color.am_color.foreground) + bg2 = am.rect((-window.width / 2),(-window.height / 2), (window.width / 2), (window.height / 2), color.am_color.background) + --top= am.line(vec2(-window.width/2,window.height/2),vec2(window.width,window.height/2),20,color.am_color.foreground) + outline\append(bg) + outline\append(bg2) + outline + +x.create = () -> + next_but = ui.button(-160, -400+20, 320, 84, "Next") + screen_i = 1 + hint_t = ui.text(0,400,360,600,screens[screen_i]) + next_but.on = () => + screen_i += 1 + log.info("Advancing tutorial screen, new text is:" .. tostring(screens[screen_i]), {"ui"}) + if hint_t + ui.delete(hint_t) + if not screens[screen_i] + x.destroy! + hint_t = ui.text(0,400,360,600,screens[screen_i]) + if screen_i == 2 + scale = am.scale(0.5) + text_pos = am.translate(0,-64) + text_pos\tag("tutorial") + ui.node\append(text_pos) + text_pos\append(scale) + oldui = ui.node + ui.node = am.group! + scale\append(ui.node) + ui.node\append(render_frame!) + game.create_graphic({ + youare: "a pawn" + poem: "Roses are red, violets are blue, here's a little game for you" + time: 600 + start: am.eval_js("Date.now()") + }) + ui.node("timer").hidden = true + ui.node("timer").paused = true + ui.node = oldui + if screen_i == 3 + prev_graphic = ui.node("tutorial") + if prev_graphic + ui.node\remove(prev_graphic) + scale = am.scale(0.5) + text_pos = am.translate(0,-64) + text_pos\tag("tutorial") + ui.node\append(text_pos) + text_pos\append(scale) + oldui = ui.node + ui.node = am.group! + ui.node\append(render_frame!) + scale\append(ui.node) + game.create_graphic({ + youare: "unmasked" + hint: "Flowers and fun" + time: 600 + start: am.eval_js("Date.now()") + }) + ui.node("timer").hidden = true + ui.node("timer").paused = true + ui.node("keep_looking").hidden = true + ui.node = oldui + if screen_i == 4 + prev_graphic = ui.node("tutorial") + prev_graphic("timer").hidden = false + prev_graphic("timer").paused = false + if sceen_i == 5 + prev_graphic = ui.node("tutorial") + if prev_graphic + ui.node\remove(prev_graphic) + + +x.destroy = () -> + prev_graphic = ui.node("tutorial") + if prev_graphic + ui.node\remove(prev_graphic) + ui.delete(next_but) + require("menu.main").initialize! + +x diff --git a/src/net.moon b/src/net.moon new file mode 100644 index 0000000..49461b6 --- /dev/null +++ b/src/net.moon @@ -0,0 +1,281 @@ +-- Handles the bridge to javascript to do peer-to-peer connections + +log = require("log") +rng = require("rng") +util = require("util") + +net = {} + +initialized = false +initialize = () -> + am.eval_js(require("js_bridge")) + initialized = true + +net.call = (method, args) -> + if not initialized + initialize! + args = args or {} + json_str = am.to_json(args) + log.info("Json string sent to javascript:" .. tostring(json_str), {"net"}) + result = am.eval_js("window.PEER." .. method .. "(" .. am.to_json(args) .. ")") + result + +net.pull_peers = () -> + if not initialized + initialize! + messages = am.eval_js("window.PEER.peer_message_queue") + am.eval_js("window.PEER.peer_message_queue = []") + messages + +net.pull_connections = () -> + if not initialized + initialize! + messages = am.eval_js("window.PEER.connection_message_queue") + am.eval_js("window.PEER.connection_message_queue = []") + messages + +net.pull_creation = () -> + if not initialized + initialize! + creations = am.eval_js("window.PEER.creation_queue") + am.eval_js("window.PEER.creation_queue = []") + creations + +-- Sequence of function(peer, message) | function(connection, message) +-- functions of (peer,message) for peer "open","connection","call","close","disconnected","error" +-- functions of (connection, message) for connection "data","open","close","error" +callbacks = {} +callback_info = {} +-- Map of [string peerid] = Peer +peers = {} + +--Connections are always create js side, this is just it's lua representation +class Connection + @connections = {} + @methods = util.reverse({"data","open","close","error"}) + new: (source, dest) => + @source = source + @dest = dest + @get: (source, dest) => + key = table.concat({source,dest},",") + if @connections[key] + return @connections[key] + @@connections[key] = Connection(source,dest) + @@connections[key] + on: (event, callback) => + if not @@methods[event] + error("Tried to set an unknown event (" .. event .. ") on a connection") + newid = #callbacks + 1 + callbacks[newid] = callback + callback_info[newid] = debug.getinfo(callback) + -- Wait until the JS-side directional connection [source,dest] is ready + while am.eval_js('window.PEER.connections[["' .. @source .. '","' .. @dest .. '"]] == null') + coroutine.yield("Waiting for peer") + -- Attach handler to this directional connection (source -> dest) + net.call("conn_on", {source: @source, dest: @dest, e: event, message: newid}) + send: (msgname, msg) => + -- Send as array: [msgname, msg] + log.info("Sending",{"net"}) + res = net.call("send",{source: @source, dest: @dest, data: {msgname, msg}}) + res + +class Peer + @methods = util.reverse({"open","connection","call","close","disconnected","error"}) + @max_attempts = 4 + @create_timeout = 10 + new: () => + log.info("Creating peer...", {"net"}) + net.call("create") + creations = {} + starttime = am.eval_js("Date.now()") + attempts = 0 + while #creations == 0 and attempts < @@max_attempts + creations = net.pull_creation() + if #creations > 1 + error("Created more than 1 peer at a time, we don't know which one we are") + messages = net.pull_connections() + log.info("Creating peer " .. attempts .. "/" .. tostring(@@max_attempts), {"net"}) + if #messages > 0 + if messages[1] and messages[1].data and messages[1].data.message.type == "network" + -- Try again + net.call("create") + attempts += 1 + starttime = am.eval_js("Date.now()") + else + error(tostring(messages)) + if am.eval_js("Date.now()") - starttime > (@@create_timeout * 1000) + net.call("create") + attempts += 1 + starttime = am.eval_js("Date.now()") + if attempts > @@max_attempts + error("Failed to create host after 4 attempts") + coroutine.yield! + if attempts == @@max_attempts + error("Failed to create peer, check https://status.peerjs.com") + @id = creations[1] + peers[@id] = @ + log.info("Creating peer: " .. @id, {"net"}) + generate_id: () => + os.date("%Y%e") .. rng.numstring(4) + replace_id: () => + log.info("Regenerating id for peer: " .. @id, {"net"}) + -- peers[@id] = nil TODO: uncomment, this breaks when running multiple peers from the same tab. + net.call("delete_peer",{name: @id}) + @id = @generate_id! + peers[@id] = @ + net.call("create", {name: @id}) + on: (event, callback) => + if not @@methods[event] + error("Tried to set an unknown event (" .. event .. ") on a peer.") + newid = #callbacks + 1 + callbacks[newid] = callback + net.call("on",{name: @id, message:newid, e: event}) + + connect: (id, options) => + conn = net.call("connect", {source: @id, dest: id}) + log.info("Got connection: " .. tostring(conn), {"net"}) + Connection\get(conn[1],conn[2]) + + +net.Peer = Peer + +-- A fake peer for testing +fakepeers = {} +fakeconnections = {} +fakecallbacks = {} +channel = require("channel") + +class FakePeer + new: (id) => + if id + @id = id + fakepeers[id] = @ + on: (event, callback) => + newid = #fakecallbacks + 1 + fakecallbacks[newid] = callback + + connect: (id, options) => + conn = channel.FaultyChannel({ + avg_latency: 200 + latency_std: 100 + loss: 0.1 + }) + conn + + +messages = {} +formatcache = {} +message_callbacks = {} +net.register_message = (name, format) -> + assert(type(format) == "table", "Format must be a table") + format.required = format.required or {} + format.optional = format.optional or {} + if not (next(format.required) or next(format.optional)) + log.warn("Message " .. name .. " registered with no fields.") + for set in *({format.required, format.optional}) + for field, type_ in pairs(set) + if type(type_) == "string" + key = string.format("%s\0%s\0%s",name,field,type_) + if not formatcache[key] + formatcache[key] = (any) -> + assert(type(any) == type_, string.format("In message %q %q must be a %q, but was a %q", name, field, type_, type(any))) + set[field] = formatcache[key] + messages[name] = format + message_callbacks[name] = {} + log.info("Registered message type:" .. name, {"net"}) + +net.validate = (name, message) -> + log.debug("Validating message:" .. tostring(message), {"net"}) + assert(type(message) == "table", "Message must be a table") + format = messages[name] + assert(format, "Failed to find a format: " .. name) + required = {} + for field, validate in pairs(format.required) + required[field] = validate + for field, value in pairs(message) + if format.required[field] + required[field](value) + required[field] = nil + if format.optional[field] + format.optional[field](value) + missing = next(required) + if missing + error("Missing required field: " .. missing) + true + +net.listen = (name, callback, id) -> + id = id or {} + message_callbacks[name] = message_callbacks[name] or {} + message_callbacks[name][id] = callback + id + +net.defen = (name, id) -> + message_callbacks[name][id] = nil + +-- net.route = (conn, name, data) -> +-- if message_callbacks[name] +-- for id, callback in pairs(message_callbacks[name]) +-- ret = message_callbacks[name](conn, + +net.rewrite_events = { + connection: (message) -> + -- message.data.data is [server, client]; build Hub-side Connection(server->client) + conn = Connection\get(message.data.data[1], message.data.data[2]) + assert(conn, "Failed to build conn?") + assert(conn.source and conn.dest) + message.data.data = conn +} + +net.pump = () -> + msg_ = net.pull_peers! + if #msg_ > 0 + log.info("Processing " .. tostring(#msg_) .. " peer messages", {"net"}) + for message in *msg_ + --log.info(tostring(message), {"net", message.data.peer}) + if net.rewrite_events[message.data.e] + log.info("Rewriting data due to " .. message.data.e .. " event", {"net", message.data.peer}) + net.rewrite_events[message.data.e](message) + log.info(tostring(message), {"net", message.data.peer}) + if not message.data.peer and message.data.e == "open" + log.info("Setting peerid for a peer that didn't have one " ..tostring(message), {"net"}) + peer = peers[message.data.peer] + assert(peer, "Failed to find peer:" .. message.data.peer .. " peers:" .. tostring(net.peers!)) + callback = callbacks[message.message] + assert(callback, "Failed to find callback " .. message.message .. " on peer " .. message.data.peer) + callback(peer,message.data) + msg_ = net.pull_connections! + if #msg_ > 0 + log.info("Processing " .. tostring(#msg_) .. " connection messages", {"net"}) + for message in *msg_ + --log.info(tostring(message), {"net", message.data.peer}) + -- Extra debug for connection routing + peer = message.data and message.data.peer or "nil" + dest = message.data and message.data.dest or "nil" + inner = message.data and message.data.data or nil + log.debug("NET connection msg peer=" .. tostring(peer) .. + " dest=" .. tostring(dest) .. + " inner=" .. tostring(inner), {"net", "debug"}) + -- For connection events, message.data is a wrapper from JS bridge. + -- The actual payload from Connection:send is in message.data.data. + payload = inner or message.data + -- message.data.peer is the source, message.data.dest is the dest from JS bridge + connection = Connection\get(message.data.peer, message.data.dest) + callback = callbacks[message.message] + if callback + wcall = () -> + callback(connection, payload) + handler = (err) -> + info = callback_info[message.message] + string.format("Failed to call callback defined at %s:%d:\n%s", info.short_src, info.linedefined, debug.traceback(err)) + assert(xpcall(wcall, handler)) + else + log.warn("Failed to find callback for message:" .. tostring(message),{"net"}) + --assert(callback, "Failed to find callback " .. tostring(message.message) .. " for message" .. tostring(message)) + +net.peers = () -> + peers + +net.node = am.group! +initialize! + +net diff --git a/src/party/English-word-lists-parts-of-speech-approximate b/src/party/English-word-lists-parts-of-speech-approximate new file mode 160000 +Subproject a78e65cb52d65662a99fba2806d2a1109a8506a diff --git a/src/party/hc b/src/party/hc new file mode 160000 +Subproject eb1f285cb1cc4d951d90c92b64a4fc85e7ed06b diff --git a/src/party/qrcodejs/qrcode.js b/src/party/qrcodejs/qrcode.js new file mode 100644 index 0000000..c750dd4 --- /dev/null +++ b/src/party/qrcodejs/qrcode.js @@ -0,0 +1 @@ +var QRCode; !function(){function a(a){this.mode=c.MODE_8BIT_BYTE,this.data=a,this.parsedData=[];for(var b=[],d=0,e=this.data.length;e>d;d++){var f=this.data.charCodeAt(d);f>65536?(b[0]=240|(1835008&f)>>>18,b[1]=128|(258048&f)>>>12,b[2]=128|(4032&f)>>>6,b[3]=128|63&f):f>2048?(b[0]=224|(61440&f)>>>12,b[1]=128|(4032&f)>>>6,b[2]=128|63&f):f>128?(b[0]=192|(1984&f)>>>6,b[1]=128|63&f):b[0]=f,this.parsedData=this.parsedData.concat(b)}this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function b(a,b){this.typeNumber=a,this.errorCorrectLevel=b,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}function i(a,b){if(void 0==a.length)throw new Error(a.length+"/"+b);for(var c=0;c<a.length&&0==a[c];)c++;this.num=new Array(a.length-c+b);for(var d=0;d<a.length-c;d++)this.num[d]=a[d+c]}function j(a,b){this.totalCount=a,this.dataCount=b}function k(){this.buffer=[],this.length=0}function m(){return"undefined"!=typeof CanvasRenderingContext2D}function n(){var a=!1,b=navigator.userAgent;return/android/i.test(b)&&(a=!0,aMat=b.toString().match(/android ([0-9]\.[0-9])/i),aMat&&aMat[1]&&(a=parseFloat(aMat[1]))),a}function r(a,b){for(var c=1,e=s(a),f=0,g=l.length;g>=f;f++){var h=0;switch(b){case d.L:h=l[f][0];break;case d.M:h=l[f][1];break;case d.Q:h=l[f][2];break;case d.H:h=l[f][3]}if(h>=e)break;c++}if(c>l.length)throw new Error("Too long data");return c}function s(a){var b=encodeURI(a).toString().replace(/\%[0-9a-fA-F]{2}/g,"a");return b.length+(b.length!=a?3:0)}a.prototype={getLength:function(){return this.parsedData.length},write:function(a){for(var b=0,c=this.parsedData.length;c>b;b++)a.put(this.parsedData[b],8)}},b.prototype={addData:function(b){var c=new a(b);this.dataList.push(c),this.dataCache=null},isDark:function(a,b){if(0>a||this.moduleCount<=a||0>b||this.moduleCount<=b)throw new Error(a+","+b);return this.modules[a][b]},getModuleCount:function(){return this.moduleCount},make:function(){this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17,this.modules=new Array(this.moduleCount);for(var d=0;d<this.moduleCount;d++){this.modules[d]=new Array(this.moduleCount);for(var e=0;e<this.moduleCount;e++)this.modules[d][e]=null}this.setupPositionProbePattern(0,0),this.setupPositionProbePattern(this.moduleCount-7,0),this.setupPositionProbePattern(0,this.moduleCount-7),this.setupPositionAdjustPattern(),this.setupTimingPattern(),this.setupTypeInfo(a,c),this.typeNumber>=7&&this.setupTypeNumber(a),null==this.dataCache&&(this.dataCache=b.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,b){for(var c=-1;7>=c;c++)if(!(-1>=a+c||this.moduleCount<=a+c))for(var d=-1;7>=d;d++)-1>=b+d||this.moduleCount<=b+d||(this.modules[a+c][b+d]=c>=0&&6>=c&&(0==d||6==d)||d>=0&&6>=d&&(0==c||6==c)||c>=2&&4>=c&&d>=2&&4>=d?!0:!1)},getBestMaskPattern:function(){for(var a=0,b=0,c=0;8>c;c++){this.makeImpl(!0,c);var d=f.getLostPoint(this);(0==c||a>d)&&(a=d,b=c)}return b},createMovieClip:function(a,b,c){var d=a.createEmptyMovieClip(b,c),e=1;this.make();for(var f=0;f<this.modules.length;f++)for(var g=f*e,h=0;h<this.modules[f].length;h++){var i=h*e,j=this.modules[f][h];j&&(d.beginFill(0,100),d.moveTo(i,g),d.lineTo(i+e,g),d.lineTo(i+e,g+e),d.lineTo(i,g+e),d.endFill())}return d},setupTimingPattern:function(){for(var a=8;a<this.moduleCount-8;a++)null==this.modules[a][6]&&(this.modules[a][6]=0==a%2);for(var b=8;b<this.moduleCount-8;b++)null==this.modules[6][b]&&(this.modules[6][b]=0==b%2)},setupPositionAdjustPattern:function(){for(var a=f.getPatternPosition(this.typeNumber),b=0;b<a.length;b++)for(var c=0;c<a.length;c++){var d=a[b],e=a[c];if(null==this.modules[d][e])for(var g=-2;2>=g;g++)for(var h=-2;2>=h;h++)this.modules[d+g][e+h]=-2==g||2==g||-2==h||2==h||0==g&&0==h?!0:!1}},setupTypeNumber:function(a){for(var b=f.getBCHTypeNumber(this.typeNumber),c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[Math.floor(c/3)][c%3+this.moduleCount-8-3]=d}for(var c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[c%3+this.moduleCount-8-3][Math.floor(c/3)]=d}},setupTypeInfo:function(a,b){for(var c=this.errorCorrectLevel<<3|b,d=f.getBCHTypeInfo(c),e=0;15>e;e++){var g=!a&&1==(1&d>>e);6>e?this.modules[e][8]=g:8>e?this.modules[e+1][8]=g:this.modules[this.moduleCount-15+e][8]=g}for(var e=0;15>e;e++){var g=!a&&1==(1&d>>e);8>e?this.modules[8][this.moduleCount-e-1]=g:9>e?this.modules[8][15-e-1+1]=g:this.modules[8][15-e-1]=g}this.modules[this.moduleCount-8][8]=!a},mapData:function(a,b){for(var c=-1,d=this.moduleCount-1,e=7,g=0,h=this.moduleCount-1;h>0;h-=2)for(6==h&&h--;;){for(var i=0;2>i;i++)if(null==this.modules[d][h-i]){var j=!1;g<a.length&&(j=1==(1&a[g]>>>e));var k=f.getMask(b,d,h-i);k&&(j=!j),this.modules[d][h-i]=j,e--,-1==e&&(g++,e=7)}if(d+=c,0>d||this.moduleCount<=d){d-=c,c=-c;break}}}},b.PAD0=236,b.PAD1=17,b.createData=function(a,c,d){for(var e=j.getRSBlocks(a,c),g=new k,h=0;h<d.length;h++){var i=d[h];g.put(i.mode,4),g.put(i.getLength(),f.getLengthInBits(i.mode,a)),i.write(g)}for(var l=0,h=0;h<e.length;h++)l+=e[h].dataCount;if(g.getLengthInBits()>8*l)throw new Error("code length overflow. ("+g.getLengthInBits()+">"+8*l+")");for(g.getLengthInBits()+4<=8*l&&g.put(0,4);0!=g.getLengthInBits()%8;)g.putBit(!1);for(;;){if(g.getLengthInBits()>=8*l)break;if(g.put(b.PAD0,8),g.getLengthInBits()>=8*l)break;g.put(b.PAD1,8)}return b.createBytes(g,e)},b.createBytes=function(a,b){for(var c=0,d=0,e=0,g=new Array(b.length),h=new Array(b.length),j=0;j<b.length;j++){var k=b[j].dataCount,l=b[j].totalCount-k;d=Math.max(d,k),e=Math.max(e,l),g[j]=new Array(k);for(var m=0;m<g[j].length;m++)g[j][m]=255&a.buffer[m+c];c+=k;var n=f.getErrorCorrectPolynomial(l),o=new i(g[j],n.getLength()-1),p=o.mod(n);h[j]=new Array(n.getLength()-1);for(var m=0;m<h[j].length;m++){var q=m+p.getLength()-h[j].length;h[j][m]=q>=0?p.get(q):0}}for(var r=0,m=0;m<b.length;m++)r+=b[m].totalCount;for(var s=new Array(r),t=0,m=0;d>m;m++)for(var j=0;j<b.length;j++)m<g[j].length&&(s[t++]=g[j][m]);for(var m=0;e>m;m++)for(var j=0;j<b.length;j++)m<h[j].length&&(s[t++]=h[j][m]);return s};for(var c={MODE_NUMBER:1,MODE_ALPHA_NUM:2,MODE_8BIT_BYTE:4,MODE_KANJI:8},d={L:1,M:0,Q:3,H:2},e={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7},f={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:1335,G18:7973,G15_MASK:21522,getBCHTypeInfo:function(a){for(var b=a<<10;f.getBCHDigit(b)-f.getBCHDigit(f.G15)>=0;)b^=f.G15<<f.getBCHDigit(b)-f.getBCHDigit(f.G15);return(a<<10|b)^f.G15_MASK},getBCHTypeNumber:function(a){for(var b=a<<12;f.getBCHDigit(b)-f.getBCHDigit(f.G18)>=0;)b^=f.G18<<f.getBCHDigit(b)-f.getBCHDigit(f.G18);return a<<12|b},getBCHDigit:function(a){for(var b=0;0!=a;)b++,a>>>=1;return b},getPatternPosition:function(a){return f.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,b,c){switch(a){case e.PATTERN000:return 0==(b+c)%2;case e.PATTERN001:return 0==b%2;case e.PATTERN010:return 0==c%3;case e.PATTERN011:return 0==(b+c)%3;case e.PATTERN100:return 0==(Math.floor(b/2)+Math.floor(c/3))%2;case e.PATTERN101:return 0==b*c%2+b*c%3;case e.PATTERN110:return 0==(b*c%2+b*c%3)%2;case e.PATTERN111:return 0==(b*c%3+(b+c)%2)%2;default:throw new Error("bad maskPattern:"+a)}},getErrorCorrectPolynomial:function(a){for(var b=new i([1],0),c=0;a>c;c++)b=b.multiply(new i([1,g.gexp(c)],0));return b},getLengthInBits:function(a,b){if(b>=1&&10>b)switch(a){case c.MODE_NUMBER:return 10;case c.MODE_ALPHA_NUM:return 9;case c.MODE_8BIT_BYTE:return 8;case c.MODE_KANJI:return 8;default:throw new Error("mode:"+a)}else if(27>b)switch(a){case c.MODE_NUMBER:return 12;case c.MODE_ALPHA_NUM:return 11;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 10;default:throw new Error("mode:"+a)}else{if(!(41>b))throw new Error("type:"+b);switch(a){case c.MODE_NUMBER:return 14;case c.MODE_ALPHA_NUM:return 13;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 12;default:throw new Error("mode:"+a)}}},getLostPoint:function(a){for(var b=a.getModuleCount(),c=0,d=0;b>d;d++)for(var e=0;b>e;e++){for(var f=0,g=a.isDark(d,e),h=-1;1>=h;h++)if(!(0>d+h||d+h>=b))for(var i=-1;1>=i;i++)0>e+i||e+i>=b||(0!=h||0!=i)&&g==a.isDark(d+h,e+i)&&f++;f>5&&(c+=3+f-5)}for(var d=0;b-1>d;d++)for(var e=0;b-1>e;e++){var j=0;a.isDark(d,e)&&j++,a.isDark(d+1,e)&&j++,a.isDark(d,e+1)&&j++,a.isDark(d+1,e+1)&&j++,(0==j||4==j)&&(c+=3)}for(var d=0;b>d;d++)for(var e=0;b-6>e;e++)a.isDark(d,e)&&!a.isDark(d,e+1)&&a.isDark(d,e+2)&&a.isDark(d,e+3)&&a.isDark(d,e+4)&&!a.isDark(d,e+5)&&a.isDark(d,e+6)&&(c+=40);for(var e=0;b>e;e++)for(var d=0;b-6>d;d++)a.isDark(d,e)&&!a.isDark(d+1,e)&&a.isDark(d+2,e)&&a.isDark(d+3,e)&&a.isDark(d+4,e)&&!a.isDark(d+5,e)&&a.isDark(d+6,e)&&(c+=40);for(var k=0,e=0;b>e;e++)for(var d=0;b>d;d++)a.isDark(d,e)&&k++;var l=Math.abs(100*k/b/b-50)/5;return c+=10*l}},g={glog:function(a){if(1>a)throw new Error("glog("+a+")");return g.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;a>=256;)a-=255;return g.EXP_TABLE[a]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},h=0;8>h;h++)g.EXP_TABLE[h]=1<<h;for(var h=8;256>h;h++)g.EXP_TABLE[h]=g.EXP_TABLE[h-4]^g.EXP_TABLE[h-5]^g.EXP_TABLE[h-6]^g.EXP_TABLE[h-8];for(var h=0;255>h;h++)g.LOG_TABLE[g.EXP_TABLE[h]]=h;i.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var b=new Array(this.getLength()+a.getLength()-1),c=0;c<this.getLength();c++)for(var d=0;d<a.getLength();d++)b[c+d]^=g.gexp(g.glog(this.get(c))+g.glog(a.get(d)));return new i(b,0)},mod:function(a){if(this.getLength()-a.getLength()<0)return this;for(var b=g.glog(this.get(0))-g.glog(a.get(0)),c=new Array(this.getLength()),d=0;d<this.getLength();d++)c[d]=this.get(d);for(var d=0;d<a.getLength();d++)c[d]^=g.gexp(g.glog(a.get(d))+b);return new i(c,0).mod(a)}},j.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]],j.getRSBlocks=function(a,b){var c=j.getRsBlockTable(a,b);if(void 0==c)throw new Error("bad rs block @ typeNumber:"+a+"/errorCorrectLevel:"+b);for(var d=c.length/3,e=[],f=0;d>f;f++)for(var g=c[3*f+0],h=c[3*f+1],i=c[3*f+2],k=0;g>k;k++)e.push(new j(h,i));return e},j.getRsBlockTable=function(a,b){switch(b){case d.L:return j.RS_BLOCK_TABLE[4*(a-1)+0];case d.M:return j.RS_BLOCK_TABLE[4*(a-1)+1];case d.Q:return j.RS_BLOCK_TABLE[4*(a-1)+2];case d.H:return j.RS_BLOCK_TABLE[4*(a-1)+3];default:return void 0}},k.prototype={get:function(a){var b=Math.floor(a/8);return 1==(1&this.buffer[b]>>>7-a%8)},put:function(a,b){for(var c=0;b>c;c++)this.putBit(1==(1&a>>>b-c-1))},getLengthInBits:function(){return this.length},putBit:function(a){var b=Math.floor(this.length/8);this.buffer.length<=b&&this.buffer.push(0),a&&(this.buffer[b]|=128>>>this.length%8),this.length++}};var l=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]],o=function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){function g(a,b){var c=document.createElementNS("http://www.w3.org/2000/svg",a);for(var d in b)b.hasOwnProperty(d)&&c.setAttribute(d,b[d]);return c}var b=this._htOption,c=this._el,d=a.getModuleCount();Math.floor(b.width/d),Math.floor(b.height/d),this.clear();var h=g("svg",{viewBox:"0 0 "+String(d)+" "+String(d),width:"100%",height:"100%",fill:b.colorLight});h.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink"),c.appendChild(h),h.appendChild(g("rect",{fill:b.colorDark,width:"1",height:"1",id:"template"}));for(var i=0;d>i;i++)for(var j=0;d>j;j++)if(a.isDark(i,j)){var k=g("use",{x:String(i),y:String(j)});k.setAttributeNS("http://www.w3.org/1999/xlink","href","#template"),h.appendChild(k)}},a.prototype.clear=function(){for(;this._el.hasChildNodes();)this._el.removeChild(this._el.lastChild)},a}(),p="svg"===document.documentElement.tagName.toLowerCase(),q=p?o:m()?function(){function a(){this._elImage.src=this._elCanvas.toDataURL("image/png"),this._elImage.style.display="block",this._elCanvas.style.display="none"}function d(a,b){var c=this;if(c._fFail=b,c._fSuccess=a,null===c._bSupportDataURI){var d=document.createElement("img"),e=function(){c._bSupportDataURI=!1,c._fFail&&_fFail.call(c)},f=function(){c._bSupportDataURI=!0,c._fSuccess&&c._fSuccess.call(c)};return d.onabort=e,d.onerror=e,d.onload=f,d.src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==",void 0}c._bSupportDataURI===!0&&c._fSuccess?c._fSuccess.call(c):c._bSupportDataURI===!1&&c._fFail&&c._fFail.call(c)}if(this._android&&this._android<=2.1){var b=1/window.devicePixelRatio,c=CanvasRenderingContext2D.prototype.drawImage;CanvasRenderingContext2D.prototype.drawImage=function(a,d,e,f,g,h,i,j){if("nodeName"in a&&/img/i.test(a.nodeName))for(var l=arguments.length-1;l>=1;l--)arguments[l]=arguments[l]*b;else"undefined"==typeof j&&(arguments[1]*=b,arguments[2]*=b,arguments[3]*=b,arguments[4]*=b);c.apply(this,arguments)}}var e=function(a,b){this._bIsPainted=!1,this._android=n(),this._htOption=b,this._elCanvas=document.createElement("canvas"),this._elCanvas.width=b.width,this._elCanvas.height=b.height,a.appendChild(this._elCanvas),this._el=a,this._oContext=this._elCanvas.getContext("2d"),this._bIsPainted=!1,this._elImage=document.createElement("img"),this._elImage.style.display="none",this._el.appendChild(this._elImage),this._bSupportDataURI=null};return e.prototype.draw=function(a){var b=this._elImage,c=this._oContext,d=this._htOption,e=a.getModuleCount(),f=d.width/e,g=d.height/e,h=Math.round(f),i=Math.round(g);b.style.display="none",this.clear();for(var j=0;e>j;j++)for(var k=0;e>k;k++){var l=a.isDark(j,k),m=k*f,n=j*g;c.strokeStyle=l?d.colorDark:d.colorLight,c.lineWidth=1,c.fillStyle=l?d.colorDark:d.colorLight,c.fillRect(m,n,f,g),c.strokeRect(Math.floor(m)+.5,Math.floor(n)+.5,h,i),c.strokeRect(Math.ceil(m)-.5,Math.ceil(n)-.5,h,i)}this._bIsPainted=!0},e.prototype.makeImage=function(){this._bIsPainted&&d.call(this,a)},e.prototype.isPainted=function(){return this._bIsPainted},e.prototype.clear=function(){this._oContext.clearRect(0,0,this._elCanvas.width,this._elCanvas.height),this._bIsPainted=!1},e.prototype.round=function(a){return a?Math.floor(1e3*a)/1e3:a},e}():function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){for(var b=this._htOption,c=this._el,d=a.getModuleCount(),e=Math.floor(b.width/d),f=Math.floor(b.height/d),g=['<table style="border:0;border-collapse:collapse;">'],h=0;d>h;h++){g.push("<tr>");for(var i=0;d>i;i++)g.push('<td style="border:0;border-collapse:collapse;padding:0;margin:0;width:'+e+"px;height:"+f+"px;background-color:"+(a.isDark(h,i)?b.colorDark:b.colorLight)+';"></td>');g.push("</tr>")}g.push("</table>"),c.innerHTML=g.join("");var j=c.childNodes[0],k=(b.width-j.offsetWidth)/2,l=(b.height-j.offsetHeight)/2;k>0&&l>0&&(j.style.margin=l+"px "+k+"px")},a.prototype.clear=function(){this._el.innerHTML=""},a}();window.QRCode=function(a,b){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:d.H},"string"==typeof b&&(b={text:b}),b)for(var c in b)this._htOption[c]=b[c];"string"==typeof a&&(a=document.getElementById(a)),this._android=n(),this._el=a,this._oQRCode=null,this._oDrawing=new q(this._el,this._htOption),this._htOption.text&&this.makeCode(this._htOption.text)},window.QRCode.prototype.makeCode=function(a){this._oQRCode=new b(r(a,this._htOption.correctLevel),this._htOption.correctLevel),this._oQRCode.addData(a),this._oQRCode.make(),this._el.title=a,this._oDrawing.draw(this._oQRCode),this.makeImage()},window.QRCode.prototype.makeImage=function(){"function"==typeof this._oDrawing.makeImage&&(!this._android||this._android>=3)&&this._oDrawing.makeImage()},window.QRCode.prototype.clear=function(){this._oDrawing.clear()},window.QRCode.CorrectLevel=d}(); diff --git a/src/party/qrcodejs/qrcode.min.js b/src/party/qrcodejs/qrcode.min.js new file mode 100644 index 0000000..d5f3ca8 --- /dev/null +++ b/src/party/qrcodejs/qrcode.min.js @@ -0,0 +1 @@ +var QRCode;!function(){function a(a){this.mode=c.MODE_8BIT_BYTE,this.data=a,this.parsedData=[];for(var b=[],d=0,e=this.data.length;e>d;d++){var f=this.data.charCodeAt(d);f>65536?(b[0]=240|(1835008&f)>>>18,b[1]=128|(258048&f)>>>12,b[2]=128|(4032&f)>>>6,b[3]=128|63&f):f>2048?(b[0]=224|(61440&f)>>>12,b[1]=128|(4032&f)>>>6,b[2]=128|63&f):f>128?(b[0]=192|(1984&f)>>>6,b[1]=128|63&f):b[0]=f,this.parsedData=this.parsedData.concat(b)}this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function b(a,b){this.typeNumber=a,this.errorCorrectLevel=b,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}function i(a,b){if(void 0==a.length)throw new Error(a.length+"/"+b);for(var c=0;c<a.length&&0==a[c];)c++;this.num=new Array(a.length-c+b);for(var d=0;d<a.length-c;d++)this.num[d]=a[d+c]}function j(a,b){this.totalCount=a,this.dataCount=b}function k(){this.buffer=[],this.length=0}function m(){return"undefined"!=typeof CanvasRenderingContext2D}function n(){var a=!1,b=navigator.userAgent;return/android/i.test(b)&&(a=!0,aMat=b.toString().match(/android ([0-9]\.[0-9])/i),aMat&&aMat[1]&&(a=parseFloat(aMat[1]))),a}function r(a,b){for(var c=1,e=s(a),f=0,g=l.length;g>=f;f++){var h=0;switch(b){case d.L:h=l[f][0];break;case d.M:h=l[f][1];break;case d.Q:h=l[f][2];break;case d.H:h=l[f][3]}if(h>=e)break;c++}if(c>l.length)throw new Error("Too long data");return c}function s(a){var b=encodeURI(a).toString().replace(/\%[0-9a-fA-F]{2}/g,"a");return b.length+(b.length!=a?3:0)}a.prototype={getLength:function(){return this.parsedData.length},write:function(a){for(var b=0,c=this.parsedData.length;c>b;b++)a.put(this.parsedData[b],8)}},b.prototype={addData:function(b){var c=new a(b);this.dataList.push(c),this.dataCache=null},isDark:function(a,b){if(0>a||this.moduleCount<=a||0>b||this.moduleCount<=b)throw new Error(a+","+b);return this.modules[a][b]},getModuleCount:function(){return this.moduleCount},make:function(){this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17,this.modules=new Array(this.moduleCount);for(var d=0;d<this.moduleCount;d++){this.modules[d]=new Array(this.moduleCount);for(var e=0;e<this.moduleCount;e++)this.modules[d][e]=null}this.setupPositionProbePattern(0,0),this.setupPositionProbePattern(this.moduleCount-7,0),this.setupPositionProbePattern(0,this.moduleCount-7),this.setupPositionAdjustPattern(),this.setupTimingPattern(),this.setupTypeInfo(a,c),this.typeNumber>=7&&this.setupTypeNumber(a),null==this.dataCache&&(this.dataCache=b.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,b){for(var c=-1;7>=c;c++)if(!(-1>=a+c||this.moduleCount<=a+c))for(var d=-1;7>=d;d++)-1>=b+d||this.moduleCount<=b+d||(this.modules[a+c][b+d]=c>=0&&6>=c&&(0==d||6==d)||d>=0&&6>=d&&(0==c||6==c)||c>=2&&4>=c&&d>=2&&4>=d?!0:!1)},getBestMaskPattern:function(){for(var a=0,b=0,c=0;8>c;c++){this.makeImpl(!0,c);var d=f.getLostPoint(this);(0==c||a>d)&&(a=d,b=c)}return b},createMovieClip:function(a,b,c){var d=a.createEmptyMovieClip(b,c),e=1;this.make();for(var f=0;f<this.modules.length;f++)for(var g=f*e,h=0;h<this.modules[f].length;h++){var i=h*e,j=this.modules[f][h];j&&(d.beginFill(0,100),d.moveTo(i,g),d.lineTo(i+e,g),d.lineTo(i+e,g+e),d.lineTo(i,g+e),d.endFill())}return d},setupTimingPattern:function(){for(var a=8;a<this.moduleCount-8;a++)null==this.modules[a][6]&&(this.modules[a][6]=0==a%2);for(var b=8;b<this.moduleCount-8;b++)null==this.modules[6][b]&&(this.modules[6][b]=0==b%2)},setupPositionAdjustPattern:function(){for(var a=f.getPatternPosition(this.typeNumber),b=0;b<a.length;b++)for(var c=0;c<a.length;c++){var d=a[b],e=a[c];if(null==this.modules[d][e])for(var g=-2;2>=g;g++)for(var h=-2;2>=h;h++)this.modules[d+g][e+h]=-2==g||2==g||-2==h||2==h||0==g&&0==h?!0:!1}},setupTypeNumber:function(a){for(var b=f.getBCHTypeNumber(this.typeNumber),c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[Math.floor(c/3)][c%3+this.moduleCount-8-3]=d}for(var c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[c%3+this.moduleCount-8-3][Math.floor(c/3)]=d}},setupTypeInfo:function(a,b){for(var c=this.errorCorrectLevel<<3|b,d=f.getBCHTypeInfo(c),e=0;15>e;e++){var g=!a&&1==(1&d>>e);6>e?this.modules[e][8]=g:8>e?this.modules[e+1][8]=g:this.modules[this.moduleCount-15+e][8]=g}for(var e=0;15>e;e++){var g=!a&&1==(1&d>>e);8>e?this.modules[8][this.moduleCount-e-1]=g:9>e?this.modules[8][15-e-1+1]=g:this.modules[8][15-e-1]=g}this.modules[this.moduleCount-8][8]=!a},mapData:function(a,b){for(var c=-1,d=this.moduleCount-1,e=7,g=0,h=this.moduleCount-1;h>0;h-=2)for(6==h&&h--;;){for(var i=0;2>i;i++)if(null==this.modules[d][h-i]){var j=!1;g<a.length&&(j=1==(1&a[g]>>>e));var k=f.getMask(b,d,h-i);k&&(j=!j),this.modules[d][h-i]=j,e--,-1==e&&(g++,e=7)}if(d+=c,0>d||this.moduleCount<=d){d-=c,c=-c;break}}}},b.PAD0=236,b.PAD1=17,b.createData=function(a,c,d){for(var e=j.getRSBlocks(a,c),g=new k,h=0;h<d.length;h++){var i=d[h];g.put(i.mode,4),g.put(i.getLength(),f.getLengthInBits(i.mode,a)),i.write(g)}for(var l=0,h=0;h<e.length;h++)l+=e[h].dataCount;if(g.getLengthInBits()>8*l)throw new Error("code length overflow. ("+g.getLengthInBits()+">"+8*l+")");for(g.getLengthInBits()+4<=8*l&&g.put(0,4);0!=g.getLengthInBits()%8;)g.putBit(!1);for(;;){if(g.getLengthInBits()>=8*l)break;if(g.put(b.PAD0,8),g.getLengthInBits()>=8*l)break;g.put(b.PAD1,8)}return b.createBytes(g,e)},b.createBytes=function(a,b){for(var c=0,d=0,e=0,g=new Array(b.length),h=new Array(b.length),j=0;j<b.length;j++){var k=b[j].dataCount,l=b[j].totalCount-k;d=Math.max(d,k),e=Math.max(e,l),g[j]=new Array(k);for(var m=0;m<g[j].length;m++)g[j][m]=255&a.buffer[m+c];c+=k;var n=f.getErrorCorrectPolynomial(l),o=new i(g[j],n.getLength()-1),p=o.mod(n);h[j]=new Array(n.getLength()-1);for(var m=0;m<h[j].length;m++){var q=m+p.getLength()-h[j].length;h[j][m]=q>=0?p.get(q):0}}for(var r=0,m=0;m<b.length;m++)r+=b[m].totalCount;for(var s=new Array(r),t=0,m=0;d>m;m++)for(var j=0;j<b.length;j++)m<g[j].length&&(s[t++]=g[j][m]);for(var m=0;e>m;m++)for(var j=0;j<b.length;j++)m<h[j].length&&(s[t++]=h[j][m]);return s};for(var c={MODE_NUMBER:1,MODE_ALPHA_NUM:2,MODE_8BIT_BYTE:4,MODE_KANJI:8},d={L:1,M:0,Q:3,H:2},e={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7},f={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:1335,G18:7973,G15_MASK:21522,getBCHTypeInfo:function(a){for(var b=a<<10;f.getBCHDigit(b)-f.getBCHDigit(f.G15)>=0;)b^=f.G15<<f.getBCHDigit(b)-f.getBCHDigit(f.G15);return(a<<10|b)^f.G15_MASK},getBCHTypeNumber:function(a){for(var b=a<<12;f.getBCHDigit(b)-f.getBCHDigit(f.G18)>=0;)b^=f.G18<<f.getBCHDigit(b)-f.getBCHDigit(f.G18);return a<<12|b},getBCHDigit:function(a){for(var b=0;0!=a;)b++,a>>>=1;return b},getPatternPosition:function(a){return f.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,b,c){switch(a){case e.PATTERN000:return 0==(b+c)%2;case e.PATTERN001:return 0==b%2;case e.PATTERN010:return 0==c%3;case e.PATTERN011:return 0==(b+c)%3;case e.PATTERN100:return 0==(Math.floor(b/2)+Math.floor(c/3))%2;case e.PATTERN101:return 0==b*c%2+b*c%3;case e.PATTERN110:return 0==(b*c%2+b*c%3)%2;case e.PATTERN111:return 0==(b*c%3+(b+c)%2)%2;default:throw new Error("bad maskPattern:"+a)}},getErrorCorrectPolynomial:function(a){for(var b=new i([1],0),c=0;a>c;c++)b=b.multiply(new i([1,g.gexp(c)],0));return b},getLengthInBits:function(a,b){if(b>=1&&10>b)switch(a){case c.MODE_NUMBER:return 10;case c.MODE_ALPHA_NUM:return 9;case c.MODE_8BIT_BYTE:return 8;case c.MODE_KANJI:return 8;default:throw new Error("mode:"+a)}else if(27>b)switch(a){case c.MODE_NUMBER:return 12;case c.MODE_ALPHA_NUM:return 11;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 10;default:throw new Error("mode:"+a)}else{if(!(41>b))throw new Error("type:"+b);switch(a){case c.MODE_NUMBER:return 14;case c.MODE_ALPHA_NUM:return 13;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 12;default:throw new Error("mode:"+a)}}},getLostPoint:function(a){for(var b=a.getModuleCount(),c=0,d=0;b>d;d++)for(var e=0;b>e;e++){for(var f=0,g=a.isDark(d,e),h=-1;1>=h;h++)if(!(0>d+h||d+h>=b))for(var i=-1;1>=i;i++)0>e+i||e+i>=b||(0!=h||0!=i)&&g==a.isDark(d+h,e+i)&&f++;f>5&&(c+=3+f-5)}for(var d=0;b-1>d;d++)for(var e=0;b-1>e;e++){var j=0;a.isDark(d,e)&&j++,a.isDark(d+1,e)&&j++,a.isDark(d,e+1)&&j++,a.isDark(d+1,e+1)&&j++,(0==j||4==j)&&(c+=3)}for(var d=0;b>d;d++)for(var e=0;b-6>e;e++)a.isDark(d,e)&&!a.isDark(d,e+1)&&a.isDark(d,e+2)&&a.isDark(d,e+3)&&a.isDark(d,e+4)&&!a.isDark(d,e+5)&&a.isDark(d,e+6)&&(c+=40);for(var e=0;b>e;e++)for(var d=0;b-6>d;d++)a.isDark(d,e)&&!a.isDark(d+1,e)&&a.isDark(d+2,e)&&a.isDark(d+3,e)&&a.isDark(d+4,e)&&!a.isDark(d+5,e)&&a.isDark(d+6,e)&&(c+=40);for(var k=0,e=0;b>e;e++)for(var d=0;b>d;d++)a.isDark(d,e)&&k++;var l=Math.abs(100*k/b/b-50)/5;return c+=10*l}},g={glog:function(a){if(1>a)throw new Error("glog("+a+")");return g.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;a>=256;)a-=255;return g.EXP_TABLE[a]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},h=0;8>h;h++)g.EXP_TABLE[h]=1<<h;for(var h=8;256>h;h++)g.EXP_TABLE[h]=g.EXP_TABLE[h-4]^g.EXP_TABLE[h-5]^g.EXP_TABLE[h-6]^g.EXP_TABLE[h-8];for(var h=0;255>h;h++)g.LOG_TABLE[g.EXP_TABLE[h]]=h;i.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var b=new Array(this.getLength()+a.getLength()-1),c=0;c<this.getLength();c++)for(var d=0;d<a.getLength();d++)b[c+d]^=g.gexp(g.glog(this.get(c))+g.glog(a.get(d)));return new i(b,0)},mod:function(a){if(this.getLength()-a.getLength()<0)return this;for(var b=g.glog(this.get(0))-g.glog(a.get(0)),c=new Array(this.getLength()),d=0;d<this.getLength();d++)c[d]=this.get(d);for(var d=0;d<a.getLength();d++)c[d]^=g.gexp(g.glog(a.get(d))+b);return new i(c,0).mod(a)}},j.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]],j.getRSBlocks=function(a,b){var c=j.getRsBlockTable(a,b);if(void 0==c)throw new Error("bad rs block @ typeNumber:"+a+"/errorCorrectLevel:"+b);for(var d=c.length/3,e=[],f=0;d>f;f++)for(var g=c[3*f+0],h=c[3*f+1],i=c[3*f+2],k=0;g>k;k++)e.push(new j(h,i));return e},j.getRsBlockTable=function(a,b){switch(b){case d.L:return j.RS_BLOCK_TABLE[4*(a-1)+0];case d.M:return j.RS_BLOCK_TABLE[4*(a-1)+1];case d.Q:return j.RS_BLOCK_TABLE[4*(a-1)+2];case d.H:return j.RS_BLOCK_TABLE[4*(a-1)+3];default:return void 0}},k.prototype={get:function(a){var b=Math.floor(a/8);return 1==(1&this.buffer[b]>>>7-a%8)},put:function(a,b){for(var c=0;b>c;c++)this.putBit(1==(1&a>>>b-c-1))},getLengthInBits:function(){return this.length},putBit:function(a){var b=Math.floor(this.length/8);this.buffer.length<=b&&this.buffer.push(0),a&&(this.buffer[b]|=128>>>this.length%8),this.length++}};var l=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]],o=function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){function g(a,b){var c=document.createElementNS("http://www.w3.org/2000/svg",a);for(var d in b)b.hasOwnProperty(d)&&c.setAttribute(d,b[d]);return c}var b=this._htOption,c=this._el,d=a.getModuleCount();Math.floor(b.width/d),Math.floor(b.height/d),this.clear();var h=g("svg",{viewBox:"0 0 "+String(d)+" "+String(d),width:"100%",height:"100%",fill:b.colorLight});h.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink"),c.appendChild(h),h.appendChild(g("rect",{fill:b.colorDark,width:"1",height:"1",id:"template"}));for(var i=0;d>i;i++)for(var j=0;d>j;j++)if(a.isDark(i,j)){var k=g("use",{x:String(i),y:String(j)});k.setAttributeNS("http://www.w3.org/1999/xlink","href","#template"),h.appendChild(k)}},a.prototype.clear=function(){for(;this._el.hasChildNodes();)this._el.removeChild(this._el.lastChild)},a}(),p="svg"===document.documentElement.tagName.toLowerCase(),q=p?o:m()?function(){function a(){this._elImage.src=this._elCanvas.toDataURL("image/png"),this._elImage.style.display="block",this._elCanvas.style.display="none"}function d(a,b){var c=this;if(c._fFail=b,c._fSuccess=a,null===c._bSupportDataURI){var d=document.createElement("img"),e=function(){c._bSupportDataURI=!1,c._fFail&&_fFail.call(c)},f=function(){c._bSupportDataURI=!0,c._fSuccess&&c._fSuccess.call(c)};return d.onabort=e,d.onerror=e,d.onload=f,d.src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==",void 0}c._bSupportDataURI===!0&&c._fSuccess?c._fSuccess.call(c):c._bSupportDataURI===!1&&c._fFail&&c._fFail.call(c)}if(this._android&&this._android<=2.1){var b=1/window.devicePixelRatio,c=CanvasRenderingContext2D.prototype.drawImage;CanvasRenderingContext2D.prototype.drawImage=function(a,d,e,f,g,h,i,j){if("nodeName"in a&&/img/i.test(a.nodeName))for(var l=arguments.length-1;l>=1;l--)arguments[l]=arguments[l]*b;else"undefined"==typeof j&&(arguments[1]*=b,arguments[2]*=b,arguments[3]*=b,arguments[4]*=b);c.apply(this,arguments)}}var e=function(a,b){this._bIsPainted=!1,this._android=n(),this._htOption=b,this._elCanvas=document.createElement("canvas"),this._elCanvas.width=b.width,this._elCanvas.height=b.height,a.appendChild(this._elCanvas),this._el=a,this._oContext=this._elCanvas.getContext("2d"),this._bIsPainted=!1,this._elImage=document.createElement("img"),this._elImage.style.display="none",this._el.appendChild(this._elImage),this._bSupportDataURI=null};return e.prototype.draw=function(a){var b=this._elImage,c=this._oContext,d=this._htOption,e=a.getModuleCount(),f=d.width/e,g=d.height/e,h=Math.round(f),i=Math.round(g);b.style.display="none",this.clear();for(var j=0;e>j;j++)for(var k=0;e>k;k++){var l=a.isDark(j,k),m=k*f,n=j*g;c.strokeStyle=l?d.colorDark:d.colorLight,c.lineWidth=1,c.fillStyle=l?d.colorDark:d.colorLight,c.fillRect(m,n,f,g),c.strokeRect(Math.floor(m)+.5,Math.floor(n)+.5,h,i),c.strokeRect(Math.ceil(m)-.5,Math.ceil(n)-.5,h,i)}this._bIsPainted=!0},e.prototype.makeImage=function(){this._bIsPainted&&d.call(this,a)},e.prototype.isPainted=function(){return this._bIsPainted},e.prototype.clear=function(){this._oContext.clearRect(0,0,this._elCanvas.width,this._elCanvas.height),this._bIsPainted=!1},e.prototype.round=function(a){return a?Math.floor(1e3*a)/1e3:a},e}():function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){for(var b=this._htOption,c=this._el,d=a.getModuleCount(),e=Math.floor(b.width/d),f=Math.floor(b.height/d),g=['<table style="border:0;border-collapse:collapse;">'],h=0;d>h;h++){g.push("<tr>");for(var i=0;d>i;i++)g.push('<td style="border:0;border-collapse:collapse;padding:0;margin:0;width:'+e+"px;height:"+f+"px;background-color:"+(a.isDark(h,i)?b.colorDark:b.colorLight)+';"></td>');g.push("</tr>")}g.push("</table>"),c.innerHTML=g.join("");var j=c.childNodes[0],k=(b.width-j.offsetWidth)/2,l=(b.height-j.offsetHeight)/2;k>0&&l>0&&(j.style.margin=l+"px "+k+"px")},a.prototype.clear=function(){this._el.innerHTML=""},a}();QRCode=function(a,b){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:d.H},"string"==typeof b&&(b={text:b}),b)for(var c in b)this._htOption[c]=b[c];"string"==typeof a&&(a=document.getElementById(a)),this._android=n(),this._el=a,this._oQRCode=null,this._oDrawing=new q(this._el,this._htOption),this._htOption.text&&this.makeCode(this._htOption.text)},QRCode.prototype.makeCode=function(a){this._oQRCode=new b(r(a,this._htOption.correctLevel),this._htOption.correctLevel),this._oQRCode.addData(a),this._oQRCode.make(),this._el.title=a,this._oDrawing.draw(this._oQRCode),this.makeImage()},QRCode.prototype.makeImage=function(){"function"==typeof this._oDrawing.makeImage&&(!this._android||this._android>=3)&&this._oDrawing.makeImage()},QRCode.prototype.clear=function(){this._oDrawing.clear()},QRCode.CorrectLevel=d}(); diff --git a/src/poems.moon b/src/poems.moon new file mode 100644 index 0000000..7da5d21 --- /dev/null +++ b/src/poems.moon @@ -0,0 +1,348 @@ +poems = { + { + text: "Three candles flicker in the moonlight, the little winged things burn up mid flight." + hints: { + "a flying bee, it makes wax", + "a moon is that sits overhead", + "three of a thing" + }, + }, + { + text: "Along the south shore where fierce waves balk, the shadows lengthen across the sand. The sun sinks beneath the loch and our great work lies close at hand." + hints: { + "a sunset over a lake", + "rough waters lapping the beach", + "labor on the beach" + }, + }, + { + text: "We seek an ancient tattered pennon that waves, over rubble that marks antediluvian graves." + hints: { + "military in ruin", + "old architecture", + "weathered gravestones" + }, + }, + { + text: "Strange plants grow where the gardener naps, their roots climb in through tiny cracks." + hints: { + "unusual foliage", + "a Sleeping groundskeeper", + "creeping plants" + }, + }, + { + text: "This lake knows what the stars forgot. It disarms the snare where others were caught." + hints: { + "water that remembers", + "celestial amnesia", + "avoiding a trap" + }, + }, + { + text: "This oaken hall whispers low and deep, the secrets that confessors keep." + hints: { + "wood that speaks", + "murmured revelations", + "the sacrament of confession" + }, + }, + { + text: "In a valley where pallid fog creeps thick and vast, we pursue the scholars of ages past." + hints: { + "mist obscuring view", + "ancient research", + "the pursuit of knowledge" + }, + }, + { + text: "A wire pushed through a lock to open a door, and a knife pushed through my skin to open more." + hints: { + "lockpicking", + "threshold and entry", + "piercing that transforms" + } + }, + { + text: "When bells toll from a distant spire, we begin the pilgrimage for our heart's desire." + hints: { + "ringing in the distance", + "a tall church tower", + "a journey with companions" + }, + }, + { + text: "What dwells in the glassy watered reef, what hides in mirrors and eats belief." + hints: { + "something beneath", + "what lives in reflections", + "dispelling conviction" + }, + }, + { + text: "The cobblestones tremble in rousing air, beneath the boulevard where gaslights flare." + hints: { + "a darkened street", + "stones that hold memory", + "excited atmosphere" + }, + }, + { + text: "The watchman's lantern casts a pallid gleam, where marble statues run through a terrible dream." + hints: { + "a light source", + "dreaming sculptures", + "a hall's custodian" + }, + }, + { + text: "The alders inscribed what none dare say, in cramped attics where moth-eaten scrolls decay." + hints: { + "rotting documents", + "forbidden writing", + "a small loft" + }, + }, + { + text: "Where ivy tendrils embrace the arabesque gate, the gardener's shears lie rusted by fate." + hints: { + "overgrown entrance", + "abandoned tools", + "oxidized implements" + }, + }, + { + text: "The carrion birds circle the blackened spire, as vesper bells announce the choir." + hints: { + "scavenging creatures", + "evening prayer", + "a darkened tower" + }, + }, + { + text: "The architect drew unhallowed planes, in vaulted chambers where torchlight wanes." + hints: { + "fading illumination", + "corrupted geometry ", + "tall rooms" + }, + }, + { + text: "The hoarfrost clings to windowpanes and spreads, where ephemeral fingers trace the words unsaid." + hints: { + "frozen crystal", + "old glass", + "ghostly writing" + }, + }, + { + text: "We shall glimpse what should not be seen, upon the heath where standing stones convene." + hints: { + "ancient monument", + "moorland expanse", + "forbidden sight" + }, + }, + { + text: "The apostate guards his musty tome, in labyrinthine halls that honeycomb." + hints: { + "an aged book", + "a maze of passages", + "a religious traitor" + }, + }, + { + text: "The portcullis descends with a rusted groan, sealing fast what we cannot postpone." + hints: { + "a heavy gate falling", + "corroded metal", + "getting trapped" + }, + }, + { + text: "The frescoes peel and the shadows fade, in cloistered halls where votaries once prayed." + hints: { + "enclosed passages", + "religious devotees", + "crumbling wall paintings" + }, + }, + { + text: "The campanile stands against ashy sky, we ring the bell and await reply." + hints: { + "a lone bell tower", + "heavy grey clouds", + "expecting a message" + }, + }, + { + text: "Beneath the causeway where rose waters flow, the ferryman rows to lands below." + hints: { + "raised pathway", + "red current", + "descent by boat" + }, + }, + { + text: "The apothecary's vials gleam and luminesce, distilling essences of noblesse." + hints: { + "glowing beakers", + "shining potions", + "familial honor" + }, + }, + { + text: "The cartographer's quill trembles as it charts, the borderlands where reason departs." + hints: { + "mapmaking", + "the edges of the known", + "whats beyond the edge of the map" + }, + }, + { + text: "In deep ossuaries where relics rest, the sexton points to what we love best." + hints: { + "bone chambers", + "sacred remains", + "a graveyard keeper" + }, + }, + { + text: "Where wisteria droops from crumbling eaves, the mourner counts and the actuary grieves." + hints: { + "hanging flowers", + "decaying roofline", + "grief tallied" + }, + }, + { + text: "Through colonnades of alabaster stone, processional shadows march to atone." + hints: { + "pale pillars", + "formal darkness", + "spectral parade" + }, + }, + { + text: "The reliquary gleams with purple and pall, enshrining whispers of a silenced cabal." + hints: { + "a sacred container", + "expensive colors", + "ancient murmurs" + }, + }, + { + text: "In dusty galleries where portraits stare, the subjects step from frame to air." + hints: { + "hall of paintings", + "watching eyes", + "art escaping" + }, + }, + { + text: "The mendicant kneels at crossroads there, offering prayers to empty air." + hints: { + "a beggars devotion", + "intersection of paths", + "unanswered plea" + }, + }, + { + text: "Where amaranth blooms when the skies are gray, the groundskeeper has lost his way." + hints: { + "unfading flowers", + "colorless skies", + "wandering caretaker" + }, + }, + { + text: "The sarcophagus lid shifts with grinding tone, revealing naught of polished bone." + hints: { + "stone coffin opening", + "grating movement", + "missing remains" + }, + }, + { + text: "In chancels dim where incense curls, the thurible swings as smoke unfurls." + hints: { + "an altar space", + "rising smoke", + "a burning censer" + }, + }, + { + text: "The oubliette waits with patient maw, forgotten by all but ancient law." + hints: { + "dungeon pit", + "hungry opening", + "archaic justice" + }, + }, + { + text: "The indentations reveal what the scribes erased, truths beneath truths carefully placed." + hints: { + "overwritten text", + "hidden words", + "layered secrets" + }, + }, + { + text: "The cenotaph stands for those not found, their bones scattered on unknown ground." + hints: { + "an empty tomb", + "the missing dead", + "a far off land" + }, + }, + { + text: "Through clerestory windows wan light falls, illuminating naught but barren walls." + hints: { + "high church windows", + "feeble radiance", + "empty surfaces" + }, + }, + { + text: "The dovecote stands though the doves have fled, roosting now where angels tread." + hints: { + "bird shelter", + "abandoned dwelling", + "dead avians" + }, + }, + { + text: "Specimens watch as centuries pass, in vitrines sealed with leaden glass." + hints: { + "display cases", + "preserved creatures", + "observing time" + }, + }, + { + text: "The caryatids bear their burden still, as the temple crumbles to dust and nil." + hints: { + "sculpted maidens", + "supporting columns", + "a sacred ruin" + }, + }, + { + text: "Where foxglove sways by lichened stone, the healer grinds what we have grown, beneath the skin, beneath the bone." + hints: { + "poisonous flowers", + "a moss-covered rock", + "grim remedies" + }, + }, + { + text: "In baptisteries where fonts run dry, the faithful await their final reply." + hints: { + "christening halls", + "empty basins", + "unanswered prayers" + }, + }, +} + +poems diff --git a/src/prefab/cabin.moon b/src/prefab/cabin.moon new file mode 100644 index 0000000..1358c5a --- /dev/null +++ b/src/prefab/cabin.moon @@ -0,0 +1,79 @@ +world = require("world") +sprites = require("world.sprites") +ecs = require("ecs") + +class CabinGraphicsComponent extends world.GraphicsComponent + -- + -- ##### + -- # # + -- ##/## + -- 3x3 floor, 1x3 * 3 walls, 1x1 * 2 door side walls, 1x1 door + buf_size: () => + (6*9) + (6*9) + (6* 2) + (6 * 1) + + populate_buf: (geom_view, normal_view, offset) => + z1 = -0.01 + z2 = -1 + iuv = sprites.wall_inside_normal + ouv = sprites.wall_outside_normal + fuv = sprites.floor_normal + h1 = sprites.help_1 + h2 = sprites.help_2 + h3 = sprites.help_3 + h1_r = false + h2_r = false + h3_r = false + wall = (geom, uv, offset, start, finish, texture) -> + geom[offset + 0] = vec3(start.x, start.y, z1) + geom[offset + 1] = vec3(start.x, start.y, z2) + geom[offset + 2] = vec3(finish.x, finish.y, z2) + geom[offset + 3] = vec3(finish.x, finish.y, z2) + geom[offset + 4] = vec3(finish.x, finish.y, z1) + geom[offset + 5] = vec3(start.x, start.y, z1) + uv[offset+0] = vec2(texture.s1,texture.t1) + uv[offset+1] = vec2(texture.s1,texture.t2) + uv[offset+2] = vec2(texture.s2,texture.t2) + uv[offset+3] = vec2(texture.s2,texture.t2) + uv[offset+4] = vec2(texture.s2,texture.t1) + uv[offset+5] = vec2(texture.s1,texture.t1) + + floor = (geom, uv, offset, start, finish) -> + tuv = fuv + if not h1_r + tuv = h1 + h1_r = true + elseif not h2_r + tuv = h2 + h2_r = true + elseif not h3_r + tuv = h3 + h3_r = true + geom[offset + 0] = vec3(start.x,start.y,z1) + geom[offset + 1] = vec3(start.x,finish.y,z1) + geom[offset + 2] = vec3(finish.x,finish.y,z1) + geom[offset + 3] = vec3(finish.x,finish.y,z1) + geom[offset + 4] = vec3(finish.x,start.y,z1) + geom[offset + 5] = vec3(start.x,start.y,z1) + normal_view[offset + 0] = vec2(tuv.s1, tuv.t1) + normal_view[offset + 1] = vec2(tuv.s1, tuv.t2) + normal_view[offset + 2] = vec2(tuv.s2, tuv.t2) + normal_view[offset + 3] = vec2(tuv.s2, tuv.t2) + normal_view[offset + 4] = vec2(tuv.s2, tuv.t1) + normal_view[offset + 5] = vec2(tuv.s1, tuv.t1) + + --left wall + j = 1 + wall(geom_view, normal_view, j, vec2(-2,-2),vec2(-2,0), sprites.wall_inside_normal) + j += 6 + for floorx = 1,3 + for floory = 1,3 + floor(geom_view, normal_view, j, vec2(-2 + (2*floorx), -2 + (2*floory)), vec2(2*floorx, 2*floory)) + j += 6 + + + +cabin = ecs.Entity("cabin",{ + graphic: CabinGraphicsComponent("graphic") +}) + +cabin diff --git a/src/prefab/hall.moon b/src/prefab/hall.moon new file mode 100644 index 0000000..59dbea0 --- /dev/null +++ b/src/prefab/hall.moon @@ -0,0 +1,21 @@ +util = require("util") + +-- Halls run from one point to another, the start and end points are in the +-- middle of the hallway. +-- "floor_gen" can be a string (the sprites texture to use)a +-- or a function (passed the "Hall" object to generate the texture for that segment. +class Hall + new: (tbl) => + util.typecheck(tbl, + "startx", "number", + "starty", "number", + "endx", "number", + "endy", "number", + "width", "number" + ) + assert(tbl.floor_gen, "Hall requires a 'floor_gen' attribute") + if type(tbl.floor_gen) == "function" + @floor_gen = tbl.floor_gen + elseif type(tbl.floor_gen) == "string" + @floor_gen = () => + tbl.floor_gen diff --git a/src/prefab/lobby.moon b/src/prefab/lobby.moon new file mode 100644 index 0000000..a560469 --- /dev/null +++ b/src/prefab/lobby.moon @@ -0,0 +1,112 @@ +sprites = require("sprites") +GraphicsComponent = require("ecs.graphics") +log = require("log") + +sd = sprites.floor +w1 = sprites.wall + +floor = (x, y) -> + r = { + --floor + vec3(x,y,0), + vec3(x+1,y,0), + vec3(x+1,y-1,0), + vec3(x+1,y-1,0), + vec3(x,y-1,0), + vec3(x,y,0) + } + r + +floor_uv = (x, y) -> + r = { + vec4(sd.s1,sd.t1,1,1), + vec4(sd.s2,sd.t1,1,1), + vec4(sd.s2,sd.t2,1,1), + vec4(sd.s2,sd.t2,1,1), + vec4(sd.s1,sd.t2,1,1), + vec4(sd.s1,sd.t1,1,1) + } + r + + +left_wall = (x,y) -> + r = { + -- Left wall + vec3(x+1,y,1), + vec3(x+1,y,0), + vec3(x+1,y-1,0), + vec3(x+1,y-1,0), + vec3(x+1,y-1,1), + vec3(x+1,y,1) + } + r + +left_wall_uv = (x,y) -> + r = { + vec4(w1.s1,w1.t1,1,1), + vec4(w1.s1,w1.t2,1,1), + vec4(w1.s2,w1.t2,1,1), + vec4(w1.s2,w1.t2,1,1), + vec4(w1.s2,w1.t1,1,1), + vec4(w1.s1,w1.t1,1,1) + } + r + +right_wall = (x,y) -> + r = { + --Right wall + vec3(x,y,0), + vec3(x,y,1), + vec3(x,y-1,1), + vec3(x,y-1,1), + vec3(x,y-1,0), + vec3(x,y,0) + } + r + +right_wall_uv = (x,y) -> + r = { + vec4(w1.s2,w1.t2,1,1), + vec4(w1.s2,w1.t1,1,1), + vec4(w1.s1,w1.t1,1,1), + vec4(w1.s1,w1.t1,1,1), + vec4(w1.s1,w1.t2,1,1), + vec4(w1.s2,w1.t2,1,1) + } + r + +compute = () -> + geom = {} + uv = {} + for x = 0,2 + for y = 0,2 + for _,v in ipairs(floor(x,y)) + table.insert(geom,v) + for _,v in ipairs(floor_uv(x,y)) + table.insert(uv,v) + for i = 0,2 -- room's left wall + for _,v in ipairs(left_wall(-1,i)) + table.insert(geom,v) + for _,v in ipairs(left_wall_uv(-1,i)) + table.insert(uv,v) + for i = 0,2 -- room's right wall + for _,v in ipairs(right_wall(3,i)) + table.insert(geom,v) + for _,v in ipairs(right_wall_uv(3,i)) + table.insert(uv,v) + geom, uv, #geom / 3 + + +class LobbyGraphic extends GraphicsComponent + new: (name, properties) => + @geom, @uv, @n_tris = compute! + properties.graphic = sprites.floor.texture + super(name, properties) + tris: () => + @n_tris + populate_buf: (geom_view, uv_view, offset) => + log.info("Creating lobby graphic" .. tostring(@geom),{"level","graphic","lobby"}) + geom_view\set(@geom, offset, @n_tris * 3) + uv_view\set(@uv, offset, @n_tris * 3) + +LobbyGraphic diff --git a/src/prefab/room.moon b/src/prefab/room.moon new file mode 100644 index 0000000..c041bb8 --- /dev/null +++ b/src/prefab/room.moon @@ -0,0 +1,10 @@ + + +class Room + new: (x,y,width,height) => + @x = x + @y = y + @width = width + @height = height + @hallways = {} + diff --git a/src/prefab/spawn.moon b/src/prefab/spawn.moon new file mode 100644 index 0000000..516c72a --- /dev/null +++ b/src/prefab/spawn.moon @@ -0,0 +1,3 @@ +-- Spawnpoint? + +class Spawnpoint extends Room diff --git a/src/prefab/worldgen.moon b/src/prefab/worldgen.moon new file mode 100644 index 0000000..1d000a6 --- /dev/null +++ b/src/prefab/worldgen.moon @@ -0,0 +1,58 @@ +args = {...} +require("rng") +self = args[1] + +gen = {} + +-- Logical worldgen +-- Strategy: splatter some rooms on a canvas +-- rooms are {location, width, height, specialty} +-- splatter some large rooms first, in a mostly-straight line, +-- then some medium rooms with a larger spread +-- then a bunch of small rooms with a large spread +-- then connect each room with nearby neighbors + +room_sizes = { + -- avgx, stdx, avgy, stdy + large: { + avg_w: 40 + std_w: 10 + avg_l: 40 + std_l: 10 + } + medium: { + avg_w: 20 + std_w: 5 + avg_l: 20 + std_l: 5 + } + small: { + avg_w: 8 + std_w: 3 + avg_l: 8 + std_l: 3 + } +} +level = { + avg_w: 1000 + std_w: 200 + avg_h: 1000 + std_h: 200 +} +gen.level = (seed) -> + random_gen = rng.generator(seed) + normal = (avg, std, gen) => + -- Box-Muller transform + bm = math.sqrt(-2 * math.log(gen())) * math.cos(2 * math.pi * gen()) + -- Box-Muller gives us std = e^-0.5 , avg = 0 + ((bm / math.exp(-1/2)) * std) + avg + width = random_gen(avg_w, std_w, random_gen) + height = random_gen(avg_h, std_h, random_gen) + rooms = {} + -- Pick a a direction to splatter + direction = random_gen() * 2 * math.pi + --rooms[0] = + + + +gen diff --git a/src/preload.lua b/src/preload.lua new file mode 100644 index 0000000..1085f63 --- /dev/null +++ b/src/preload.lua @@ -0,0 +1,107 @@ +-- Stuff to load before everything else + +--[[ +rewrite traceback function to map file names and line numbers to moonscript +]] +local require_order = {} +local old_traceback = debug.traceback +debug.traceback = function(...) + local orig_traceback = old_traceback(...) + local noprint = {} + return orig_traceback:gsub("([%w/_]+%.lua):(%d+):",function(filename, linenum) + --If our file is not moonscript, we won't have a debug file + local debugfile = am.load_string(filename .. ".X") + if not debugfile then + return filename .. ":" .. linenum .. ":" + end + debugfile = debugfile .. "\n" + for line in debugfile:gmatch("([^\n]+)\n") do + local _,_,pos,lua,moon = line:find("(%d+)%s+(%d+):%b[] >> (%d+)") + if pos and lua and moon and tonumber(linenum) == tonumber(lua) then + filename = filename:gsub(".lua$",".moon") + linenum = moon + break + end + end + return string.format("%s:%d:", filename, linenum) + end) --.. "\nRequire order: [" .. table.concat(require_order, ",\n") .. "]" +end + +local old_require = require +local required = {} +require = function(...) + args = {...} + if not required[args[1]] then + required[args[1]] = true + table.insert(require_order, args[1]) + end + return old_require(...) +end + +--[[ +Display where print statements are comming from + +local oldprint = print +print = function(...) + error("Print") + oldprint(debug.traceback()) +end +]] + +-- Override tostring to display more info about the table +local old_tostring = tostring +local numtabs = 0 +local printed_tables = {} +local function tostring_helper(el) + assert(type(el) == "table", "Tried to call helper with something that was not a table, it was a " .. type(el)) + local mt = getmetatable(el) + if mt and mt.__tostring then + return mt.__tostring(el) + elseif printed_tables[el] == true then + return old_tostring(el) + else + printed_tables[el] = true + numtabs = numtabs + 1 + local strbuilder = {"{"} + for k,v in pairs(el) do + local key,value + if type(k) == "table" then + key = tostring_helper(k) + else + key = old_tostring(k) + end + if type(v) == "table" then + value = tostring_helper(v) + else + value = old_tostring(v) + end + strbuilder[#strbuilder + 1] = string.format("%s%s : %s", string.rep("\t",numtabs), key, value) + end + strbuilder[#strbuilder + 1] = string.rep("\t",numtabs - 1) .. "}" + numtabs = numtabs - 1 + return table.concat(strbuilder,"\n") + end + +end +function tostring(el) + printed_tables = {} + if type(el) == "table" then + return tostring_helper(el) + end + return old_tostring(el) +end + +-- Override global error function to log errors through the log system +local old_error = error +function error(message, level) + -- Get the log module if available + local log_available, log = pcall(require, "log") + if log_available and log and log.error then + -- Get full traceback + local traceback = debug.traceback(tostring(message), 2) + log.error(traceback, {"error"}) + end + -- Call the original error function + old_error(message, level or 2) +end + diff --git a/src/rng.moon b/src/rng.moon new file mode 100644 index 0000000..d7606f9 --- /dev/null +++ b/src/rng.moon @@ -0,0 +1,46 @@ +-- Contains pseudo-random number generators, and some helper functions + +rng = {} +totally_random_seed = tonumber(os.date("%Y%H%M%S")) +math.randomseed(totally_random_seed) + +-- same syntax as math.random, if m and n are passed, they are lower and upper bounds +-- if only m is passed, it is the upper bound +-- if neither is passed, between 0 and 1 +-- Example: +-- local rng = require("rng") +-- local generator1 = rng.generator() +-- local random1 = generator1() +-- local generator2 = rng.generator() +-- local random2 = generator2() +-- assert(random1, random2) +rng.generator = (seed, m, n) -> + seed = seed or tonumber(os.date("%Y%S")) + co = coroutine.wrap(() -> + while true + math.randomseed(seed) + if m and n + seed = math.random(m,n) + elseif m + seed = math.random(m) + else + seed = math.random() + coroutine.yield(seed) + ) + co, seed + +rng.randomstring = (charset, length) -> + t = {} + charset_len = #charset + for i = 1, length + char = math.random(charset_len) + t[i] = charset\sub(char,char) + table.concat(t) + +rng.hexstring = (length) -> + rng.randomstring("0123456789ABCDEF", length) + +rng.numstring = (length) -> + rng.randomstring("0123456789", length) + +rng diff --git a/src/server/init.moon b/src/server/init.moon new file mode 100644 index 0000000..9c2c22f --- /dev/null +++ b/src/server/init.moon @@ -0,0 +1,143 @@ + +ecs = require("ecs") +log = require("log") +world = require("world") +net = require("net") +NetworkedComponent = require("ecs.networked") +PredictedComponent = require("ecs.predicted") +player_movement = require("shared.player_movement") + +x = {} + +x.initialize = () -> + world.domain = "server" + if not world.hub + log.error("Running server init, but no world hub has been created",{"server"}) + print("World was:",world) + error("World.hub has not been set") + + log.info("Server initalized",{"server"}) + world.level_sync.name = "levels.lobby" + + pawns = {} -- peerid to entity lookup + net.register_message("RequestLevel",{}) + world.hub\listen("RequestLevel", "RespondLevel", (from_client, data) -> + log.info("Got reqeust for level info" .. tostring(from_client), {"net","server"}) + world.hub\send(from_client, "RespondLevel",{ + name: world.level_sync.name + data: world.level_sync.data + }) + ) + net.register_message("RequestPeers",{}) + world.hub\listen("RequestPeers", "RespondPeers", (from_client, data) -> + log.info("Got request for player info" .. tostring(from_client), {"net","server"}) + players = {} + for peerid, ent in pairs(world.level_sync.peers) + players[peerid] = ent\get("net")\pack! + log.info("Got data for player" .. peerid .. ":" .. players[peerid], {"net","server"}) + world.hub\send(from_client, "RespondPeers", players) + ) + net.register_message("RequestEntities",{}) + world\check! + world.hub\listen("RequestEntities", "RespondEntities", (from_client, data) -> + log.info("Got request for entities from " .. tostring(from_client), {"net","server"}) + log.info("Responding with:" .. tostring(world.level_sync.ents), {"net","server"}) + log.info("At request for entities, level is " .. tostring(world.level_sync.name), {"net","server"}) + if world.level_sync.name == "levels.game" + return -- Don't sync here, instead send a R + world\check! + -- Simplified way to send entities + nents = {} + for i, entity in pairs(world.level_sync.ents) + ent_net = entity\get("net") + if not ent_net + error("Server entity does not have a net component:" .. tostring(entity)) + if not ent_net.properties.type + error("Failed to find net entity type for " .. tostring(net_ent) .. " net component was: " .. tostring(ent_net.net_properties)) + nents[i] = ent_net\pack! + world.hub\send(from_client, "RespondEntities", nents) + ) + world.hub\listen("Join", "CreatePawn", (from_client, data) -> + assert(world.hub, "Join should only be called on the server") + log.info("Got player joining:" .. tostring(from_client), {"net","server"}) + --pawn = ecs.Entity! + --spawn_loc = assert(world.level_sync.ref\get_spawn_location!, "Failed to find inital spawn location") + --net = NetworkedComponent("net",{ + --type: "player" + --pos: spawn_loc + --ang: 0 + --vel: {0,0,0} + --acc: {0,0,0} + --player_name: "name" + --last_update: am.eval_js("Date.now()") + --peerid: from_client + --}) + --pawn\add(net, "net") + --pred = PredictedComponent("pred",{acc:{0,0,0}, vel: {0,0,0}, pos: {0,0,0}}, "net", player_movement) + --pawn\add(pred, "pred") + --world.level_sync.peers[from_client] = pawn + + --world.hub\broadcast("CreatePawn", net\pack!) + + -- Surface Join events to the browser for integration tests. + -- Guarded so it is a no-op in non-HTML environments. + if am and am.eval_js and am.to_json + js = string.format("window._hubJoinReceived = true; window._hubJoinData = %s;", am.to_json(data or {})) + am.eval_js(js) + ) + require("levels.lobby") + lobby = ecs.Entity! + lobby\add(ecs.NetworkedComponent("lobby_peer",{ + type: "level", + level_name: world.level_sync.name + level_data: {world.hub.peer.id} + }), "net") + world.level_sync.ref = { + id: "level from server/init.moon" + get_spawn_location: () -> + {0,0,0} + } + net.register_message("SuggestPlayerUpdate",{ + optional: { + pos: "table" -- x, y, z + ang: "number" -- 0->360 + vel: "table" -- x, y, z + acc: "table" -- x, y, z + player_name: "string" + last_update: "number" + } + }) + world.hub\listen("SuggestPlayerUpdate","UpdateName",(from_client, data) -> + log.debug("Got player update from " .. from_client .. ":" .. tostring(data), {"net","server","player"}) + net = world.level_sync.peers[from_client]\get("net") + if not net + error("Got message from client" .. tostring(from_client) .. " but no such client exists!") + if data.player_name + net.properties.player_name = data.player_name + if data.pos + net.properties.pos = data.pos + if data.vel + net.properties.vel = data.vel + if data.last_update + net.properties.last_update = data.last_update + if data.acc + for i = 1,3 + if not (data.acc[i] and type(data.acc[i] == "number")) + log.warn("Peer " .. from_client .. " sent bad acceleration", {"net","player"}) + return + v = vec3(data.acc[1], data.acc[2], data.acc[3]) + if math.length(v) > 1 + log.warn("Peer " .. from_client .. " sent too much acceleration: " .. tostring(math.length(v)),{"net","player"}) + return + net.properties.acc = data.acc + ) + world.hub\listen("RequestRole","Request role", (clientid, _) -> + client_data = world.level_sync.client_data + log.info("Responding with role:" .. tostring(client_data[clientid]), {"net","server"}) + world.hub\send(clientid, "RespondRole", client_data[clientid]) + ) + + + world.domain = "client" + +x diff --git a/src/settings.moon b/src/settings.moon new file mode 100644 index 0000000..e2a5452 --- /dev/null +++ b/src/settings.moon @@ -0,0 +1,19 @@ +settings = {} + +settings.n_unmasked = 1 +settings.game_time = 600 --in seconds + +-- Ideas for extra roles that can be turned on: +-- Executioner (can reveal if anyone is killed) +-- Sleeper Agent (reveal once someone says a codeword) +-- Founder (Reveal any time, peek at someone's mask) +-- Tardy (Reveal in the last minute of gameplay) +-- Secret Police (Voting out does not count against the number of votes the cult has) +-- Enforcer (Reveal any time, kill any other player) +-- Recruiter (Knows 2 other players that are not the masked player) +-- Fool (Tries to be killed, all other players lose) +-- Necromancer (Reveals after a player is killed and brings a dead player back to life (allow 1 more wrong guess)) +-- True Beliver (Takes on the role of any other cultist after they are killed) +-- Exceptional sacrifice (Reveals after players run out of attempts to find the unmasked, cultists get 1 more guess) + +settings diff --git a/src/shader_shim.moon b/src/shader_shim.moon new file mode 100644 index 0000000..76e0aec --- /dev/null +++ b/src/shader_shim.moon @@ -0,0 +1,22 @@ +-- Sometimes we want to compile shaders, use the syntax +-- {variable} to adress a variable +win = require("window") +inputs = { + "@width": win.width + "@height": win.height +} + +shaders = setmetatable({},{ + __index:(self, key) -> + vert_name = "shaders/" .. key .. ".vert" + frag_name = "shaders/" .. key .. ".frag" + vert = assert(am.load_string(vert_name), "Failed to find " .. vert_name) + frag = assert(am.load_string(frag_name), "Failed to find " .. frag_name) + vert_subbed = vert\gsub("@%b{}",(n) -> tostring(inputs[n])) + frag_subbed = frag\gsub("@%b{}",(n) -> tostring(inputs[n])) + succ, program = pcall(am.program, vert_subbed, frag_subbed) + if not succ + error(string.format("Failed compiling shader %q: %s vertex shader: %s fragment shader: %s", key, program, vert_subbed, frag_subbed)) + am.use_program(am.program(vert_subbed, frag_subbed)) +}) +shaders diff --git a/src/shaders/lake.frag b/src/shaders/lake.frag new file mode 100644 index 0000000..c1c23f0 --- /dev/null +++ b/src/shaders/lake.frag @@ -0,0 +1,93 @@ +precision mediump float; +uniform vec4 black; +uniform vec4 outline; +uniform vec4 highlight; +uniform vec4 foreground; +uniform vec4 midground; +uniform vec4 shadow; +uniform vec4 background; +uniform vec4 lamp1; //vec3 position, float strength +uniform vec4 lamp2; +uniform vec4 lamp3; // max 3 lamps per shaded vertex +uniform vec4 lamp4; +uniform vec4 lamp5; +uniform vec4 lamp6; +uniform vec4 lamp7; +uniform vec4 lamp8; +uniform float streamer; // turn off the noise in the light +uniform float time; //used for noise +uniform sampler2D atlas; +uniform sampler2D previous; +uniform float nlamps; +varying vec2 worldxy; +varying vec2 norm; + +// Author @patriciogv - 2015 +float random (vec2 st) { + return fract( + sin( + dot(st.xy,vec2(12.9898,78.233)) + ) * + 43758.5453123 + ); +} + +// stolen from https://www.shadertoy.com/view/Msf3WH +float noise( in vec2 p ) +{ + const float K1 = 0.366025404; // (sqrt(3)-1)/2; + const float K2 = 0.211324865; // (3-sqrt(3))/6; + + vec2 i = floor( p + (p.x+p.y)*K1 ); + vec2 a = p - i + (i.x+i.y)*K2; + float m = step(a.y,a.x); + vec2 o = vec2(m,1.0-m); + vec2 b = a - o + K2; + vec2 c = a - 1.0 + 2.0*K2; + vec3 h = max( 0.5-vec3(dot(a,a), dot(b,b), dot(c,c) ), 0.0 ); + vec3 n = h*h*h*h*vec3( dot(a,random(i+0.0)), dot(b,random(i+o)), dot(c,random(i+1.0))); + return dot( n, vec3(70.0) ); +} + +void main() { + vec4 coord = gl_FragCoord + vec4(worldxy * 256., 0, 0); + /* + coord.x -= worldxy.x; + coord.y -= worldxy.y; + */ + //coord = coord / 1000.; + // calculate color at this pixel + vec4 normal = texture2D(atlas, norm); + float color = 0.; + vec4 lamp1_norm = lamp1 * 256.; + color += lamp1_norm.w - distance(lamp1_norm.xy - worldxy, coord.xy); + color = max(color,(lamp2.w * 256.) - distance((lamp2.xy * 256.) - worldxy, coord.xy)); + color = max(color,(lamp3.w * 256.) - distance((lamp3.xy * 256.) - worldxy, coord.xy)); + // divide to get a normalized color + //color /= (256. * max(max(lamp1.w, lamp2.w), lamp3.w)); + color /= 256. * 5.; + //color = sqrt(color / 2046.); + // Sett the normal texture under our lamplight + color = dot(vec4(color),normal) / 1.; + // make the colors fuzzy near the border (or don't if we're streaming) + float rng = random(vec2(coord.x, coord.y) + vec2(color, time)); + color -= (pow(rng / 1.3, 10. * color)) * streamer; + // add noise to the water + /* */ + if(color > 1.) + gl_FragColor = highlight * normal.a; + else if(color > 0.8) + gl_FragColor = foreground * normal.a; + else if(color > 0.6) + gl_FragColor = midground * normal.a; + else if(color > 0.4) + gl_FragColor = background * normal.a; + else if(color > 0.2) + gl_FragColor = shadow * normal.a; + else + gl_FragColor = black * normal.a; + /* + gl_FragColor = normal* vec4(color , color, color,1.); + */ + //gl_FragColor = normal* vec4(color , color / 10., color / 1024.,1.); +} diff --git a/src/shaders/lake.moon b/src/shaders/lake.moon new file mode 100644 index 0000000..bac2c42 --- /dev/null +++ b/src/shaders/lake.moon @@ -0,0 +1,29 @@ + +shader_shim = require("shader_shim") +win = require("window") +world = require("world") + +node = shader_shim.lake\append(am.bind({ + MV: mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + (-win.width / 2), (-win.height/2), 0, 1 + ), + P: mat4(1) + lake: am.vec3_array({}) + light1: am.vec4_array({}) + light2: am.vec4_array({}) + light3: am.vec4_array({}) + world_x: 0 + world_y: 0 + time: am.current_time() +}))\append(am.draw("triangles")) + +node\action((self) -> + self("bind").time = am.current_time! + self("bind").world_x = world.world_x + self("bind").world_y = world.world_y +) + +node diff --git a/src/shaders/lake.vert b/src/shaders/lake.vert new file mode 100644 index 0000000..2745cf9 --- /dev/null +++ b/src/shaders/lake.vert @@ -0,0 +1,13 @@ +precision highp float; +attribute vec3 lake; +attribute vec4 lamp1; //position, strength +attribute vec4 lamp2; +attribute vec4 lamp3; // max 3 lamps per shaded vertex +uniform float time; //used for noise +uniform float world_x; +uniform float world_y; +uniform mat4 MV; +uniform mat4 P; +void main() { + gl_Position = P * MV * vec4(lake.x - world_x, lake.y - world_y, 0., 1.0); +} diff --git a/src/shaders/land.frag b/src/shaders/land.frag new file mode 100644 index 0000000..1eb8d93 --- /dev/null +++ b/src/shaders/land.frag @@ -0,0 +1,100 @@ +precision mediump float; +uniform vec4 black; +uniform vec4 outline; +uniform vec4 highlight; +uniform vec4 foreground; +uniform vec4 midground; +uniform vec4 shadow; +uniform vec4 background; +uniform vec4 lamp1; //vec3 position, float strength +uniform vec4 lamp2; +uniform vec4 lamp3; // max 3 lamps per shaded vertex +uniform vec4 lamp4; +uniform vec4 lamp5; +uniform vec4 lamp6; +uniform vec4 lamp7; +uniform vec4 lamp8; +uniform float streamer; // turn off the noise in the light +uniform float time; //used for noise +uniform sampler2D atlas; +uniform float nlamps; +uniform float water; +varying vec2 worldxy; +varying vec2 norm; + +// Author @patriciogv - 2015 +float random (vec2 st) { + return fract( + sin( + dot(st.xy,vec2(12.9898,78.233)) + ) * + 43758.5453123 + ); +} + +// stolen from https://www.shadertoy.com/view/Msf3WH +vec2 hash( vec2 p ) // replace this by something better +{ + p = vec2( dot(p,vec2(127.1,311.7)), dot(p,vec2(269.5,183.3)) ); + return -1.0 + 2.0*fract(sin(p)*43758.5453123); +} +float noise( in vec2 p ) +{ + const float K1 = 0.366025404; // (sqrt(3)-1)/2; + const float K2 = 0.211324865; // (3-sqrt(3))/6; + + vec2 i = floor( p + (p.x+p.y)*K1 ); + vec2 a = p - i + (i.x+i.y)*K2; + float m = step(a.y,a.x); + vec2 o = vec2(m,1.0-m); + vec2 b = a - o + K2; + vec2 c = a - 1.0 + 2.0*K2; + vec3 h = max( 0.5-vec3(dot(a,a), dot(b,b), dot(c,c) ), 0.0 ); + vec3 n = h*h*h*h*vec3( dot(a,hash(i+0.0)), dot(b,hash(i+o)), dot(c,hash(i+1.0))); + return dot( n, vec3(70.0) ); +} + +void main() { + vec4 coord = gl_FragCoord + vec4(worldxy * 256., 0, 0); + /* + coord.x -= worldxy.x; + coord.y -= worldxy.y; + */ + //coord = coord / 1000.; + // calculate color at this pixel + vec4 normal = texture2D(atlas, norm); + float color = 0.; + vec4 lamp1_norm = lamp1 * 256.; + color += lamp1_norm.w - distance(lamp1_norm.xy - worldxy, coord.xy); + color = max(color,(lamp2.w * 256.) - distance((lamp2.xy * 256.) - worldxy, coord.xy)); + color = max(color,(lamp3.w * 256.) - distance((lamp3.xy * 256.) - worldxy, coord.xy)); + // divide to get a normalized color + //color /= (256. * max(max(lamp1.w, lamp2.w), lamp3.w)); + color /= 256. * 5.; + //color = sqrt(color / 2046.); + // see the normal texture under the color + color = dot(vec4(color),normal) / 1.; + // make the colors fuzzy near the border (or don't if we're streaming) + float rng = random(vec2(coord.x, coord.y) + vec2(color, time)); + color -= (pow(rng / 1.3, 10. * color)) * streamer; + // add noise to water + if(water > 1.) + color += (noise(coord.xy + vec2(time, time)) - 0.0) * 0.1; + /* */ + if(color > 1.) + gl_FragColor = highlight * normal.a; + else if(color > 0.8) + gl_FragColor = foreground * normal.a; + else if(color > 0.6) + gl_FragColor = midground * normal.a; + else if(color > 0.4) + gl_FragColor = background * normal.a; + else if(color > 0.2) + gl_FragColor = shadow * normal.a; + else + gl_FragColor = black * normal.a; + /* + gl_FragColor = normal* vec4(color , color, color,1.); + */ + //gl_FragColor = normal* vec4(color , color / 10., color / 1024.,1.); +} diff --git a/src/shaders/land.vert b/src/shaders/land.vert new file mode 100644 index 0000000..1c9b9f3 --- /dev/null +++ b/src/shaders/land.vert @@ -0,0 +1,26 @@ +precision highp float; +attribute vec3 land; +attribute vec2 landnormal; +uniform float rot; +uniform float world_x; +uniform float world_y; +uniform mat4 MV; +uniform mat4 P; +varying vec2 worldxy; +varying mat4 pre; +varying vec2 norm; +void main() { + norm = landnormal; + mat2 rotate = mat2( + cos(rot), -sin(rot), + sin(rot), cos(rot) + ); + worldxy = vec2(world_x, world_y); + pre = P * MV; + vec2 local = (land.xy - worldxy) * rotate; + float z_scale = 0.5; + // clamp so that everything becomes orthographic once we move away + float xoff = clamp(land.z * local.x * z_scale, -0.5, 0.5); + float yoff = clamp(land.z * local.y * z_scale, -0.5, 0.5); + gl_Position = P * MV * vec4(local.xy - vec2(xoff, yoff), land.z, 1.0); +} diff --git a/src/shaders/palette.vert b/src/shaders/palette.vert new file mode 100644 index 0000000..5937253 --- /dev/null +++ b/src/shaders/palette.vert @@ -0,0 +1,26 @@ +precision highp float; +attribute vec3 world; // position +attribute vec2 texuv; +attribute float r; // for round objects, 0 for non-round +varying vec2 textureuv; +varying float radius; +varying mat3 light1; +varying vec4 v_color; +uniform float world_x; +uniform float world_y; +uniform mat4 MV; +uniform mat4 P; +void main() { + v_color = vec4(world.xyz,1.); + vec2 vxy = vec2(world.x - world_x, world.y - world_y); + float z_scale = 0.5; + float max_parallax = 0.5; + float xoff = clamp(world.z * vxy.x * z_scale, -max_parallax, max_parallax); + float yoff = clamp(world.z * vxy.y * z_scale, -max_parallax, max_parallax); + textureuv=texuv; + //radius = r; + // if z > 0 then + // xoff = ceil(xoff, 0) + // add to the z coord so we don't intersect with the ui + gl_Position = P * MV * vec4(vxy.x + xoff, vxy.y + yoff, world.z, 1.0); +} diff --git a/src/shaders/player.frag b/src/shaders/player.frag new file mode 100644 index 0000000..5d329fa --- /dev/null +++ b/src/shaders/player.frag @@ -0,0 +1,14 @@ +precision mediump float; +varying vec2 textureuv; // uv +uniform sampler2D textures; +uniform sampler2D emissives; +uniform sampler2D normals; +varying mat3 light1; // position, color, intensity-fadetime-? +varying vec4 v_color; +void main() { + vec2 uv = textureuv; + //gl_FragColor = texture2D(textures,uv);// + vec4(uv.xy / 4.,0.,1.); + gl_FragColor = vec4(uv.xy / 1., 0., max(uv.x, uv.y)); + //gl_FragColor = texture2D(textures,screen_intersection.xy); + +} diff --git a/src/shaders/player.vert b/src/shaders/player.vert new file mode 100644 index 0000000..ce53bf5 --- /dev/null +++ b/src/shaders/player.vert @@ -0,0 +1,23 @@ +precision highp float; +attribute vec3 player; +attribute vec2 texuv; +varying vec2 textureuv; +attribute vec4 lamp1; //vec3 position, float strength +attribute vec4 lamp2; +attribute vec4 lamp3; // max 3 lamps per shaded player +uniform float time; //used for noise +uniform float world_x; +uniform float world_y; +uniform float dir; +uniform mat4 MV; +uniform mat4 P; +void main() { + textureuv=texuv; + mat2 rotate = mat2( + cos(dir), -sin(dir), + sin(dir), cos(dir) + ); + vec2 world = vec2(world_x, world_y); + vec2 local = (player.xy - world) * rotate; + gl_Position = P * MV * vec4(local.xy, -2, 1.0); +} diff --git a/src/shaders/stars.frag b/src/shaders/stars.frag new file mode 100644 index 0000000..d45917e --- /dev/null +++ b/src/shaders/stars.frag @@ -0,0 +1,5 @@ +precision mediump float; +uniform vec4 color; +void main() { + gl_FragColor = color; +} diff --git a/src/shaders/stars.lua b/src/shaders/stars.lua new file mode 100644 index 0000000..3e4138e --- /dev/null +++ b/src/shaders/stars.lua @@ -0,0 +1,75 @@ +local win = require("window") +local color = require("color") +local world = require("world") +local shim = require("shader_shim") +local numstars = 500 -- we might have as many as 4 over +local genned_stars = 0 +local period_x = 3 +local period_y = 3 +local stars = {} +local tries = 0 +aspect = win.width / win.height +while genned_stars < numstars and tries < 100000 do + local rngx = math.random() + local xpos = rngx * win.width --* (period_x - 1) + local rngy = math.random() + local ypos = rngy * win.height --* (period_y - 1) + local blinks = math.random() > 0.3 and (math.random() * 2 * math.pi) or 0 + --if math.distance(vec2(rngx,rngy), vec2(0.53,0.5)) > 0.5 then + local off = vec2(math.abs(rngx - 0.50) * aspect, math.abs(rngy-0.5)) + if math.length(off) > 0.5 then + stars[#stars+1] = vec3(xpos, ypos, blinks) + genned_stars = genned_stars + 1 + if xpos < win.width then + -- duplicate on the last screen + stars[#stars+1] = vec3(xpos + (win.width * (period_x-2)), ypos, blinks) + genned_stars = genned_stars + 1 + end + if ypos < win.height then + stars[#stars+1] = vec3(xpos, ypos + (win.height * (period_y-2)), blinks) + genned_stars = genned_stars + 1 + end + if xpos < win.width and ypos < win.height then + stars[#stars+1] = vec3(xpos + (win.width * (period_x-2)), ypos+(win.height * (period_y-2)),blinks) + genned_stars = genned_stars + 1 + end + end + tries = tries + 1 +end +assert(genned_stars == numstars, "Failed to generate stars") +local node = am.blend("premult") ^ shim.stars +^ am.bind({ + MV = mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + (-win.width / 2), (-win.height/2), 0, 1 + ), + color = color.am_color.highlight, + stars = am.vec3_array(stars), + world_x = am.current_time(), + world_x_period = (period_x - 2) * win.width, + world_y = am.current_time(), + world_y_period = (period_y - 2) * win.height, + time = am.current_time(), + lamp1 = vec3(0), + lamp2 = vec3(0), + lamp3 = vec3(0), + lamp4 = vec3(0), + lamp5 = vec3(0), + lamp6 = vec3(0), + lamp7 = vec3(0), + lamp8 = vec3(0) +}) +^ am.draw("points") +node:action(function(self) + self("bind").time = am.current_time() + self("bind").world_x = world.world_x + self("bind").world_y = world.world_y + local lamps = world.level.lamps_on_screen() + for i,v in pairs(lamps) do + print("Setting lamp", i, "to", v) + self("bind")["lamp" .. tostring(i)] = v + end +end) +return node diff --git a/src/shaders/stars.vert b/src/shaders/stars.vert new file mode 100644 index 0000000..8737482 --- /dev/null +++ b/src/shaders/stars.vert @@ -0,0 +1,30 @@ +precision highp float; +attribute vec3 stars; +uniform float time; +uniform float world_x; +uniform float world_y; +uniform float world_x_period; +uniform float world_y_period; +uniform vec4 lamp1; +uniform vec4 lamp2; +uniform vec4 lamp3; +uniform vec4 lamp4; +uniform vec4 lamp5; +uniform vec4 lamp6; +uniform vec4 lamp7; +uniform vec4 lamp8; +uniform mat4 MV; +uniform mat4 P; +void main() { + float world_x_off = mod(world_x, world_x_period); + float world_y_off = mod(world_y, world_y_period); + vec4 pos = P * MV * vec4(stars.x - world_x_off, stars.y - world_y_off, -0.1, 1.0); + gl_Position = pos; + float intensity = sin(stars.z + time) * cos(time) + 1.; + /* + if(distance(pos.xyz, lamp1.xyz) < 80.) + intensity = 0.; + */ + gl_PointSize = pow(intensity, 2.) * stars.z * 0.3; + //gl_PointSize = distance(pos.xyz, lamp1.xyz); +} diff --git a/src/shaders/world.frag b/src/shaders/world.frag new file mode 100644 index 0000000..9cc1bc9 --- /dev/null +++ b/src/shaders/world.frag @@ -0,0 +1,29 @@ +precision mediump float; +varying vec2 textureuv; // uv +varying float radius; +uniform sampler2D textures; +uniform sampler2D emissives; +uniform sampler2D normals; +varying mat3 light1; // position, color, intensity-fadetime-? +uniform float time; +varying vec4 v_color; +void main() { + + vec2 uv = textureuv; + //vec2 uv = gl_FragCoord.xy; + //vec3 view_origin = vec3(0., 0., -3.); + //vec3 view_direction = vec3(uv, 3); + //vec3 screen_intersection = vec3(uv.x, uv.y, 0.); + vec4 raw = texture2D(textures,uv); + gl_FragColor = raw; + //gl_FragColor = texture2D(textures,uv);// + vec4(uv.xy / 4.,0.,1.); + //if(raw.r == 1.0 && raw.g == 1.0 && raw.b == 1.0){ + //gl_FragColor = vec4(0.9058, 0.9215, 0.7725, 1); + //} else if(raw.r == 0.0 && raw.g == 0.0 && raw.b == 0.0){ + //gl_FragColor = vec4(0.298, 0.267, 0.216, 1); + //} else { + //gl_FragColor = raw; + //} +} + //gl_FragColor = vec4(gl_FragCoord.z, gl_FragCoord.z, gl_FragCoord.z, 1.); + //gl_FragColor = texture2D(textures,screen_intersection.xy); diff --git a/src/shaders/world.frag.back b/src/shaders/world.frag.back new file mode 100644 index 0000000..15d7b27 --- /dev/null +++ b/src/shaders/world.frag.back @@ -0,0 +1,20 @@ +precision mediump float; +varying vec2 textureuv; // uv +varying float radius; +uniform sampler2D textures; +uniform sampler2D emissives; +uniform sampler2D normals; +varying mat3 light1; // position, color, intensity-fadetime-? +uniform float time; +varying vec4 v_color; +void main() { + + vec2 uv = textureuv; + //vec2 uv = gl_FragCoord.xy; + //vec3 view_origin = vec3(0., 0., -3.); + //vec3 view_direction = vec3(uv, 3); + //vec3 screen_intersection = vec3(uv.x, uv.y, 0.); + gl_FragColor = texture2D(textures,uv);// + vec4(uv.xy / 4.,0.,1.); + //gl_FragColor = texture2D(textures,screen_intersection.xy); + +} diff --git a/src/shaders/world.moon b/src/shaders/world.moon new file mode 100644 index 0000000..87dba90 --- /dev/null +++ b/src/shaders/world.moon @@ -0,0 +1,356 @@ +win = require("window") +color = require("color") +world = require("world") +sprites = require("sprites") +shader_shim = require("shader_shim") +hc = require("party.hc.init") +log = require("log") +assert(world.world_x, "No world_x" .. debug.traceback()) +-- Process the world into buffers to send to the shader +view_angle = math.pi / 4 +near_plane = 0.01 +far_plane = 100 +aspect = win.width / win.height +-- 2.5D model-view matrix: scale down and offset camera +s_mv = mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, -2, 1 +) +p_mv = math.perspective(math.rad(85), aspect, near_plane, far_plane) + +sd = sprites.floor +w1 = sprites.wall + +-- Each point needs: +-- vec3 position (x,y,z) +-- vec2 (u,v) +up_down_hall = (x,y) -> + r = { + --floor + vec3(x,y,0), + vec3(x+1,y,0), + vec3(x+1,y-1,0), + vec3(x+1,y-1,0), + vec3(x,y-1,0), + vec3(x,y,0), + + -- Left wall + vec3(x,y,1), + vec3(x,y,0), + vec3(x,y-1,0), + vec3(x,y-1,0), + vec3(x,y-1,1), + vec3(x,y,1), + + --Right wall + vec3(x+1,y,0), + vec3(x+1,y,1), + vec3(x+1,y-1,1), + vec3(x+1,y-1,1), + vec3(x+1,y-1,0), + vec3(x+1,y,0), + } + r + +room = (x,y,w,h,left_holes,right_holes,top_holes,bottom_holes) -> + left_holes\sort() + right_holes\sort() + top_holes\sort() + bottom_holes\sort() + r = { + --floor + vec3(x,y,0) + vec3(x+w,y,0), + vec3(x+w,y-h,0), + vec3(x+w,y-h,0), + vec3(x,y-h,0), + vec3(x,y,0), + + --left wall + } + r + +barrel = (x,y,w,h) -> + tris = 18 + rad = (w/2) + l = x - (w/2) + j = x + (w/2) + t = h + b = 0 + f = y - (w/2) + n = y + (w/2) + r = { + --top + vec3(l,f,h), + vec3(l,n,h), + vec3(j,n,h), + vec3(j,n,h), + vec3(j,f,h), + vec3(l,f,h), + } + step = (2*math.pi)/tris + for i = 0,2*math.pi,step + r[#r+1] =vec3(x + math.cos(i)*rad,y + math.sin(i)*n,h) + r[#r+1] =vec3(x + math.cos(i+step)*rad,math.sin(i+step)*n,h) + r[#r+1] =vec3(x + math.cos(i+step)*rad,math.sin(i+step)*n,0) + r[#r+1] =vec3(x + math.cos(i+step)*rad,math.sin(i+step)*n,0) + r[#r+1] =vec3(x + math.cos(i)*rad,math.sin(i)*n,0) + r[#r+1] =vec3(x + math.cos(i)*rad,math.sin(i)*n,h) + r + +barrel_uv = (x,y,w,h) -> + tris = 18 + r = { + vec4(w1.s1,w1.t1,1,1), + vec4(w1.s1,w1.t2,1,1), + vec4(w1.s2,w1.t2,1,1), + vec4(w1.s2,w1.t2,1,1), + vec4(w1.s2,w1.t1,1,1), + vec4(w1.s1,w1.t1,1,1), + } + step = (2*math.pi)/tris + for i = 0,2*math.pi,step + perc = (i / (2*math.pi)) + nextperc = ((i+step) / (2*math.pi)) + srange = w1.s2 - w1.s1 + trange = w1.t2 - w1.t1 + sstart = w1.s1 + (srange * perc) + send = w1.s1 + (srange * nextperc) + tstart = w1.t1 + tend = w1.t2 + r[#r+1] = vec4(sstart ,tstart,1,1) + r[#r+1] = vec4(sstart ,tend,1,1) + r[#r+1] = vec4(send ,tend,1,1) + r[#r+1] = vec4(send ,tend,1,1) + r[#r+1] = vec4(send ,tstart,1,1) + r[#r+1] = vec4(sstart ,tstart,1,1) + r + +barrel_r = (x,y,w,h) -> + r = {-1,-1,1,1,1,-1,0,0,0,0,0,0} + r + + +-- uvs are s,t,smult, tmult +up_down_hall_uv = (x,y) -> + r = { + --floor + vec4(sd.s1,sd.t1,1,1), + vec4(sd.s2,sd.t1,1,1), + vec4(sd.s2,sd.t2,1,1), + vec4(sd.s2,sd.t2,1,1), + vec4(sd.s1,sd.t2,1,1), + vec4(sd.s1,sd.t1,1,1), + -- left wall + vec4(w1.s1,w1.t1,1,1), + vec4(w1.s1,w1.t2,1,1), + vec4(w1.s2,w1.t2,1,1), + vec4(w1.s2,w1.t2,1,1), + vec4(w1.s2,w1.t1,1,1), + vec4(w1.s1,w1.t1,1,1), + -- right wall + vec4(w1.s2,w1.t2,1,1), + vec4(w1.s2,w1.t1,1,1), + vec4(w1.s1,w1.t1,1,1), + vec4(w1.s1,w1.t1,1,1), + vec4(w1.s1,w1.t2,1,1), + vec4(w1.s2,w1.t2,1,1), + + } + r + +up_down_hall_r = (x,y) -> + r = { + 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,0,0,0,0 + } + r + +-- Barrel? + + +add_verts = (tbl, new) -> + for i = 1, #new + tbl[#tbl+1] = new[i] + +world_geom = {} +world_uv = {} +world_r = {} +add_verts(world_geom, up_down_hall(0,-1)) +add_verts(world_uv, up_down_hall_uv(0,0)) +add_verts(world_r, up_down_hall_r(0,0)) +add_verts(world_geom, up_down_hall(0,1)) +add_verts(world_uv, up_down_hall_uv(0,0)) +add_verts(world_r, up_down_hall_r(0,0)) +add_verts(world_geom, up_down_hall(0,0)) +add_verts(world_uv, up_down_hall_uv(0,0)) +add_verts(world_r, up_down_hall_r(0,0)) +add_verts(world_geom, barrel(-1,0,0.5,0.5)) +add_verts(world_uv, barrel_uv(-1,0,0.5,0.5)) +add_verts(world_r, barrel_r(-1,0,0.5,0.5)) +--add_verts(world_geom, barrel(0.5,0.5,0.5,0.5)) +--add_verts(world_uv, barrel_uv(0.5,0.5,0.5,0.5)) +--add_verts(world_r, barrel_r(0.5,0.5,0.5,0.5)) +--sprites["diffuse"].texture.wrap = "repeat" +--sprites["normals"].texture.wrap = "repeat" + +test_world = { + vec3(0,0,0), + vec3(1,0,0), + vec3(0,1,0) +} +test_uvs = { + vec4(sd.s1,sd.t1,1,1), + vec4(sd.s2,sd.t1,1,1), + vec4(sd.s2,sd.t2,1,1), +} +test_r = { + 0,0,0 +} + +-- How big should our buffer for the world be? +-- Let's call it at up to 100 players +--MAX_PLAYERS = 128 +MAX_PLAYERS = 8 +--MAX_LEVEL_TRIS = 1 * 1024 * 1024 -- 1M level triangles? +MAX_LEVEL_TRIS = 1024 -- testing? +buffer_tris = (MAX_PLAYERS * 2) + MAX_LEVEL_TRIS +geom_buffer = am.buffer(buffer_tris * 3 * 12) --am.vec3_array +uv_buffer = am.buffer(buffer_tris * 3 * 16) --am.vec2_array +geom_view = geom_buffer\view("vec3") +uv_view = uv_buffer\view("vec4") +for i = 1, #world_geom + geom_view[i] = world_geom[i] + +for i = 1, #world_uv + uv_view[i] = world_uv[i] + +world.geom_view = geom_view +world.uv_view = uv_view + +buf_cursor = #world_geom -- the vertex number +shimworld = shader_shim.world +depth_test = am.depth_test("less") +shimworld\append(depth_test) +--cull = am.cull_face("back") +--depth_test\append(cull) + +--{ + --MV: s_mv + --P: mat4(1) + --world_x: math.sin(am.current_time!) * 2 + --world_y: math.cos(am.current_time!) * 2 + ----world_x: 0, + ----world_y: 0, + --world: geom_view, + --texuv: uv_view, + ----world: am.vec3_array(world_geom) + ----texuv: am.vec4_array(world_uv) + ----r: am.float_array(world_r) + ----world:am.vec3_array(test_world) + ----texuv: am.vec4_array(test_uvs) + ----r: am.float_array(test_r) + --time: am.current_time(), + --textures: sprites.floor.texture +--} + +-- In the example we have +-- am.group (buildings_group) +-- ^ many am.bind(...) +-- ^ am.draw("triangles") + + +bind = am.group! +depth_test\append(bind) + +add = (n) -> + assert(n.tris) + assert(n.populate_buf) + assert(n.properties) + assert(n.properties.graphic) + --assert(buf_cursor + n_tris < buffer_tris, "Not enough tris! Had " .. buf_cursor .. " and wanted " .. n_tris .. " so we sould end up with " .. (buf_cursor + n_tris) .. " but we only have " .. buffer_tris) + n_tris = n\tris! + log.info(string.format("Adding %d tris to buffer at %d",n_tris, buf_cursor),{"graphics"}) + geom_buffer = am.buffer(n_tris * 3 * 12) --am.vec3_array + uv_buffer = am.buffer(n_tris * 3 * 16) --am.vec2_array + geom_view = geom_buffer\view("vec3") + uv_view = uv_buffer\view("vec4") + texture = n.properties.graphic.texture + n\populate_buf(geom_view, uv_view, 1) + tbind = am.bind({ + MV: s_mv + P: p_mv + world_x: math.sin(am.current_time!) * 2 + world_y: math.cos(am.current_time!) * 2 + world: geom_view, + texuv: uv_view, + time: am.current_time! + textures: texture + }) + tbind\append(am.draw("triangles")) + bind\append(tbind) + n.node = tbind + --buf_cursor += n_tris * 6 + +remove = (n) -> + bind\remove(n.node) + +clear = () -> + log.info("Clearing shader!") + geom_view\set(vec3(0,0,0),1,buf_cursor) + uv_view\set(vec4(0,0,0,0),1,buf_cursor) + buf_cursor = 1 + + +--draw = am.draw("triangles", 1, buf_cursor) +--bind\append(draw) + +--world.geom = binds.geom +--world.texuv = binds.texuv + +shimworld\action(() => + binds = bind\all("bind") + binds.time = am.current_time! + --bind.world_x = math.sin(am.current_time!) * 2 + --bind.world_y = math.cos(am.current_time!) * 2 + binds.world_x = world.world_x + binds.world_y = world.world_y +) +node = shimworld + +--node = shader_shim.world\append(am.depth_test("less")\append(am.cull_face("front")\append(am.bind({ -- should cull front + --MV: s_mv + --P: mat4(1) + --color: color.am_color.highlight, + --world_x: 0, + --world_y: 0, + --world: am.vec3_array(world_geom) + --texuv: am.vec4_array(world_uv) + --r: am.float_array(world_r) + ----world:am.vec3_array(test_world) + ----texuv: am.vec4_array(test_uvs) + ----r: am.float_array(test_r) + --time: am.current_time(), + --textures: sprites.floor1_diffuse.texture +--})\append(am.draw("triangles"))))) +--node\action(() => + --bind = self("bind") + --bind.time = am.current_time! + --bind.world_x = math.sin(am.current_time!) * 2 + --bind.world_y = math.cos(am.current_time!) * 2 + ----bind.world_x = world.world_x + ----bind.world_y = world.world_y +--) +{ + :add + :clear + :remove + node: node + bind: node("bind") +} diff --git a/src/shaders/world.moon.back b/src/shaders/world.moon.back new file mode 100644 index 0000000..7950f51 --- /dev/null +++ b/src/shaders/world.moon.back @@ -0,0 +1,327 @@ +win = require("window") +color = require("color") +world = require("world") +sprites = require("sprites") +shader_shim = require("shader_shim") +hc = require("party.hc.init") +log = require("log") +PlayerGraphicComponent = require("ecs.player_graphic") +assert(world.world_x, "No world_x" .. debug.traceback()) +-- Process the world into buffers to send to the shader + +view_angle = math.pi / 4 +near_plane = 1 +far_plane = 2 +aspect = win.width / win.height +s_mv = mat4( + 1, 0, 0, 0, + 0, aspect, 0, 0, + 0, 0, 1, 0, + -0.0, 0.5, 0, 4 + ) +p_mv = mat4( + 1 / ((win.width / win.height) * math.tan(view_angle / 2)), 0, 0, 0, + 0, 1/math.tan(view_angle/2), 0, 0, + 0, 0, far_plane / (far_plane - near_plane), 1, + 0, 0,(-far_plane * near_plane)/(far_plane - near_plane), 0 +) + +sd = sprites.floor +w1 = sprites.wall + +-- Each point needs: +-- vec3 position (x,y,z) +-- vec2 (u,v) +up_down_hall = (x,y) -> + r = { + --floor + vec3(x,y,0), + vec3(x+1,y,0), + vec3(x+1,y-1,0), + vec3(x+1,y-1,0), + vec3(x,y-1,0), + vec3(x,y,0), + + -- Left wall + vec3(x,y,1), + vec3(x,y,0), + vec3(x,y-1,0), + vec3(x,y-1,0), + vec3(x,y-1,1), + vec3(x,y,1), + + --Right wall + vec3(x+1,y,0), + vec3(x+1,y,1), + vec3(x+1,y-1,1), + vec3(x+1,y-1,1), + vec3(x+1,y-1,0), + vec3(x+1,y,0), + } + r + +room = (x,y,w,h,left_holes,right_holes,top_holes,bottom_holes) -> + left_holes\sort() + right_holes\sort() + top_holes\sort() + bottom_holes\sort() + r = { + --floor + vec3(x,y,0) + vec3(x+w,y,0), + vec3(x+w,y-h,0), + vec3(x+w,y-h,0), + vec3(x,y-h,0), + vec3(x,y,0), + + --left wall + } + r + +barrel = (x,y,w,h) -> + tris = 18 + rad = (w/2) + l = x - (w/2) + j = x + (w/2) + t = h + b = 0 + f = y - (w/2) + n = y + (w/2) + r = { + --top + vec3(l,f,h), + vec3(l,n,h), + vec3(j,n,h), + vec3(j,n,h), + vec3(j,f,h), + vec3(l,f,h), + } + step = (2*math.pi)/tris + for i = 0,2*math.pi,step + r[#r+1] =vec3(x + math.cos(i)*rad,y + math.sin(i)*n,h) + r[#r+1] =vec3(x + math.cos(i+step)*rad,math.sin(i+step)*n,h) + r[#r+1] =vec3(x + math.cos(i+step)*rad,math.sin(i+step)*n,0) + r[#r+1] =vec3(x + math.cos(i+step)*rad,math.sin(i+step)*n,0) + r[#r+1] =vec3(x + math.cos(i)*rad,math.sin(i)*n,0) + r[#r+1] =vec3(x + math.cos(i)*rad,math.sin(i)*n,h) + r + +barrel_uv = (x,y,w,h) -> + tris = 18 + r = { + vec4(w1.s1,w1.t1,1,1), + vec4(w1.s1,w1.t2,1,1), + vec4(w1.s2,w1.t2,1,1), + vec4(w1.s2,w1.t2,1,1), + vec4(w1.s2,w1.t1,1,1), + vec4(w1.s1,w1.t1,1,1), + } + step = (2*math.pi)/tris + for i = 0,2*math.pi,step + perc = (i / (2*math.pi)) + nextperc = ((i+step) / (2*math.pi)) + srange = w1.s2 - w1.s1 + trange = w1.t2 - w1.t1 + sstart = w1.s1 + (srange * perc) + send = w1.s1 + (srange * nextperc) + tstart = w1.t1 + tend = w1.t2 + r[#r+1] = vec4(sstart ,tstart,1,1) + r[#r+1] = vec4(sstart ,tend,1,1) + r[#r+1] = vec4(send ,tend,1,1) + r[#r+1] = vec4(send ,tend,1,1) + r[#r+1] = vec4(send ,tstart,1,1) + r[#r+1] = vec4(sstart ,tstart,1,1) + r + +barrel_r = (x,y,w,h) -> + r = {-1,-1,1,1,1,-1,0,0,0,0,0,0} + r + + +-- uvs are s,t,smult, tmult +up_down_hall_uv = (x,y) -> + r = { + --floor + vec4(sd.s1,sd.t1,1,1), + vec4(sd.s2,sd.t1,1,1), + vec4(sd.s2,sd.t2,1,1), + vec4(sd.s2,sd.t2,1,1), + vec4(sd.s1,sd.t2,1,1), + vec4(sd.s1,sd.t1,1,1), + -- left wall + vec4(w1.s1,w1.t1,1,1), + vec4(w1.s1,w1.t2,1,1), + vec4(w1.s2,w1.t2,1,1), + vec4(w1.s2,w1.t2,1,1), + vec4(w1.s2,w1.t1,1,1), + vec4(w1.s1,w1.t1,1,1), + -- right wall + vec4(w1.s2,w1.t2,1,1), + vec4(w1.s2,w1.t1,1,1), + vec4(w1.s1,w1.t1,1,1), + vec4(w1.s1,w1.t1,1,1), + vec4(w1.s1,w1.t2,1,1), + vec4(w1.s2,w1.t2,1,1), + + } + r + +up_down_hall_r = (x,y) -> + r = { + 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,0,0,0,0 + } + r + +-- Barrel? + + +add_verts = (tbl, new) -> + for i = 1, #new + tbl[#tbl+1] = new[i] + +world_geom = {} +world_uv = {} +world_r = {} +add_verts(world_geom, up_down_hall(0,-1)) +add_verts(world_uv, up_down_hall_uv(0,0)) +add_verts(world_r, up_down_hall_r(0,0)) +add_verts(world_geom, up_down_hall(0,1)) +add_verts(world_uv, up_down_hall_uv(0,0)) +add_verts(world_r, up_down_hall_r(0,0)) +add_verts(world_geom, up_down_hall(0,0)) +add_verts(world_uv, up_down_hall_uv(0,0)) +add_verts(world_r, up_down_hall_r(0,0)) +add_verts(world_geom, barrel(-1,0,0.5,0.5)) +add_verts(world_uv, barrel_uv(-1,0,0.5,0.5)) +add_verts(world_r, barrel_r(-1,0,0.5,0.5)) +--add_verts(world_geom, barrel(0.5,0.5,0.5,0.5)) +--add_verts(world_uv, barrel_uv(0.5,0.5,0.5,0.5)) +--add_verts(world_r, barrel_r(0.5,0.5,0.5,0.5)) +--sprites["diffuse"].texture.wrap = "repeat" +--sprites["normals"].texture.wrap = "repeat" + +test_world = { + vec3(0,0,0), + vec3(1,0,0), + vec3(0,1,0) +} +test_uvs = { + vec4(sd.s1,sd.t1,1,1), + vec4(sd.s2,sd.t1,1,1), + vec4(sd.s2,sd.t2,1,1), +} +test_r = { + 0,0,0 +} + +-- How big should our buffer for the world be? +-- Let's call it at up to 100 players +--MAX_PLAYERS = 128 +MAX_PLAYERS = 8 +--MAX_LEVEL_TRIS = 1 * 1024 * 1024 -- 1M level triangles? +MAX_LEVEL_TRIS = 1024 -- testing? +buffer_tris = (MAX_PLAYERS * PlayerGraphicComponent.tris!) + MAX_LEVEL_TRIS +geom_buffer = am.buffer(buffer_tris * 3 * 12) --am.vec3_array +uv_buffer = am.buffer(buffer_tris * 3 * 16) --am.vec2_array +geom_view = geom_buffer\view("vec3") +uv_view = uv_buffer\view("vec4") +for i = 1, #world_geom + geom_view[i] = world_geom[i] + +for i = 1, #world_uv + uv_view[i] = world_uv[i] + +world.geom_view = geom_view +world.uv_view = uv_view + +buf_cursor = #world_geom -- the vertex number +add = (n) -> + assert(n.tris) + assert(n.populate_buf) + n_tris = n\tris! + assert(buf_cursor + n_tris < buffer_tris, "Not enough tris! Had " .. buf_cursor .. " and wanted " .. n_tris .. " so we sould end up with " .. (buf_cursor + n_tris) .. " but we only have " .. buffer_tris) + log.info(string.format("Adding %d tris to buffer at %d",n_tris, buf_cursor),{"graphics"}) + n\populate_buf(geom_view, uv_view, buf_cursor) + buf_cursor += n_tris * 6 + +clear = () -> + log.info("Clearing shader!") + geom_view\set(vec3(0,0,0),1,buf_cursor) + uv_view\set(vec4(0,0,0,0),1,buf_cursor) + buf_cursor = 1 + +shimworld = shader_shim.world +depth_test = am.depth_test("less") +shimworld\append(depth_test) +cull = am.cull_face("front") +depth_test\append(cull) +binds = { + MV: s_mv + P: mat4(1) + color: color.am_color.highlight, + world_x: 0, + world_y: 0, + world: geom_view, + texuv: uv_view, + --world: am.vec3_array(world_geom) + --texuv: am.vec4_array(world_uv) + --r: am.float_array(world_r) + --world:am.vec3_array(test_world) + --texuv: am.vec4_array(test_uvs) + --r: am.float_array(test_r) + time: am.current_time(), + textures: sprites.floor.texture +} +bind = am.bind(binds) +cull\append(bind) +draw = am.draw("triangles", 1, buf_cursor) +bind\append(draw) + +world.geom = binds.geom +world.texuv = binds.texuv + +shimworld\action(() => + bind = self("bind") + bind.time = am.current_time! + --bind.world_x = math.sin(am.current_time!) * 2 + --bind.world_y = math.cos(am.current_time!) * 2 + bind.world_x = world.world_x + bind.world_y = world.world_y +) +node = shimworld + +--node = shader_shim.world\append(am.depth_test("less")\append(am.cull_face("front")\append(am.bind({ -- should cull front + --MV: s_mv + --P: mat4(1) + --color: color.am_color.highlight, + --world_x: 0, + --world_y: 0, + --world: am.vec3_array(world_geom) + --texuv: am.vec4_array(world_uv) + --r: am.float_array(world_r) + ----world:am.vec3_array(test_world) + ----texuv: am.vec4_array(test_uvs) + ----r: am.float_array(test_r) + --time: am.current_time(), + --textures: sprites.floor1_diffuse.texture +--})\append(am.draw("triangles"))))) +--node\action(() => + --bind = self("bind") + --bind.time = am.current_time! + --bind.world_x = math.sin(am.current_time!) * 2 + --bind.world_y = math.cos(am.current_time!) * 2 + ----bind.world_x = world.world_x + ----bind.world_y = world.world_y +--) +{ + :add + :clear + node: node + bind: node("bind") +} diff --git a/src/shaders/world.vert b/src/shaders/world.vert new file mode 100644 index 0000000..5937253 --- /dev/null +++ b/src/shaders/world.vert @@ -0,0 +1,26 @@ +precision highp float; +attribute vec3 world; // position +attribute vec2 texuv; +attribute float r; // for round objects, 0 for non-round +varying vec2 textureuv; +varying float radius; +varying mat3 light1; +varying vec4 v_color; +uniform float world_x; +uniform float world_y; +uniform mat4 MV; +uniform mat4 P; +void main() { + v_color = vec4(world.xyz,1.); + vec2 vxy = vec2(world.x - world_x, world.y - world_y); + float z_scale = 0.5; + float max_parallax = 0.5; + float xoff = clamp(world.z * vxy.x * z_scale, -max_parallax, max_parallax); + float yoff = clamp(world.z * vxy.y * z_scale, -max_parallax, max_parallax); + textureuv=texuv; + //radius = r; + // if z > 0 then + // xoff = ceil(xoff, 0) + // add to the z coord so we don't intersect with the ui + gl_Position = P * MV * vec4(vxy.x + xoff, vxy.y + yoff, world.z, 1.0); +} diff --git a/src/shaders/world.vert.back b/src/shaders/world.vert.back new file mode 100644 index 0000000..42276fe --- /dev/null +++ b/src/shaders/world.vert.back @@ -0,0 +1,26 @@ +precision highp float; +attribute vec3 world; // position +attribute vec2 texuv; +attribute float r; // for round objects, 0 for non-round +varying vec2 textureuv; +varying float radius; +varying mat3 light1; +uniform vec4 color; +varying vec4 v_color; +uniform float world_x; +uniform float world_y; +uniform mat4 MV; +uniform mat4 P; +void main() { + v_color = vec4(world.xyz,1.); + vec2 vxy = vec2(world.x - world_x, world.y - world_y); + float z_scale = 0.5; + float xoff = clamp(world.z * vxy.x * z_scale, -32., 32.); + float yoff = clamp(world.z * vxy.y * z_scale, -32., 32.); + textureuv=texuv; + //radius = r; + // if z > 0 then + // xoff = ceil(xoff, 0) + // add to the z coord so we don't intersect with the ui + gl_Position = P * MV * vec4(vxy.x + xoff, vxy.y + yoff, -world.z -1., 1.0); +} diff --git a/src/shared/player_movement.moon b/src/shared/player_movement.moon new file mode 100644 index 0000000..31aada4 --- /dev/null +++ b/src/shared/player_movement.moon @@ -0,0 +1,54 @@ +-- we want to find the location based on inital velocity and position, constant acceleration, and delta time +-- Each function should be called with a `PredictedComponent` as `self`. + +-- In a normal simulation, velocity adds +-- acceleration * delta time +-- every tick, minus some friction: +-- coefficient * current velocity +-- i.e. velocity = (acceleration * delta) - (friction * velocity) +-- every tick +-- velocity[tick] = (acceleration * delta[tick]) - (friction * velocity[tick - 1]) +-- velocity[4] = (acceleration * delta[4]) - (friction * velocity[3]) +-- = (acceleration * delta[4]) - (friction * ((acceleration * delta[3]) - (friction * velocity[2]))) +-- = (acceleration * delta[4]) - (friction * ((acceleration * delta[3]) - (friction * ((acceleration * delta[2]) - (friction * velocity[inital]))))) +-- = (acceleration * delta[4]) - (friction * ((acceleration * delta[3]) - ((friction * acceleration * delta[2]) - (friction * friction * velocity[inital])))) +-- = (acceleration * delta[4]) - (friciton * ((acceleration * delta[3]) - (friction * acceleration * delta[2]) + (friction^2 * velocity[inital]))) +-- = (acceleration * delta[4]) - ((friction * acceleration * delta[3]) - (friction * friction * acceleration * delta[2]) + (friction^3 * velocity[inital])) +-- = (acceleration * delta[4]) - (friction * acceleration * delta[3]) + (friction^2 * acceleration * delta[2]) - (friction^3 * velocity[inital]) +-- as delta approaches 0 (high fidelity simulation), the middle components become e^(-friction * delta), and acceleration needs to be divided by friction +-- Position is a second layer on top +-- position[tick] = position[tick-1] + velocity[tick] +-- position[2] = position[inital] + velocity[2] +-- = position[inital] + (acceleration * delta[2]) - (friction * velocity[inital]) +-- position[delta] = (delta * (acceleration / friction) ) - ((1 / friction) * (velocity[inital] - (acceleratin / friction)) * e^(-friction * delta) + position[inital] + +friction = 0.3 +{ + acc:() => + acc = vec3(unpack(@net.properties.acc)) + movement_speed = 1/1000 -- @net.properties.move_speed? + freeze = 1 -- @net.properties.frozen? + newacc = acc * (movement_speed) * (freeze) + {newacc.x, newacc.y, newacc.z} + vel: () => + acc = vec3(unpack(@properties.acc)) + vel = vec3(unpack(@net.properties.vel)) + now = am.eval_js("Date.now();") + --print("Net is ", @net.properties) + delta = (now - @net.properties.last_update) / 1000 + newvel = (acc / friction) + ((vel - (acc / friction)) * math.exp(-friction * delta)) + {newvel.x, newvel.y, newvel.z} + pos: () => + now = am.eval_js("Date.now();") + delta = (now - @net.properties.last_update) / 1000 + vel = vec3(unpack(@properties.vel)) + pos = vec3(unpack(@properties.pos)) + acc = vec3(unpack(@properties.acc)) + friction_loss = acc / friction + -- when delta = 0 (up to date) + -- pos = (1/friction) * (velocity - friction_loss) * 1 + position + -- = 2 * (2 - 2) * 1 + position + -- = position + newpos = (friction_loss * delta) - ((1/friction) * (vel - friction_loss) * (math.exp(-friction * delta))) + pos + {newpos.x, newpos.y, newpos.z} +} diff --git a/src/task.moon b/src/task.moon new file mode 100644 index 0000000..342255e --- /dev/null +++ b/src/task.moon @@ -0,0 +1,25 @@ +task = {} +tasks = {} + +task.add = (co) -> + tasks[co] = true + +task.pump = () -> + for task, _ in pairs tasks + if coroutine.status(task) ~= "dead" + succ, err = coroutine.resume(task) + if not succ + error(debug.traceback(task, err)) + else + tasks[task] = nil + +task.await = (co) -> + if tasks[co] + coroutine.yield! + +task.node = am.group! +task.node\action(() -> + task.pump! +) + +task diff --git a/src/textbox_bridge.js b/src/textbox_bridge.js new file mode 100644 index 0000000..c52902e --- /dev/null +++ b/src/textbox_bridge.js @@ -0,0 +1,74 @@ + +var i = 0; +/* Detour SDL.receiveEvent to we can focus textboxes + */ + +var oldReceive = SDL.receiveEvent; +SDL.receiveEvent = function(e){ + console.log("Intercepting event!"); + return oldReceive(e); +}; +window.TEXTBOX = { + create_textbox: function(tbl) { + var value = tbl.value; + var palceholder = tbl.placeholder; + var s = document.createElement('input'); + s.setAttribute("type","text"); + s.setAttribute("id","textbox" + i); + s.setAttribute("style","z-index: 1; position:absolute; visibility:hidden;"); + var p = document.getElementById("container"); + var noop = function(){}; + p.prepend(s); + console.log("[JS] Added textbox" + i, s); + /* None of these work, amulet intercepts the keys */ + /* + s.addEventListener("keypress",function(e) { + console.log("Keypress on the textbox"); + }); + s.addEventListener("keyup",function(e) { + console.log("keyup on the textbox"); + }); + s.addEventListener("keydown",function(e) { + console.log("keydown on the textbox"); + }); + */ + s.addEventListener("focusin",function(e){ + e.preventDefault = noop; + }); + s.addEventListener("focusout",function(e){ + e.preventDefault = noop; + }); + // When we get an event, stop amulet from doing .preventDefault() + s.addEventListener("keypress",function(e){ + e.preventDefault = noop; + }); + s.addEventListener("keyup",function(e){ + e.preventDefault = noop; + }); + s.addEventListener("keydown",function(e){ + e.preventDefault = noop; + }); + i++; + return i; + }, + focus: function(tbl) { + var id = tbl.id; + var e = document.getElementById("textbox" + id); + e.setAttribute("style","z-index: 1; position:absolute;"); + console.log("[JS] Clicking element", e); + e.focus(); + }, + blur: function(tbl) { + var id = tbl.id; + var e = document.getElementById("textbox" + id); + e.setAttribute("style","position:absolute; visibility:hidden;"); + console.log("[JS] Bluring element", e); + e.blur(); + }, + get_text: function(tbl) { + var id = tbl.id; + var e = document.getElementById("textbox" + id); + console.log("Getting text",e.text); + return e.value; + } +}; diff --git a/src/ui.moon b/src/ui.moon new file mode 100644 index 0000000..25847bd --- /dev/null +++ b/src/ui.moon @@ -0,0 +1,269 @@ +hc = require("party.hc.init") +win = require("window") +log = require("log") +util = require("util") +Button = require("ui.button") +Joystick = require("ui.joystick") +Textbox = require("ui.textbox") +sprites = require("sprites") +color = require("color") + +ui_world = hc.new(64) + +am.eval_js(require("controller_bridge")) + +ui = {} +ui.events = { + touch: {} + mouse: {} + controller: {} + keyboard: {} +} +ui.button = (x,y,width,height,text,controller_binds,font) -> + font = font or sprites.yataghan64 + log.info(string.format("Creating button at (%d, %d) with size (%d, %d) and text %q",x,y,width,height,text),{"ui"}) + controller_binds = controller_binds or {} + assert(x and type(x) == "number", "x must be anumber") + assert(y and type(y) == "number", "y must be anumber") + assert(width and type(width) == "number", "width must be anumber") + assert(height and type(height) == "number", "height must be anumber") + button = Button(x,y,width,height,text, font) + ui.node\append(button.node) + bounds = ui_world\rectangle(x,y,width,height) + ui.events.touch[bounds] = button + ui.events.mouse[bounds] = button + for bind in *controller_binds + ui.events.controller[bind] = button + button + +ui.click = (x,y) -> + ui_world\shapesAt(x,y) + +ui.joystick = (x,y,r,controller_binds) -> + controller_binds = controller_binds or {} + joystick = Joystick(x,y,r) + ui.node\append(joystick.node) + bounds = ui_world\circle(x,y,r) + ui.events.touch[bounds] = joystick + for bind in *controller_binds + ui.events.controller[bind] = joystick + joystick + +ui.text = (x,y,width,height,text) -> + line_height = 16 + text = text or "" + rope = { + raw: text + width: 0 + height: 0 + lines: {} --list of line, a line is: + -- { + -- raw: string + -- tokens: token[] + -- width: number (pixels) + -- height: number (pixels) + -- } + -- A token is: + -- { + -- raw: string + -- width: number (pixels) + -- height: number (pixels) + -- } + } + current_line = { + raw: "" + tokens: {} + width: 0 + height: 0 + } + for word in text\gmatch("(%S+)") + t = am.text(sprites.yataghan32, " " .. word) + tps = t.width + if tps + current_line.width > width -- create a new line + rope.width = math.max(rope.width, current_line.width) + rope.height += line_height + current_line.height + if #rope.lines == 0 -- no line height for the first line + rope.height -= line_height + table.insert(rope.lines, current_line) + t = am.text(sprites.yataghan32, word) + token = { + raw: word + width: t.width + height: t.height + } + current_line = { + raw: word + tokens: { + token + } + width: t.width + height: t.height + } + else -- append token to this line + t = am.text(sprites.yataghan32, word) + token = { + raw: word + width: t.width + height: t.height + } + current_line.raw = current_line.raw .. " " .. word + current_line.width += tps + current_line.height = math.max(current_line.height, t.height) + table.insert(current_line.tokens, token) + if #current_line.tokens > 0 + table.insert(rope.lines, current_line) + group = am.group! + y_cursor = -line_height -- no line height for first line + for i = 1, #rope.lines + y_cursor -= line_height + line_pos = am.translate(x, y + y_cursor) + line_text = am.text(sprites.yataghan32, rope.lines[i].raw,color.am_color.foreground) + line_pos\append(line_text) + group\append(line_pos) + y_cursor -= rope.lines[i].height + rope.height = -y_cursor + if rope.height == 0 + rope.height = 1 + if rope.width == 0 + rope.width = 1 + bounds = ui_world\rectangle(x,y,rope.width, rope.height) + ui.node\append(group) + element = { + node: group + rope: rope + } + -- No events? + element + +ui.textbox = (x,y,width,height,value,placeholder) -> + value = value or "" + placeholder = placeholder or "" + textbox = Textbox(x,y,width,height,value,placeholder) + ui.node\append(textbox.node) + bounds = ui_world\rectangle(x,y,width,height) + ui.events.mouse[bounds] = textbox + ui.events.keyboard[textbox] = true + textbox + +ui.delete = (element) -> + ui.node\remove(element.node) + for b,e in pairs(ui.events.mouse) + if e == element + ui.events.mouse[b] = nil + if ui.events.keyboard[element] + ui.events.keyboard[element] = nil + for b,e in pairs(ui.events.touch) + if e == element + ui.events.touch[b] = nil + for b,e in pairs(ui.events.controller) + if e == element + ui.events.controller[b] = nil + +ui.node = am.group! + +has_fire = (obj) -> + assert(obj.fire, obj.__class.__name .. " doesn't have a .fire method") + +--ui.dbg = am.translate(0,0)\append(am.circle(vec2(0,0),5,vec4(0,0,0,1)))\append(am.text("Hello, world!")) +--ui.node\append(ui.dbg) + +ui.node\action(() -> + pos = win\mouse_position() + down = win\mouse_pressed("left") + up = win\mouse_released("left") + wheel = win\mouse_wheel_delta() + keys = win\keys_pressed() + am.eval_js("CONT.loop()") + cont_state = am.eval_js("CONT.last_state") + -- Debugging for mouse position: + --ui.dbg.position2d = pos + mo_tbl = + event: "mouse_over" + data: pos + md_tbl = + event: "mouse_down" + data: pos + mu_tbl = + event: "mouse_up" + data: pos + for collider,_ in pairs(ui_world\shapesAt(pos.x, pos.y)) + match = ui.events.mouse[collider] + if match + has_fire(match) + --log.info("Found button under mouse:" .. tostring(match), {"ui","mouseover"}) + match\fire(mo_tbl) + if down + log.info("Found button under mouse:" .. tostring(match), {"ui","mousedown"}) + match\fire(md_tbl) + if up + log.info("Found button under mouse:" .. tostring(match), {"ui","mouseup"}) + match\fire(mu_tbl) + if math.length(wheel) > 0 + etbl = + event: "mouse_scroll" + data: wheel + for collider, uiobj in pairs(ui.events.mouse) + has_fire(uiobj) + uiobj\fire(etbl) + if #keys > 0 + --print("Got keys:" .. tostring(keys)) + etbl = + event: "keys_pressed" + data: keys + shift: win\key_down("lshift") or win\key_down("rshift") + for uiobj, _ in pairs(ui.events.keyboard) + has_fire(uiobj) + if uiobj\fire(etbl) + break -- allow any keyboard listener to "trap" the signal by returning true + if cont_state.on + for axis,value in pairs(cont_state.axes) + name = "axis" .. axis + uiobj = ui.events.controller[name] + if uiobj and has_fire(uiobj) + etbl = + event: "controller_axis" + data: value + uiobj\fire(etbl) + for button,value in pairs(cont_state.buttons) + name = "button" .. button + uiobj = ui.events.controller[name] + if uiobj and has_fire(uiobj) + etbl = + event: "controller_pressed" + data: value + uiobj\fire(etbl) + +-- in_touch_events = { +-- "active_touch" +-- "touches_began" +-- } +-- for touch in *win\active_touches! +-- tpos = win\touch_position(touch) +-- etbl = +-- event: "active_touch" +-- data: tpos +-- for collider,_ in pairs(ui_world\shapesAt(tpos.x, tpos.y)) +-- print("Touched collider:", collider) +-- match = ui.events.touch[collider] +-- if match +-- has_fire(match) +-- match\fire(etbl) +-- delta = win\touch_delta(touch) +-- if math.length(delta) > 0 +-- dtbl = +-- event: "touch_delta" +-- data: delta +-- for _, uiobj in pairs(ui.events.touch) +-- has_fire(uiobj) +-- uiobj\fire(dtbl) +-- for touch in *win\touches_ended! +-- etbl = +-- event: "touches_ended" +-- data: win\touch_position(touch) +-- for _,uiobj in pairs(ui.events.touch) +-- has_fire(uiobj) +-- uiobj\fire(etbl) + -- todo: expand this with controller support. +) + +ui diff --git a/src/ui/button.moon b/src/ui/button.moon new file mode 100644 index 0000000..9b55a44 --- /dev/null +++ b/src/ui/button.moon @@ -0,0 +1,133 @@ + +s = require("sprites") +util = require("util") +color = require("color") +world = require("world") +sprites = require("sprites") +states = {"up","down"} +rows = {"upper","mid","lower"} +cols = {"left","mid","right"} +class Button + --am.sprite() only works once the window is created + @initialized = false + @initialize: => + for _, state, _, row, _, col in util.cartesian(states, rows, cols) + name = table.concat({state,row,col},"_") + assert(s["button_" .. name], "Failed to find sprite:" .. name) + @[name] = am.sprite(s["button_" .. name],"left","top") + @initialized = true + new: (x,y,w,h,text,font)=> + if not @@initialized + @@initialize! + @em = 16 -- width of a character + text = text or "" + assert(w > 15, "Button must have at least width 15") + @node = am.group! + position = am.translate(x,y+h)\tag("position") + @up_sprites = am.group! + position\append(@up_sprites) + @down_sprites = am.group! + @node\append(position) + @up_sprites\append(@@up_upper_left) + @up_sprites\append( + am.translate(@@up_upper_left.width,0)\append( + am.scale(w - @@up_upper_left.width - @@up_upper_right.width,1)\append( + @@up_upper_mid + ))) + @up_sprites\append( + am.translate(w - @@up_upper_right.width, 0)\append( + @@up_upper_right + )) + mid_height = h - @@up_upper_left.height - @@up_lower_left.height + @up_sprites\append( + am.translate(0,-@@up_upper_left.height)\append( + am.scale(1,mid_height)\append( + @@up_mid_left + ))) + @up_sprites\append( + am.translate(@@up_upper_left.width, -@@up_upper_left.height)\append( + am.scale(w - @@up_mid_left.width - @@up_mid_right.width, h - @@up_upper_mid.height - @@up_lower_mid.height)\append( + @@up_mid_mid + ))) + @up_sprites\append( + am.translate(w - @@up_mid_right.width,-@@up_upper_right.height)\append( + am.scale(1,h - @@up_upper_right.height - @@up_lower_right.height)\append( + @@up_mid_right + ))) + @up_sprites\append( + am.translate(0,-(h - @@up_lower_left.height))\append( + @@up_lower_left + )) + @up_sprites\append( + am.translate(@@up_lower_left.width,-(h - @@up_lower_mid.height))\append( + am.scale(w - @@up_lower_left.width - @@up_lower_right.width,1)\append( + @@up_lower_mid + ))) + @up_sprites\append( + am.translate(w - @@up_lower_right.width, -(h - @@up_lower_right.height))\append( + @@up_lower_right + )) + @down_sprites\append(@@down_upper_left) + @down_sprites\append( + am.translate(@@down_upper_left.width,0)\append( + am.scale(w - @@down_upper_left.width - @@down_upper_right.width,1)\append( + @@down_upper_mid + ))) + @down_sprites\append( + am.translate(w - @@down_upper_right.width, 0)\append( + @@down_upper_right + )) + mid_height = h - @@down_upper_left.height - @@down_lower_left.height + @down_sprites\append( + am.translate(0,-@@down_upper_left.height)\append( + am.scale(1,mid_height)\append( + @@down_mid_left + ))) + @down_sprites\append( + am.translate(@@down_upper_left.width, -@@down_upper_left.height)\append( + am.scale(w - @@down_mid_left.width - @@down_mid_right.width, h - @@down_upper_mid.height - @@down_lower_mid.height)\append( + @@down_mid_mid + ))) + @down_sprites\append( + am.translate(w - @@down_mid_right.width,-@@down_upper_right.height)\append( + am.scale(1,h - @@down_upper_right.height - @@down_lower_right.height)\append( + @@down_mid_right + ))) + @down_sprites\append( + am.translate(0,-(h - @@down_lower_left.height))\append( + @@down_lower_left + )) + @down_sprites\append( + am.translate(@@down_lower_left.width,-(h - @@down_lower_mid.height))\append( + am.scale(w - @@down_lower_left.width - @@down_lower_right.width,1)\append( + @@down_lower_mid + ))) + @down_sprites\append( + am.translate(w - @@down_lower_right.width, -(h - @@down_lower_right.height))\append( + @@down_lower_right + )) + if font + @text = am.text(font, text, "left","top", color.am_color.foreground) + else + @text = am.text(text, "left", "top", color.am_color.foreground) + position\append( + am.translate(@@down_upper_left.width, -@@down_upper_right.height)\append( + am.scale(world.controller.text_size)\append( + @text + ))) + @depressed = false + down: () => + @depressed = true + @.node("position")\replace(@up_sprites, @down_sprites) + up: () => + @depressed = false + @.node("position")\replace(@down_sprites, @up_sprites) + fire: (e) => + if e.event == "touches_began" or e.event == "mouse_down" + @down! + if @on + @on(e) + if e.event == "touches_ended" or e.event == "mouse_up" + @up! + +Button diff --git a/src/ui/checkbox.moon b/src/ui/checkbox.moon new file mode 100644 index 0000000..e76a790 --- /dev/null +++ b/src/ui/checkbox.moon @@ -0,0 +1,13 @@ +Button = require("ui.button") +class Checkbox extends Button + fire: (e) => + if e.event == "mouse_down" + if @depressed + @up! + else + @down! + --@down! + if @on + @on(@depressed) + +Checkbox diff --git a/src/ui/joystick.moon b/src/ui/joystick.moon new file mode 100644 index 0000000..98936ed --- /dev/null +++ b/src/ui/joystick.moon @@ -0,0 +1,98 @@ + +color = require("color") +window = require("window") + +circle_cache = setmetatable({},{__mode: "v"}) +hollow_circle = (x,y,radius, thickness, color) -> + key = string.format("%d\0%d\0%d\0%d",x,y,radius, thickness) + if circle_cache[key] + return circle_cache[key] + arr = {} + segments = 60 + step = (2*math.pi) / segments + for i = 0,2*math.pi, step + arr[#arr+1] = vec2(i+step, 1) + arr[#arr+1] = vec2(i, 1) + arr[#arr+1] = vec2(i+step,0) + arr[#arr+1] = vec2(i+step,0) + arr[#arr+1] = vec2(i,1) + arr[#arr+1] = vec2(i,0) + circle = am.use_program(am.program([[ + precision highp float; + attribute vec2 index; + uniform float thickness; + uniform float radius; + uniform mat4 MV; + uniform mat4 P; + void main() { + float distance = thickness * index[1]; + vec2 vert = vec2(cos(index[0]) * (radius - distance), sin(index[0]) * (radius - distance)); + gl_Position = P * MV * vec4(vert, 0.0, 1.0); + } + ]],[[ + precision mediump float; + uniform vec4 color; + void main() { + gl_FragColor = color; + } + ]]))\append(am.bind({ + MV: mat4( + 1, 0, 0, 0 + 0, 1, 0, 0 + 0, 0, 1, 0 + x, y, 0, 1 + ) + thickness: thickness + radius: radius + index: am.vec2_array(arr) + color: color + })\append(am.draw("triangles"))) + circle_cache[key] = circle + circle + +class Joystick + --am.sprite() only works once the window is created + @initialized = false + @initialize: => + @hollow_circle = am.group! + step = 0.5 + thickness = 0.02 + lastpoint = vec2(1,0) + print("color.am_color.background is:", color.am_color.background) + for k,v in pairs(color.am_color) + print(k,":",v) + highlight_start = (5/8) * math.pi + highlight_end = (7/8) * math.pi + shadow_start = (3/2) * math.pi + shadow_end = 2 * math.pi + for i = 0,2*math.pi,step + nextpoint = vec2(math.cos(i), math.sin(i)) + @hollow_circle\append(am.line(lastpoint + vec2(1,1), nextpoint + vec2(1,1), thickness, color.am_color.outline)) + --@hollow_circle\append(am.line(lastpoint, nextpoint, thickness, color.am_color.background)) + lastpoint = nextpoint + @hollow_circle\append(am.line(lastpoint, vec2(1,0), thickness * 2, color.am_color.outline)) + @hollow_circle\append(am.line(lastpoint,vec2(1,0), thickness, color.am_color.background)) + @initialized = true + new: (x,y,r)=> + if not @@initialized + @@initialize! + @node = am.group! + position = am.translate(x,y)\tag("position") + @node\append(position) + --position\append(am.circle(vec2(x,y), r, color.am_color.background)) + @stick_pos = am.translate(0,0)\tag("stick") + position\append( + @stick_pos\append( + am.circle(vec2(0,0), r/9, color.am_color.outline)\append( + am.circle(vec2(0,0), r/10, color.am_color.background)\append( + am.circle(vec2(-r/60,r/60),r/15, color.am_color.foreground)\append( + am.circle(vec2(5,-5),r/13, color.am_color.background) + ))))) + --position\append(am.scale(r,r)\append(@@hollow_circle)) + --position\append(am.circle(vec2(x,y),r)\append(am.blend("subtract")\append(am.circle(vec2(x,y),r-10)))) + position\append(hollow_circle(x,y,r,8,color.am_color.outline)) + position\append(hollow_circle(x,y,r-math.sqrt(2),5,color.am_color.background)) + fire: (tbl) => + print("Fired",tbl) + +Joystick diff --git a/src/ui/textbox.moon b/src/ui/textbox.moon new file mode 100644 index 0000000..38c6d84 --- /dev/null +++ b/src/ui/textbox.moon @@ -0,0 +1,78 @@ + +color = require("color") +Button = require("ui.button") +am.eval_js(require("textbox_bridge")) + +valid_chars = "abcdefghijklmnopqrstuvwxyz" +shifted_nums = "!@#$%^&*()" +i = 0 +class Textbox extends Button + new: (x,y,w,h,value,placeholder) => + super(x,y,w,h,value) + @id = i + i = i + 1 + args = am.to_json({ + name: value or "" + placeholder: placeholder or "" + }) + --am.eval_js("window.amulet.window_has_focus = 0;") + am.eval_js("window.TEXTBOX.create_textbox(" .. args .. ");") + if value == "" + @text.text = placeholder + --@text.color = color.am_color.shadow + @cursor = am.group( + am.translate(@em,0)\append( + am.rect(0,0,@em/4,-@em,color.am_color.foreground) + )) + @cursor\action(() => + if not @should_hide + @hidden = math.floor(am.current_time! * 2) % 2 == 0 + else + @hidden = true + ) + @cursor.should_hide = true + @text\append(@cursor) + @cursor_pos = #@text.text + @update_cursor_pos! + @max_chars = math.huge + @cursor + down: () => + super! + --@cursor.should_hide = false + --@text.color = color.am_color.foreground + print("textbox down") + am.eval_js("window.TEXTBOX.focus(" .. am.to_json({id: @id}) .. ");") + up: () => + super! + print("Textbox up") + am.eval_js("window.TEXTBOX.blur(" .. am.to_json({id: @id}) .. ");") + val = am.eval_js("window.TEXTBOX.get_text(" .. am.to_json({id:@id}) .. ");") + print("Up, got val:", val) + --@cursor.should_hide = true + --@text.color = color.am_color.shadow + update_cursor_pos: () => + @.cursor("translate").x = @cursor_pos * 9 + fire: (e) => + if e.event == "mouse_down" + @down! + if @on + @on(e) + add_key = e.event == "keys_pressed" and @depressed + if add_key + for key in *e.data + if key == "kp_enter" or key == "enter" + if @on + @on(e) + elseif key == "escape" + @up! + + @update_cursor_pos! + text = @text.text + newtext = am.eval_js("window.TEXTBOX.get_text(" .. am.to_json({id:@id}) .. ");") + if newtext != text + @text.text = newtext + if @onchange + @onchange! + false + +Textbox diff --git a/src/util.lua b/src/util.lua new file mode 100644 index 0000000..f74953a --- /dev/null +++ b/src/util.lua @@ -0,0 +1,142 @@ +--[[ +Various helpful functions +]] +local util = {} +function util.cartesian(...) + -- cartesian(tbl1, tbl2, tbl3, ...) + -- for each table, returns a permutation of key, value in tbl1, tbl2, ect. + local args = {...} + return coroutine.wrap(function() + local cursors = {} -- cursors[1], cursors[3], ect. are the keys + for k,v in ipairs(args) do + local a,b = next(v,nil) + cursors[(k*2) - 1] = a + cursors[(k*2)] = b + end + coroutine.yield(unpack(cursors)) + local any_left = true + while any_left do + while next(args[#args],cursors[#cursors - 1]) do + local a,b = next(args[#args],cursors[#cursors - 1]) + cursors[#cursors - 1] = a + cursors[#cursors] = b + coroutine.yield(unpack(cursors)) + end + any_left = false + for i = #args, 1, -1 do + if next(args[i],cursors[(i*2)-1]) then + cursors[(i*2)-1], cursors[i*2] = next(args[i],cursors[(i*2)-1]) + for j = i+1, #args do + cursors[(j*2)-1], cursors[j*2] = next(args[j],nil) + end + coroutine.yield(unpack(cursors)) + any_left = true + break + end + end + end + end) +end + +-- Override tostring to display more info about the table +local old_tostring = tostring +local numtabs = 0 +local printed_tables = {} +local function tostring_helper(el) + assert(type(el) == "table", "Tried to call helper with something that was not a table, it was a " .. type(el)) + local mt = getmetatable(el) + if mt and mt.__tostring then + return mt.__tostring(el) + elseif printed_tables[el] == true then + return old_tostring(el) + else + printed_tables[el] = true + numtabs = numtabs + 1 + local strbuilder = {"{"} + for k,v in pairs(el) do + local key,value + if type(k) == "table" then + key = tostring_helper(k) + else + key = old_tostring(k) + end + if type(v) == "table" then + value = tostring_helper(v) + else + value = old_tostring(v) + end + strbuilder[#strbuilder + 1] = string.format("%s%s : %s", string.rep("\t",numtabs), key, value) + end + strbuilder[#strbuilder + 1] = string.rep("\t",numtabs - 1) .. "}" + numtabs = numtabs - 1 + return table.concat(strbuilder,"\n") + end + +end +function tostring(el) + printed_tables = {} + if type(el) == "table" then + return tostring_helper(el) + end + return old_tostring(el) +end + +function util.reverse(tbl, val) + val = val or true + local ret = {} + for _,v in ipairs(tbl) do + ret[v] = val + end + return ret +end + +function util.typecheck(tbl, ...) + local args = {...} + assert(#args % 2 == 0,"Typecheck should have an odd number of arguments, found " .. tostring(#args + 1) .. ".") + for i = 1, #args, 2 do + assert(args[i] and type(args[i]) == "string", "Cannot check a field of type " .. type(args[i]) .. " at position " .. tostring(i + 1) .. ".") + assert(tbl[args[i]], "Failed to find a field: " .. args[i]) + assert(args[i+1] and type(args[i + 1]) == "string", "Cannot check for a type " .. type(args[i + 1]) .. " at position " .. tostring(i + 2) .. ".") + assert(type(tbl[args[i]]) == args[i+1], "Expected a " .. args[i+1] .. " at position " .. tostring(i+2) .. " but found a " .. type(tbl[args[i]])) + end + return true +end + +function util.peer_to_code(str) + -- Turn peerjs peer ids into shorter strings + -- Example: 0dbbe67a-5358-4ea2-910a-195754b556a7 + -- = 16 bytes + local buffer = am.buffer(16) + local view = buffer:view("ubyte") + local i = 0 + for byte in str:gmatch("%x%x") do + local n = tonumber(byte,16) + view[(i % 16) + 1] = n + i = i + 1 + end + local encoded = am.base64_encode(buffer) + return encoded:gsub("[/+]",{["/"] = "-",["+"] = "_"}):sub(1,22) -- chop the last 2 glyphs, assume 16 bytes +end + +function util.code_to_peer(str) + local padded = str:gsub("[_-]",{["-"] = "/", ["_"] = "+"}) .. "==" + local buffer = am.base64_decode(padded) + local view = buffer:view("ubyte") + local dashes = {4,2,2,2,6} + local builder = {} + local i = 1 + while #dashes > 0 do + local nbytes = table.remove(dashes,1) + for _ = 1, nbytes do + table.insert(builder, string.format("%02x",view[i])) + i = i + 1 + end + if #dashes > 0 then + table.insert(builder,"-") + end + end + ret = table.concat(builder) + return ret +end + +return util diff --git a/src/window.moon b/src/window.moon new file mode 100644 index 0000000..7fc7953 --- /dev/null +++ b/src/window.moon @@ -0,0 +1,11 @@ +color = require("color") +-- Special file to hold the window, no dependencies! +win = am.window{ + title: "GGJ 2026" + width: 360 + height: 800 + clear_color: color.am_color.background + depth_buffer: true +} +win.scene = am.group! +win diff --git a/src/world.moon b/src/world.moon new file mode 100644 index 0000000..d3a2ca7 --- /dev/null +++ b/src/world.moon @@ -0,0 +1,52 @@ +-- Global state +--win = require("window") +hc = require("party.hc.init") +--ecs = require("ecs") +--settings = require("settings") +color = require("color") +log = require("log") + +MAX_LAMPS = 8 + +--Use a collider to decide what to render +x = { + -- client-side viewport offsets from the world + world_x: 0 + world_y: 0 + -- Have we selected an input type yet? + controller: { -- logical input abstraction, not a physical input device controller + text_size: 1 + } + domain: "client" -- "client" or "server" + -- (Client) Level information + level: { + graphics:{} -- Client side graphics + entities:{} -- Client side entities + graphic_world: hc.new(5) -- Client side graphics world + physics_world: hc.new(1) + collider: nil -- Collider in the physics world to figure out what we can see + } + -- (Server Networked) level information + level_sync: { + name: "" -- The name of the level + data: {} -- sequence, holds arguments for level initalization + ents: {} -- holds all the entities in the level + entid: 0 -- a nonce entid, just keep increasing and allows for holes in "ents" + peers: {} -- holds peers in [peerid: string] = Entity + phys: hc.new(5) -- The physics world we need to collide with + ref: nil -- Holds a ref to the level, has things like name, get_spawn_location(), ect. + } + sync_time: () -> + am.current_time! + hub: nil -- server, filled out later + network: nil -- client, filled out later + -- Stuff that goes to the shader + geom_view: nil -- am.vec3_array + uv_view: nil -- am.vec4_array + check: () => + assert(@level_sync.ents, "Ents is nil") + assert(type(@level_sync.ents), "ents is not a table") +} + +log.info("At the end of src/world.moon, world_x is" .. tostring(x.world_x)) +x diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..d4839a6 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +# Tests package diff --git a/tests/integration/README.md b/tests/integration/README.md new file mode 100644 index 0000000..99f74a6 --- /dev/null +++ b/tests/integration/README.md @@ -0,0 +1,127 @@ +# Integration Tests with Selenium + +This directory contains Selenium-based integration tests for the web application. + +## Setup + +1. Install Python dependencies: +```bash +pip install -r requirements-test.txt +``` + +2. Chrome browser must be installed (ChromeDriver will be downloaded automatically) + +## Running Tests + +### Run all tests: +```bash +pytest +``` + +### Run specific test categories: +```bash +# Smoke tests only +pytest -m smoke + +# UI interaction tests +pytest -m ui + +# Network tests +pytest -m network +``` + +### Run in headless mode: +```bash +$env:HEADLESS="1"; pytest +``` + +### Run with verbose output: +```bash +pytest -v +``` + +### Run specific test file: +```bash +pytest tests/integration/test_smoke.py +``` + +### Run specific test function: +```bash +pytest tests/integration/test_smoke.py::test_page_loads +``` + +## How It Works + +The test suite automatically: +1. Starts a local HTTP server on a free port +2. Serves the application from the `ggj26/` directory +3. Runs tests against the local server +4. Shuts down the server when tests complete + +This ensures JavaScript features requiring a web server (like CORS, modules, etc.) work correctly. + +## Environment Variables + +- `APP_URL`: Override the automatic HTTP server to test against an external URL + ```bash + $env:APP_URL="http://localhost:8080"; pytest + ``` + +- `HEADLESS`: Set to "1" to run tests in headless mode (no visible browser) + ```bash + $env:HEADLESS="1"; pytest + ``` + +## Test Structure + +- `conftest.py`: Pytest fixtures and configuration + - `http_server`: Starts local HTTP server (session-scoped) + - `base_url`: URL for the application + - `driver`: Selenium WebDriver instance + - `app`: Loads the application + - `ready_app`: Loads app and waits for it to be ready + - `wait`: WebDriverWait helper + +- `test_smoke.py`: Basic smoke tests to verify core functionality +- `test_ui.py`: UI interaction tests (clicks, keyboard, mouse) + +## Writing New Tests + +Example test: +```python +import pytest +from selenium.webdriver.common.by import By + +@pytest.mark.smoke +def test_my_feature(app): + """Test description.""" + element = app.find_element(By.ID, "my-element") + assert element.is_displayed() +``` + +## Debugging + +### View browser during test execution: +Don't set `HEADLESS=1` - tests will run with visible browser. + +### Add breakpoints: +```python +import pdb; pdb.set_trace() +``` + +### Take screenshots on failure: +```python +def test_something(app): + try: + # test code + pass + except: + app.save_screenshot("failure.png") + raise +``` + +### Check console logs: +```python +logs = app.get_log("browser") +print(logs) +``` diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 0000000..a265048 --- /dev/null +++ b/tests/integration/__init__.py @@ -0,0 +1 @@ +# Integration tests package diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py new file mode 100644 index 0000000..02302ca --- /dev/null +++ b/tests/integration/conftest.py @@ -0,0 +1,273 @@ +""" +Pytest configuration and fixtures for Selenium integration tests. +""" +import pytest +import os +import time +import threading +import socket +from http.server import HTTPServer, SimpleHTTPRequestHandler +from selenium import webdriver +from selenium.webdriver.chrome.service import Service +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.support.ui import WebDriverWait +from webdriver_manager.chrome import ChromeDriverManager + + +def find_free_port(): + """Find a free port to use for the HTTP server.""" + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(('', 0)) + s.listen(1) + port = s.getsockname()[1] + return port + + +@pytest.fixture(scope="session") +def http_server(): + """Start a local HTTP server to serve the application.""" + # Check if APP_URL is provided (to skip server for external testing) + if os.getenv("APP_URL"): + yield os.getenv("APP_URL") + return + + # Find project root and ggj26 directory + project_root = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + ggj26_dir = os.path.join(project_root, "ggj26") + + # Verify directory exists + if not os.path.exists(ggj26_dir): + raise FileNotFoundError(f"ggj26 directory not found at {ggj26_dir}") + + # Find a free port + port = find_free_port() + + # Change to ggj26 directory for serving + original_dir = os.getcwd() + os.chdir(ggj26_dir) + + # Create HTTP server + server = HTTPServer(('localhost', port), SimpleHTTPRequestHandler) + + # Start server in background thread + server_thread = threading.Thread(target=server.serve_forever, daemon=True) + server_thread.start() + + # Wait a moment for server to start + time.sleep(0.5) + + base_url = f"http://localhost:{port}" + print(f"\nStarted HTTP server at {base_url}") + + yield base_url + + # Cleanup + server.shutdown() + os.chdir(original_dir) + + +@pytest.fixture(scope="session") +def base_url(http_server): + """Base URL for the application.""" + return http_server + + +@pytest.fixture(scope="function") +def chrome_options(): + """Chrome options for Selenium WebDriver.""" + options = Options() + + # Set Chromium binary location + chromium_path = os.getenv("CHROMIUM_PATH", r"D:\Programs\Chromium\chrome-win\chrome.exe") + if os.path.exists(chromium_path): + options.binary_location = chromium_path + + # Headless mode - set HEADLESS=1 to run tests without opening browser + if os.getenv("HEADLESS") == "1": + options.add_argument("--headless=new") + + # Additional options for stability + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_argument("--disable-gpu") + options.add_argument("--window-size=1920,1080") + + # Enable WebGL and canvas + options.add_argument("--enable-webgl") + options.add_argument("--ignore-gpu-blacklist") + + # Allow file access (needed for file:// protocol) + options.add_argument("--allow-file-access-from-files") + + # Disable unnecessary features + options.add_argument("--disable-extensions") + options.add_experimental_option("excludeSwitches", ["enable-logging"]) + + return options + + +@pytest.fixture(scope="session") +def chrome_options_session(): + """Chrome options for Selenium WebDriver (session-scoped).""" + options = Options() + + # Set Chromium binary location + chromium_path = os.getenv("CHROMIUM_PATH", r"D:\Programs\Chromium\chrome-win\chrome.exe") + if os.path.exists(chromium_path): + options.binary_location = chromium_path + + # Headless mode - set HEADLESS=1 to run tests without opening browser + if os.getenv("HEADLESS") == "1": + options.add_argument("--headless=new") + + # Additional options for stability + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_argument("--disable-gpu") + options.add_argument("--window-size=1920,1080") + + # Enable WebGL and canvas + options.add_argument("--enable-webgl") + options.add_argument("--ignore-gpu-blacklist") + + # Allow file access (needed for file:// protocol) + options.add_argument("--allow-file-access-from-files") + + # Disable unnecessary features + options.add_argument("--disable-extensions") + options.add_experimental_option("excludeSwitches", ["enable-logging"]) + + return options + + +@pytest.fixture(scope="session") +def driver(chrome_options_session): + """Selenium WebDriver instance (session-scoped - reused across all tests).""" + # Get the Chrome/Chromium version to download matching ChromeDriver + from webdriver_manager.chrome import ChromeDriverManager + from webdriver_manager.core.os_manager import ChromeType + + # Install ChromeDriver that matches the Chromium version + service = Service(ChromeDriverManager(chrome_type=ChromeType.CHROMIUM).install()) + driver = webdriver.Chrome(service=service, options=chrome_options_session) + driver.implicitly_wait(10) + + yield driver + + # Teardown - only happens once at end of session + driver.quit() + + +@pytest.fixture(scope="function") +def wait(driver): + """WebDriverWait instance with 10 second timeout.""" + return WebDriverWait(driver, 10) + + +@pytest.fixture(scope="function") +def app(driver, base_url): + """Load the application in the browser (reloads for each test).""" + driver.get(base_url) + + # Wait for initial page load + time.sleep(2) + + yield driver + + # Clear browser logs after each test to avoid cross-contamination + try: + driver.get_log("browser") + except: + pass + + +def check_for_amulet_error(driver): + """ + Check if Amulet has logged any errors via the log_js_bridge. + Returns tuple (has_error, error_text) + """ + try: + # Check if window.amuletErrors exists and has any errors + amulet_errors = driver.execute_script(""" + if (typeof window.amuletErrors !== 'undefined' && window.amuletErrors.length > 0) { + return window.amuletErrors; + } + return null; + """) + + if amulet_errors and len(amulet_errors) > 0: + # Take a screenshot for debugging + import datetime + timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + screenshot_path = f"error_screenshot_{timestamp}.png" + driver.save_screenshot(screenshot_path) + + error_messages = [f"{err['level'].upper()}: {err['message']}" for err in amulet_errors] + return True, f"Amulet errors detected:\n" + "\n".join(error_messages) + f"\n(screenshot: {screenshot_path})" + + return False, None + except Exception as e: + # If we can't check, assume no error + return False, None + + +def check_javascript_errors(driver, ignore_favicon=True): + """ + Check for JavaScript errors in the browser console. + Returns a list of error messages (empty if no errors). + """ + logs = driver.get_log("browser") + errors = [log for log in logs if log["level"] == "SEVERE"] + + # Filter out favicon 404 errors if requested + if ignore_favicon: + errors = [log for log in errors if "favicon.ico" not in log.get("message", "")] + + return errors + + +def assert_no_javascript_errors(driver, ignore_favicon=True): + """ + Assert that there are no JavaScript errors in the browser console. + Also checks for Amulet error screens. + Raises AssertionError if errors are found. + """ + # Check for Amulet blue screen errors first + from selenium.webdriver.common.by import By + has_amulet_error, error_msg = check_for_amulet_error(driver) + if has_amulet_error: + pytest.fail(f"Amulet error detected: {error_msg}") + + # Then check console logs + errors = check_javascript_errors(driver, ignore_favicon) + assert len(errors) == 0, f"JavaScript errors found: {errors}" + + +def wait_for_canvas_ready(driver, timeout=30): + """ + Wait for the Amulet canvas to be ready. + Returns True when canvas is visible and status overlay is hidden. + """ + wait = WebDriverWait(driver, timeout) + + # Wait for status overlay to disappear (indicates loading is complete) + from selenium.webdriver.common.by import By + from selenium.webdriver.support import expected_conditions as EC + + try: + # Wait for status-overlay to have display:none or be removed + wait.until( + EC.invisibility_of_element_located((By.ID, "status-overlay")) + ) + return True + except: + return False + + +@pytest.fixture(scope="function") +def ready_app(app): + """Load app and wait for it to be ready.""" + if wait_for_canvas_ready(app): + return app + else: + pytest.fail("Application did not load within timeout period") diff --git a/tests/integration/test_client_hub_messaging.py b/tests/integration/test_client_hub_messaging.py new file mode 100644 index 0000000..f5765cf --- /dev/null +++ b/tests/integration/test_client_hub_messaging.py @@ -0,0 +1,374 @@ +""" +Integration tests for client-hub messaging using Selenium. +Tests that client doesn't receive its own Join message back. +""" +import pytest +import time +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC + +pytestmark = [pytest.mark.nondestructive] + + +@pytest.mark.integration +def test_client_does_not_receive_own_join_message(ready_app, wait): + """ + Test that when a client sends a Join message to the hub, + it doesn't receive it back through its own message handler. + + This test: + 1. Clicks "Host" to create a hub and connect host client + 2. Injects JS to monitor if the host client receives a Join message + 3. Verifies the host client does NOT receive its own Join message + """ + driver = ready_app + + # Inject monitoring script before clicking Host + driver.execute_script(""" + // Track if client receives Join message + window.clientReceivedOwnJoin = false; + + // Monkey-patch Client.handle_message to detect Join messages + (function() { + // Wait for modules to load + var checkInterval = setInterval(function() { + try { + var clientModule = require('client'); + var Client = clientModule.Client; + + if (Client && Client.prototype && Client.prototype.handle_message) { + var originalHandleMessage = Client.prototype.handle_message; + + Client.prototype.handle_message = function(callback_id, message_data) { + // Check if this is a Join message + if (message_data && Array.isArray(message_data) && message_data[0] === 'Join') { + console.log('[TEST] Client received Join message!'); + window.clientReceivedOwnJoin = true; + } + + // Call original method + return originalHandleMessage.call(this, callback_id, message_data); + }; + + console.log('[TEST] Successfully patched Client.handle_message'); + clearInterval(checkInterval); + } + } catch (e) { + // Modules not loaded yet, keep trying + } + }, 100); + + // Give up after 10 seconds + setTimeout(function() { + clearInterval(checkInterval); + }, 10000); + })(); + """) + + # Wait for Host button to be available + time.sleep(1) + + # Click Host button + try: + # Try to find and click Host button via JavaScript + clicked = driver.execute_script(""" + // Look for Host button in the UI + var buttons = document.querySelectorAll('canvas'); + if (buttons.length > 0) { + // Simulate click on canvas where Host button would be + var canvas = buttons[0]; + var event = new MouseEvent('click', { + view: window, + bubbles: true, + cancelable: true, + clientX: canvas.width / 2, + clientY: canvas.height / 2 - 64 // Approximate position of Host button + }); + canvas.dispatchEvent(event); + return true; + } + return false; + """) + + if not clicked: + # Fallback: Try clicking the canvas directly + canvas = driver.find_element(By.ID, "canvas") + canvas.click() + except Exception as e: + pytest.fail(f"Failed to click Host button: {e}") + + # Wait for connection to establish + time.sleep(3) + + # Check if client received its own Join message + received_own_join = driver.execute_script("return window.clientReceivedOwnJoin || false;") + + # Get console logs for debugging + logs = driver.get_log("browser") + test_logs = [log['message'] for log in logs if '[TEST]' in log.get('message', '')] + + print(f"\n=== Test Debug Info ===") + print(f"Client received own Join: {received_own_join}") + print(f"Test logs: {test_logs}") + + # Assert client did NOT receive its own Join message + assert not received_own_join, "Client should not receive its own Join message back from hub" + + +@pytest.mark.integration +def test_hub_receives_join_message(ready_app, wait): + """ + Test that the hub correctly receives the Join message from a connecting client. + + This test: + 1. Clicks "Host" to create a hub + 2. Monitors hub.handle_message for Join messages + 3. Verifies the hub receives the Join message + """ + driver = ready_app + + # Inject monitoring script + driver.execute_script(""" + // Track if hub receives Join message + window.hubReceivedJoin = false; + window.joinMessageData = null; + + // Monkey-patch Hub.handle_message to detect Join messages + (function() { + var checkInterval = setInterval(function() { + try { + var hubModule = require('hub'); + var Hub = hubModule.Hub; + + if (Hub && Hub.prototype && Hub.prototype.handle_message) { + var originalHandleMessage = Hub.prototype.handle_message; + + Hub.prototype.handle_message = function(from_client, msgname, data) { + // Normalise arguments: depending on the Lua/JS bridge, + // the message array may arrive as `msgname` or `data`, + // and may be 0- or 1-indexed from JS. + var message = null; + if (Array.isArray(msgname)) { + message = msgname; + } else if (Array.isArray(data)) { + message = data; + } + var msg_type = null; + var msg_data = null; + if (message) { + msg_type = message[0] || message[1] || null; + msg_data = message[1] || message[2] || null; + } + if (msg_type === 'Join') { + console.log('[TEST] Hub received Join message from:', from_client); + window.hubReceivedJoin = true; + window.joinMessageData = msg_data; + } + + // Call original method + return originalHandleMessage.call(this, from_client, msgname, data); + }; + + console.log('[TEST] Successfully patched Hub.handle_message'); + clearInterval(checkInterval); + } + } catch (e) { + // Modules not loaded yet, keep trying + } + }, 100); + + setTimeout(function() { + clearInterval(checkInterval); + }, 10000); + })(); + """) + + # Wait for modules to load + time.sleep(1) + + # Click Host button (same approach as previous test) + try: + clicked = driver.execute_script(""" + var canvas = document.getElementById('canvas'); + if (canvas) { + var event = new MouseEvent('click', { + view: window, + bubbles: true, + cancelable: true, + clientX: canvas.width / 2, + clientY: canvas.height / 2 - 64 + }); + canvas.dispatchEvent(event); + return true; + } + return false; + """) + + if not clicked: + canvas = driver.find_element(By.ID, "canvas") + canvas.click() + except Exception as e: + pytest.fail(f"Failed to click Host button: {e}") + + # Wait for connection and message processing + time.sleep(3) + + # Debug world/hub state before assertions + world_debug = driver.execute_script(""" + try { + var world = typeof require === 'function' ? require('world') : null; + return { + hasWorld: !!world, + hasHub: !!(world && world.hub), + hasNetwork: !!(world && world.network) + }; + } catch (e) { + return { error: String(e) }; + } + """) + print(f"World debug: {world_debug}") + js_flags = driver.execute_script(""" + return { + clientConnected: !!window._clientConnectedToHub, + clientJoinPayload: window._clientJoinPayload || null, + hubJoinReceived: !!window._hubJoinReceived, + hubJoinData: window._hubJoinData || null + }; + """) + print(f"JS flags: {js_flags}") + + # Check if hub received Join message (Lua side exposes this via js_bridge) + hub_received_join = driver.execute_script("return (window.hubReceivedJoin || window._hubJoinReceived) || false;") + join_data = driver.execute_script("return window.joinMessageData || window._hubJoinData || null;") + + # Get console logs for debugging + logs = driver.get_log("browser") + test_logs = [log['message'] for log in logs if '[TEST]' in log.get('message', '')] + + print(f"\n=== Test Debug Info ===") + print(f"Hub received Join: {hub_received_join}") + print(f"Join data: {join_data}") + print(f"Test logs: {test_logs}") + + # Assert hub received the Join message + assert hub_received_join, "Hub should receive Join message from connecting client" + assert join_data is not None, "Join message should contain data" + # Check that join data has a 'name' field + if join_data: + assert 'name' in join_data, "Join message data should contain 'name' field" + + +@pytest.mark.integration +def test_message_flow_integrity(ready_app, wait): + """ + End-to-end test of message flow: client sends, hub receives, but client doesn't get echo. + + This combines both previous tests to verify the complete message flow. + """ + driver = ready_app + + # Inject comprehensive monitoring + driver.execute_script(""" + window.testResults = { + clientReceivedJoin: false, + hubReceivedJoin: false, + joinData: null + }; + + (function() { + var checkInterval = setInterval(function() { + try { + // Patch Client + var clientModule = require('client'); + var hubModule = require('hub'); + + if (clientModule && clientModule.Client && + hubModule && hubModule.Hub) { + + var Client = clientModule.Client; + var Hub = hubModule.Hub; + + // Patch client + if (Client.prototype.handle_message) { + var origClientHandle = Client.prototype.handle_message; + Client.prototype.handle_message = function(cid, mdata) { + if (mdata && mdata[0] === 'Join') { + console.log('[TEST] CLIENT RECEIVED JOIN - THIS IS THE BUG!'); + window.testResults.clientReceivedJoin = true; + } + return origClientHandle.call(this, cid, mdata); + }; + } + + // Patch hub + if (Hub.prototype.handle_message) { + var origHubHandle = Hub.prototype.handle_message; + Hub.prototype.handle_message = function(from, msgname, data) { + // Normalise message array from Lua/JS bridge. + var message = null; + if (Array.isArray(msgname)) { + message = msgname; + } else if (Array.isArray(data)) { + message = data; + } + var msg_type = null; + var msg_data = null; + if (message) { + msg_type = message[0] || message[1] || null; + msg_data = message[1] || message[2] || null; + } + if (msg_type === 'Join') { + console.log('[TEST] Hub received Join - CORRECT!'); + window.testResults.hubReceivedJoin = true; + window.testResults.joinData = msg_data; + } + return origHubHandle.call(this, from, msgname, data); + }; + } + + console.log('[TEST] Patching complete'); + clearInterval(checkInterval); + } + } catch (e) { + // Keep trying + } + }, 100); + + setTimeout(function() { clearInterval(checkInterval); }, 10000); + })(); + """) + + time.sleep(1) + + # Click Host + canvas = driver.find_element(By.ID, "canvas") + canvas.click() + + # Wait for messages to flow + time.sleep(3) + + # Get results, augmenting from Lua-side hub instrumentation if present + results = driver.execute_script(""" + if (typeof window.testResults === 'undefined') { + window.testResults = { + clientReceivedJoin: false, + hubReceivedJoin: false, + joinData: null + }; + } + if (window._hubJoinReceived) { + window.testResults.hubReceivedJoin = true; + } + if (window._hubJoinData) { + window.testResults.joinData = window._hubJoinData; + } + return window.testResults; + """) + + print(f"\n=== Complete Test Results ===") + print(f"Results: {results}") + + # Assertions + assert results['hubReceivedJoin'], "Hub must receive Join message" + assert not results['clientReceivedJoin'], "Client must NOT receive its own Join message (bug fixed!)" + assert results['joinData'] is not None, "Join message must have data" diff --git a/tests/integration/test_error_detection.py b/tests/integration/test_error_detection.py new file mode 100644 index 0000000..7cd9bf9 --- /dev/null +++ b/tests/integration/test_error_detection.py @@ -0,0 +1,34 @@ +""" +Test to verify that Amulet error detection is working. +This test checks if the error detection mechanism can detect errors. +""" +import pytest +from selenium.webdriver.common.by import By +import time + + +pytestmark = [pytest.mark.nondestructive] + + +@pytest.mark.smoke +def test_error_detection_mechanism(app): + """Test that the error detection mechanism is set up correctly.""" + # Wait for app to fully initialize + time.sleep(3) + + # Check if window.amuletErrors is defined + errors_defined = app.execute_script(""" + return typeof window.amuletErrors !== 'undefined'; + """) + + assert errors_defined, "window.amuletErrors is not defined - log_js_bridge may not be loaded" + + # Check initial state + error_count = app.execute_script("return window.amuletErrors.length;") + print(f"Initial error count: {error_count}") + + # If there are errors, print them + if error_count > 0: + errors = app.execute_script("return window.amuletErrors;") + for err in errors: + print(f" {err['level']}: {err['message']}") diff --git a/tests/integration/test_smoke.py b/tests/integration/test_smoke.py new file mode 100644 index 0000000..cc44165 --- /dev/null +++ b/tests/integration/test_smoke.py @@ -0,0 +1,71 @@ +""" +Smoke tests for basic application functionality. +""" +import pytest +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC + +pytestmark = [pytest.mark.nondestructive] + + +@pytest.mark.smoke +def test_page_loads(app): + """Test that the page loads successfully.""" + assert "Amulet" in app.title or app.title != "" + + +@pytest.mark.smoke +def test_canvas_exists(app): + """Test that the canvas element exists.""" + canvas = app.find_element(By.ID, "canvas") + assert canvas is not None + assert canvas.is_displayed() + + +@pytest.mark.smoke +def test_canvas_dimensions(app): + """Test that canvas has proper dimensions.""" + canvas = app.find_element(By.ID, "canvas") + + # Canvas should have width and height attributes + width = canvas.get_attribute("width") + height = canvas.get_attribute("height") + + assert width is not None + assert height is not None + assert int(width) > 0 + assert int(height) > 0 + + +@pytest.mark.smoke +def test_no_javascript_errors(app): + """Test that there are no JavaScript errors on page load.""" + # Get browser console logs + logs = app.get_log("browser") + + # Filter for severe errors (not warnings) + errors = [log for log in logs if log["level"] == "SEVERE"] + + # Filter out favicon 404 errors (expected) + errors = [log for log in errors if "favicon.ico" not in log.get("message", "")] + + # Assert no severe errors + assert len(errors) == 0, f"JavaScript errors found: {errors}" + + +@pytest.mark.smoke +def test_status_overlay_present(app): + """Test that the status overlay exists (for loading indication).""" + status_overlay = app.find_element(By.ID, "status-overlay") + assert status_overlay is not None + + +@pytest.mark.smoke +def test_container_structure(app): + """Test that the main container structure is correct.""" + container = app.find_element(By.ID, "container") + assert container is not None + + # Check for canvas inside container + canvas = container.find_element(By.ID, "canvas") + assert canvas is not None diff --git a/tests/integration/test_ui.py b/tests/integration/test_ui.py new file mode 100644 index 0000000..f86c74b --- /dev/null +++ b/tests/integration/test_ui.py @@ -0,0 +1,136 @@ +""" +UI interaction tests for the application. +""" +import pytest +from selenium.webdriver.common.by import By +from selenium.webdriver.common.action_chains import ActionChains +from selenium.webdriver.common.keys import Keys +import time +import sys +import os + +# Import helper from conftest +sys.path.insert(0, os.path.dirname(__file__)) +from conftest import assert_no_javascript_errors + +pytestmark = [pytest.mark.nondestructive] + + +@pytest.mark.ui +def test_canvas_interaction(app): + """Test basic canvas interaction (click).""" + canvas = app.find_element(By.ID, "canvas") + + # Click on the canvas + ActionChains(app).move_to_element(canvas).click().perform() + + # Wait a moment for any click handlers + time.sleep(0.5) + + # Debug: Check what's in the error arrays + amulet_errors = app.execute_script("return window.amuletErrors;") + browser_logs = app.get_log("browser") + print(f"\n=== DEBUG INFO ===") + print(f"Amulet errors count: {len(amulet_errors) if amulet_errors else 0}") + if amulet_errors and len(amulet_errors) > 0: + print(f"Amulet errors: {amulet_errors}") + print(f"Browser log count: {len(browser_logs)}") + for log in browser_logs: + if log['level'] in ['SEVERE', 'WARNING']: + print(f" [{log['level']}] {log['message'][:200]}") + print(f"==================\n") + + # Check for JavaScript errors + assert_no_javascript_errors(app) + + # Verify canvas is still functional + assert canvas.is_displayed() + + +@pytest.mark.ui +def test_canvas_mouse_movement(app): + """Test mouse movement over canvas.""" + canvas = app.find_element(By.ID, "canvas") + + # Move mouse across canvas + actions = ActionChains(app) + actions.move_to_element_with_offset(canvas, 50, 50) + actions.move_by_offset(100, 100) + actions.perform() + + time.sleep(0.5) + assert_no_javascript_errors(app) + assert canvas.is_displayed() + + +@pytest.mark.ui +def test_keyboard_input(app): + """Test keyboard input to the application.""" + canvas = app.find_element(By.ID, "canvas") + + # Click canvas to focus + canvas.click() + time.sleep(0.3) + + # Send some keyboard input + actions = ActionChains(app) + actions.send_keys("w") + actions.send_keys("a") + actions.send_keys("s") + actions.send_keys("d") + actions.perform() + + time.sleep(0.5) + assert_no_javascript_errors(app) + assert canvas.is_displayed() + + +@pytest.mark.ui +def test_right_click_prevention(app): + """Test that right-click context menu is prevented on canvas.""" + canvas = app.find_element(By.ID, "canvas") + + # Attempt right-click (context menu should be prevented) + ActionChains(app).context_click(canvas).perform() + + time.sleep(0.3) + assert_no_javascript_errors(app) + + # Canvas should still be functional + assert canvas.is_displayed() + + +@pytest.mark.ui +def test_window_resize(app): + """Test that canvas handles window resize.""" + original_size = app.get_window_size() + + # Resize window + app.set_window_size(1280, 720) + time.sleep(0.5) + + canvas = app.find_element(By.ID, "canvas") + assert canvas.is_displayed() + assert_no_javascript_errors(app) + + # Restore original size + app.set_window_size(original_size["width"], original_size["height"]) + time.sleep(0.5) + + +@pytest.mark.ui +def test_canvas_drag(app): + """Test drag operations on canvas.""" + canvas = app.find_element(By.ID, "canvas") + + # Perform drag operation + actions = ActionChains(app) + actions.move_to_element_with_offset(canvas, 100, 100) + actions.click_and_hold() + actions.move_by_offset(50, 50) + actions.release() + actions.perform() + + time.sleep(0.5) + assert_no_javascript_errors(app) + assert canvas.is_displayed() diff --git a/yataghan.ttf b/yataghan.ttf Binary files differnew file mode 100644 index 0000000..248997e --- /dev/null +++ b/yataghan.ttf |
