diff --git a/.dclignore b/.dclignore new file mode 100644 index 0000000..cc00416 --- /dev/null +++ b/.dclignore @@ -0,0 +1,12 @@ +.* +package.json +package-lock.json +yarn-lock.json +build.json +tsconfig.json +tslint.json +node_modules/ +*.ts +*.tsx +dist/ +*.blend \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3604110 --- /dev/null +++ b/.gitignore @@ -0,0 +1,80 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless + +# FuseBox cache +.fusebox/ + + +# Blender files +*.blend \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7a88dd9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,19 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib", + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "node_modules": true, + ".vscode": true, + "tsconfig.json": true, + "package.json": true, + "package-lock.json": true, + ".dclignore": true, + ".gitignore": true, + "now.json": true, + "Dockerfile": true + } +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7221ee3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +FROM node:10 + +# Create app directory +WORKDIR /usr/src/app + +RUN npm install --global decentraland@next + +# Install app dependencies +# A wildcard is used to ensure both package.json AND package-lock.json are copied +# where available (npm@5+) +COPY package*.json ./ + +RUN npm install +# If you are building your code for production +# RUN npm install --only=production + +# Bundle app source +COPY . . + +EXPOSE 8000 +CMD [ "npm", "start", "--", "--ci" ] \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4330ee9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019 Metaverse Holdings Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e98bbe2 --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# Remote mural + +A scene that uses a server and a REST API to sync a scene state amongst multiple users. You can paint pixels in a mural that other users can see. The colors of each pixel are stored in a remote server. + +- Create a REST server +- Call REST API + +![](screenshot/screenshot.png) + + + +**Install the CLI** + +Download and install the Decentraland CLI by running the following command + +```bash +npm i -g decentraland +``` + +For a more details, follow the steps in the [Installation guide](https://docs.decentraland.org/documentation/installation-guide/). + + +**Previewing the scene** + +Once you've installed the CLI, download this example and navigate to its directory from your terminal or command prompt. + +#### Run the REST server + +(In another terminal window) + +```sh +# from the project root +cd server + +# install node dependencies +npm install + +# start the server +npm start +``` + +#### Run the scene preview + +```sh +# from the project root +cd scene + +# install node dependencies +npm install + +# start the preview +dcl start +``` + +Open multiple browser tabs by copying the preview URL. Interact with the door on one tab to see that it also changes on other tabs as well. + +Any dependencies are installed and then the CLI will open the scene in a new browser tab automatically. + +**Usage** + +Click on a color on the palette on the right. Once a color is selected, you can paint the pixels on the wall with that color. Other users will see the same wall, painted with the same colors. + +Learn more about how to build your own scenes in our [documentation](https://docs.decentraland.org/) site. + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..8d3364e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,46 @@ +{ + "name": "dcl-project", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true + }, + "decentraland-ecs": { + "version": "1.0.1-20190104203621.commit-307ac03", + "resolved": "https://registry.npmjs.org/decentraland-ecs/-/decentraland-ecs-1.0.1-20190104203621.commit-307ac03.tgz", + "integrity": "sha512-21WQAN++UISlCec4LcP7zYTK38+4+Zb/ZvI5erqPxD6IrGNCExb4LNiF5R1Qe6yufoys9zMdMhe0TmOv3oeCFA==", + "dev": true, + "requires": { + "typescript": "^3.2.2", + "uglify-js": "^3.4.9" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "typescript": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz", + "integrity": "sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==", + "dev": true + }, + "uglify-js": { + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", + "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", + "dev": true, + "requires": { + "commander": "~2.17.1", + "source-map": "~0.6.1" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..2a68ae0 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "dcl-project", + "version": "1.0.0", + "description": "My new Decentraland project", + "scripts": { + "start": "dcl start", + "build": "build-ecs", + "watch": "build-ecs --watch", + "deploy:now": "now --platform-version 1 --docker", + "ecs:install-next": "npm install --save-dev decentraland-ecs@next", + "ecs:install-stable": "npm install --save-dev decentraland-ecs@latest" + }, + "devDependencies": { + "decentraland-ecs": "next" + } +} diff --git a/scene.json b/scene.json new file mode 100644 index 0000000..ec3b410 --- /dev/null +++ b/scene.json @@ -0,0 +1,30 @@ +{ + "display": { + "title": "interactive-text", + "favicon": "favicon_asset" + }, + "contact": { + "name": "king of the bongo", + "email": "" + }, + "owner": "", + "scene": { + "parcels": [ + "0,0" + ], + "base": "0,0" + }, + "communications": { + "type": "webrtc", + "signalling": "https://signalling-01.decentraland.org" + }, + "policy": { + "contentRating": "E", + "fly": true, + "voiceEnabled": true, + "blacklist": [], + "teleportPosition": "" + }, + "main": "bin/game.js", + "tags": [] +} \ No newline at end of file diff --git a/screenshot/blank-canvas.png b/screenshot/blank-canvas.png new file mode 100644 index 0000000..7b32117 Binary files /dev/null and b/screenshot/blank-canvas.png differ diff --git a/screenshot/screenshot.png b/screenshot/screenshot.png new file mode 100644 index 0000000..60e9c69 Binary files /dev/null and b/screenshot/screenshot.png differ diff --git a/screenshot/shapes.png b/screenshot/shapes.png new file mode 100644 index 0000000..60e9c69 Binary files /dev/null and b/screenshot/shapes.png differ diff --git a/screenshot/success.png b/screenshot/success.png new file mode 100644 index 0000000..1b417d8 Binary files /dev/null and b/screenshot/success.png differ diff --git a/server/.DS_Store b/server/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/server/.DS_Store differ diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..a202f82 --- /dev/null +++ b/server/package.json @@ -0,0 +1,25 @@ +{ + "name": "sample-sync-rest-server", + "version": "1.0.0", + "description": "My new Decentraland project", + "private": true, + "author": "Tony Crowe (https://github.com/tcrowe)", + "license": "UNLICENSED", + "dependencies": { + "@types/body-parser": "^1.17.0", + "@types/cors": "^2.8.4", + "@types/express": "^4.16.0", + "@types/lodash": "^4.14.117", + "body-parser": "^1.18.3", + "cors": "^2.8.4", + "express": "^4.15.4", + "lodash": "^4.17.11", + "nodemon": "^1.18.5", + "ts-node": "^7.0.1", + "typescript": "^3.1.3" + }, + "scripts": { + "watch": "nodemon -q -L -d 1 -w server.ts -w lib --ext ts --exec ts-node --project tsconfig.json --pretty server.ts || true", + "start": "ts-node --project tsconfig.json --pretty server.ts" + } +} diff --git a/server/server.ts b/server/server.ts new file mode 100644 index 0000000..79b06bd --- /dev/null +++ b/server/server.ts @@ -0,0 +1,138 @@ +import * as express from "express"; +import * as cors from "cors"; +//import { Schema, Document } from "mongoose"; +import * as bodyParser from "body-parser"; +//import { Interface } from "readline"; +const validColorPattern = /^#[0-9A-F]{3,6}$/i; + +// +// express app config +// +const expressApp = express(); +const pixelRouter = express.Router(); +const port = 7753; +const host = "127.0.0.1"; + +expressApp.use(cors()); + +// +// Pixel schema and model +// +export interface IPixel { + x: number, + y: number, + color?: string +} + +let pixels: IPixel[] = [] + +// +// get all pixels +// GET /api/pixels/ +// +pixelRouter.get("/", function(req: express.Request, res: express.Response) { + res.status(200).json(pixels); +}); + +// +// get one pixel by {x,y} +// GET /api/pixels/pixel/?x=0&y=0 +// +pixelRouter.get("/pixel", function( + req: express.Request, + res: express.Response +) { + let { x, y } = req.query; + + if (isNaN(x) === true || isNaN(y) === true) { + const msg = `x or y were not a number, x: ${x}, y: ${y}`; + return res.status(400).json({ error: msg }); + } + + x = parseInt(x); + y = parseInt(y); + + const pixel = pixels.find((pixel) => pixel.x === x && pixel.y === y + ) + if (pixel){ + res.status(200).json(pixel || {}); + } + else{ + const msg = `error getting one pixel, x: ${x}, y: ${y}`; + console.error(msg); + return res.status(500).json({ error: msg }); + } +}); + +// +// post a new pixel which does not exist +// or update a pixel that already exists +// POST /api/pixels/pixel +// +pixelRouter.post("/pixel", bodyParser.json(), function( + req: express.Request, + res: express.Response +) { + let { x, y, color } = req.body; + + console.log(x, y, color) + + if (isNaN(x) === true || isNaN(y) === true) { + const msg = `x or y were not a number, x: ${x}, y: ${y}`; + return res.status(400).json({ error: msg }); + } + + x = parseInt(x); + y = parseInt(y); + + + if (color && (typeof color !== "string" || validColorPattern.test(color) === false)) { + const msg = `the color was not valid hex, color: ${color}`; + console.error(msg); + return res.status(400).json({ error: msg }); + } + + + let pixel = pixels.find((pixel) => pixel.x === x && pixel.y === y + ) + if (pixel != null){ + if(color){ + // update pixel + pixel.color = color + res.status(200).json(pixel) + console.log(`changed color of existing pixel`) + }else{ + // remove pixel + for( var i = 0; i < pixels.length; i++){ + if ( pixels[i].x === x && pixels[i].y === y) { + pixels.splice(i, 1) + break + } + } + console.log("removed a pixel") + res.status(200).json(pixels) + } + + } + else { + // new pixel + let newPixel: IPixel = {x: x, y: y, color: color} + pixels.push(newPixel) + res.status(200).json(newPixel) + console.log("new pixel: " + x, " ", y, " color: " , color) + } + +}); + + + +// +// attach the pixels REST router +// +expressApp.use("/api/pixels", pixelRouter); + +// +// start up the express app +// +expressApp.listen(port, host); +console.log(`listening http://${host}:${port}`); diff --git a/server/tsconfig.json b/server/tsconfig.json new file mode 100644 index 0000000..06c77f4 --- /dev/null +++ b/server/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "module": "CommonJS", + "target": "ES6", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "sourceMap": true, + "moduleResolution": "node", + "forceConsistentCasingInFileNames": true, + "noUnusedLocals": true, + "allowSyntheticDefaultImports": false, + "newLine": "lf", + "stripInternal": true, + "strict": true, + "baseUrl": ".", + "removeComments": true, + "outDir": ".", + "pretty": true, + "lib": ["es2017"] + }, + "exclude": ["node_modules"] +} diff --git a/src/game.ts b/src/game.ts new file mode 100644 index 0000000..5b9bad9 --- /dev/null +++ b/src/game.ts @@ -0,0 +1,415 @@ + +// how often to refresh scene, in seconds +const refreshInterval: number = 1 +let refreshTimer: number = refreshInterval + + +@Component('pixel') +export class Pixel { + x: number + y: number + //color: string + constructor(x : number = 0, y : number = 0){ + this.x = x + this.y = y + //this.color = color + } +} + +const pixels = engine.getComponentGroup(Pixel) + + +@Component('swatch') +export class Swatch { + x: number + y: number + //color: Color3 + active: boolean + size: number + constructor(x : number = 0, y : number = 0){ + this.x = x + this.y = y + //this.color = color + this.active = false + this.size = 0 + } +} + +const swatches = engine.getComponentGroup(Swatch) + + +///////// SYSTEMS + + +export class GrowSwatches implements ISystem { + + update(dt: number) { + for (let swatch of swatches.entities) { + let state = swatch.get(Swatch) + let transform = swatch.get(Transform) + if (state.active && state.size < 1){ + state.size += dt * 2 + transform.scale = Vector3.Lerp(swatchScale, swatchSelectedScale, state.size) + transform.position.z = Scalar.Lerp(swatchZUnselected, swatchZSelected, state.size) + } + else if (!state.active && state.size > 0){ + state.size -= dt * 2 + transform.scale = Vector3.Lerp(swatchScale, swatchSelectedScale, state.size) + transform.position.z = Scalar.Lerp(swatchZUnselected, swatchZSelected, state.size) + } + + } + } +} + +// Add system to engine +engine.addSystem(new GrowSwatches()) + +export class CheckServer implements ISystem { + update(dt:number){ + refreshTimer -= dt + if (refreshTimer <0){ + refreshTimer = refreshInterval + getFromServer() + } + } + +} +engine.addSystem(new CheckServer()) + + + +//////// PARAMETERS + +const wallBlocksX: number = 21; +const wallBlocksY: number = 6; +const wallWidth = 7; +const wallHeight = 2; +const wallOffsetX = 0.75; +const wallOffsetY = 1; +const wallPixelPrefix = "wall-pixel-"; +const wallPixelZ = 5; + +const wallPixelScale: Vector3 = new Vector3(wallWidth / wallBlocksX - 0.01, wallHeight / wallBlocksY - 0.01, 0.01) + +const swatchPrefix = "swatch-"; + +// z = 0.1 or else clicks would not fire +const swatchScale = new Vector3(0.16, 0.16, 0.1) +const swatchSelectedScale = new Vector3(0.18, 0.18, 0.1) + +const swatchZSelected = -0.06 +const swatchZUnselected = -0.03 + + +/* + +Color list + +Source: +https://www.patternfly.org/styles/color-palette/ + ++ some are commented to get the entity count down ++ more wall pixels could be added if the palette is truncated + +*/ +const blankColor = "#0099CC" + +const paletteColor = "#666666" + +const swatchColors = [ + blankColor, + //"#FDBEBF", + //"#F7BD7F", + //"#F39D3C", + //"#EC7A08", + //"#B35C00", + //"#773D00", + // "#3b1f00", + //"#FBEABC", + "#F9D67A", + //"#F5C12E", + //"#F0AB00", + //"#B58100", + "#795600", + // "#3d2c00", + //"#E4F5BC", + "#C8EB79", + //"#ACE12E", + "#92D400", + //"#6CA100", + "#486B00", + // // "#253600", + //"#CFE7CD", + //"#9ECF99", + "#6EC664", + //"#3F9C35", + "#2D7623", + "#1E4F18", + // // "#0f280d", + //"#BEDEE1", + //"#7DBDC3", + //"#3A9CA6", + "#007A87", + //"#005C66", + "#003D44", + // // "#001f22", + //"#BEEDF9", + //"#7CDBF3", + //"#35CAED", + "#00B9E4", + //"#008BAD", + //"#005C73", + // // "#002d39", + //"#DEF3FF", + //"#BEE1f4", + //"#7DC3E8", + //"#39A5DC", + "#0088CE", + //"#00659C", + // // "#004368", + // // "#002235", + //"#C7BFFF", + //"#A18fff", + //"#8461f7", + "#703FEC", + //"#582FC0", + "#40199A", + // // "#1f0066", + // "#fafafa", + // // "#f5f5f5", + // "#ededed", + // // "#d1d1d1", + //"#BBBBBB", + // // "#8b8d8f", + //"#72767B", + // // "#4d5258", + //"#393F44", + // // "#292e34", + "#030303", + // "#CC0000", + "#A30000", + //"#8B0000", + //"#470000", + // "#2c0000", + paletteColor +] + + +let activePixels = [] + + +////// SCENERY + +/* + +There are two materials used for the wall: ++ wallPixelColorMaterial - opaque material which is the background for colors ++ wallPixelTransparentMaterial - transparent material used for no color + +*/ + +let wallPixelColorMaterial = {} + +for (let i = 0; i< swatchColors.length; i++){ + let material = new Material() + let color = Color3.FromHexString(swatchColors[i]) + material.ambientColor = color + material.albedoColor = color + material.reflectivityColor = color + wallPixelColorMaterial[swatchColors[i]] = material +} + + +let wallPixelTransparentMaterial = new Material() +wallPixelTransparentMaterial.alpha = 0.1 +wallPixelTransparentMaterial.ambientColor= Color3.FromHexString(blankColor) +wallPixelTransparentMaterial.albedoColor=Color3.FromHexString(blankColor) +wallPixelTransparentMaterial.reflectivityColor=Color3.FromHexString(blankColor) +wallPixelTransparentMaterial.hasAlpha=true +wallPixelTransparentMaterial.transparencyMode =2 + +let currentColor: Material = wallPixelTransparentMaterial + +/* + +An [x] icon shows on the palette. This is that texture material. + +*/ + +let transparentMaterial = new BasicMaterial() +transparentMaterial.texture = "./textures/transparent-texture.png" + + + +function InitiateWall(){ + + for (let xIndex = 0; xIndex < wallBlocksX; xIndex += 1) { + for (let yIndex = 0; yIndex < wallBlocksY; yIndex += 1) { + const xPos = (wallWidth / wallBlocksX) * xIndex + wallOffsetX; + const yPos = (wallHeight / wallBlocksY) * yIndex + wallOffsetY; + + let pix = new Entity() + pix.set(new Transform({ + position: new Vector3(xPos, yPos, wallPixelZ), + scale: wallPixelScale + })) + pix.set(new Pixel(xIndex, yIndex)) + + pix.set(wallPixelTransparentMaterial) + pix.set(new PlaneShape()) + pix.set(new OnClick(e=> { + clickPixel(pix) + })) + + engine.addEntity(pix) + } + } +} + + +function InitiatePalette(){ + let paletteContainer = new Entity() + paletteContainer.set(new Transform({ + position: new Vector3(8.5, 1, 3), + rotation: Quaternion.Euler(30, 50, 0) + })) + engine.addEntity(paletteContainer) + + let palette = new Entity() + palette.setParent(paletteContainer) + palette.set(new Transform({ + scale: new Vector3(2.2, 1, 1) + })) + palette.set(new PlaneShape()) + palette.set(wallPixelColorMaterial[paletteColor]) + engine.addEntity(palette) + let rowY = 0 + for (let i = 0; i< swatchColors.length; i++){ + const x = ((i % 12) + 1) / 6 - 1.08; + if (i % 12 === 0) { + rowY -= 0.17; + } + const y = rowY + 0.5; + + let colorOption = new Entity() + colorOption.setParent(paletteContainer) + colorOption.set(new Transform({ + position: new Vector3(x, y, swatchZUnselected), + scale: swatchScale + })) + colorOption.set(new Swatch(x, y)) + //log(wallPixelColorMaterial[i].albedoColor) + if(i == 0){ + colorOption.set(transparentMaterial) + }else{ + let col = swatchColors[i] + colorOption.set(wallPixelColorMaterial[col]) + } + + colorOption.set(new PlaneShape()) + colorOption.set(new OnClick(e=> { + clickSwatch(colorOption) + })) + + engine.addEntity(colorOption) + + } +} + + +InitiateWall() +InitiatePalette() + +function clickPixel(pix: Entity){ + //pix.set(currentColor) + log("setting color to pixel") + + let x = pix.get(Pixel).x + let y = pix.get(Pixel).y + let color + if (currentColor.albedoColor){ + color = currentColor.albedoColor.toHexString() + } else{ + // transparent + color = null + } + + + let url = `${apiUrl}/api/pixels/pixel` + let method = "POST"; + let headers = { "Content-Type": "application/json" } + let body = JSON.stringify({"x": x, "y": y, "color": color}) + + executeTask(async () => { + try { + let response = await fetch(url, { + headers: headers, + method: method, + body: body}) + } catch { + log("error sending pixel change") + } + + }) + getFromServer() +} + +function clickSwatch(colorOption: Entity){ + // inactivate all options + for (let swatch of swatches.entities) { + swatch.get(Swatch).active = false + } + // activate clicked + colorOption.get(Swatch).active = true + // set painting color + currentColor = colorOption.get(Material) + log("clicked color in the palette") +} + +///// Connect to the REST API + +const apiUrl = "http://127.0.0.1:7753" + +const headers = { + Accept: "application/json", + "Content-Type": "application/json" +}; + + +//getFromServer() + +function getFromServer() { + + let url = `${apiUrl}/api/pixels` + + executeTask(async () => { + try { + let response = await fetch(url) + let json = await response.json() + //log(json) + for (let pixel of pixels.entities){ + let x = pixel.get(Pixel).x + let y = pixel.get(Pixel).y + let pix = json.find((p)=> p.x === x && p.y === y ) + + if(pix && pix.color){ + if (wallPixelColorMaterial[pix.color]){ + let material = wallPixelColorMaterial[pix.color] + pixel.set(material) + } + else{ + log("pixel color" + pix.color + " not supported on " + x + " & " + y) + } + } + else { + pixel.set(wallPixelTransparentMaterial) + } + } + log("got data from server") + } catch { + log("error getting all pixels") + } + + }) +} \ No newline at end of file diff --git a/textures/transparent-texture.png b/textures/transparent-texture.png new file mode 100644 index 0000000..588a53d Binary files /dev/null and b/textures/transparent-texture.png differ diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..af3f9d5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "outFile": "./bin/game.js" + }, + "include": ["src/**/*.ts"], + "extends": "./node_modules/decentraland-ecs/types/tsconfig.json" +}