The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .babelrc
├── .eslintrc.js
├── .gitignore
├── LICENSE
├── README.md
├── assets
    └── cities
    │   ├── NEWCITY.SC2
    │   └── TESTCITY.SC2
├── index.html
├── lib
    └── fomantic-ui-2.7.1
    │   ├── LICENSE.md
    │   ├── semantic.css
    │   ├── semantic.js
    │   └── themes
    │       └── default
    │           └── assets
    │               ├── fonts
    │                   ├── brand-icons.eot
    │                   ├── brand-icons.svg
    │                   ├── brand-icons.ttf
    │                   ├── brand-icons.woff
    │                   ├── brand-icons.woff2
    │                   ├── icons.eot
    │                   ├── icons.svg
    │                   ├── icons.ttf
    │                   ├── icons.woff
    │                   ├── icons.woff2
    │                   ├── outline-icons.eot
    │                   ├── outline-icons.svg
    │                   ├── outline-icons.ttf
    │                   ├── outline-icons.woff
    │                   └── outline-icons.woff2
    │               └── images
    │                   └── flags.png
├── package.json
├── screenshots
    ├── 1.png
    ├── 2.png
    ├── 3.png
    └── 4.png
├── src
    ├── cell
    │   ├── cell.js
    │   ├── corners.js
    │   ├── location.js
    │   ├── position.js
    │   ├── related.js
    │   ├── simulation.js
    │   ├── surrounding.js
    │   ├── tiles.js
    │   └── tiles
    │   │   ├── building.js
    │   │   ├── edge.js
    │   │   ├── heightmap.js
    │   │   ├── highway.js
    │   │   ├── index.js
    │   │   ├── pipe.js
    │   │   ├── power.js
    │   │   ├── rail.js
    │   │   ├── road.js
    │   │   ├── subway.js
    │   │   ├── terrain.js
    │   │   ├── tile.js
    │   │   ├── underground.js
    │   │   ├── water.js
    │   │   └── zone.js
    ├── city
    │   ├── city.js
    │   ├── layers
    │   │   ├── building.js
    │   │   ├── edge.js
    │   │   ├── heightmap.js
    │   │   ├── highway.js
    │   │   ├── index.js
    │   │   ├── layer.js
    │   │   ├── pipe.js
    │   │   ├── power.js
    │   │   ├── rail.js
    │   │   ├── road.js
    │   │   ├── subway.js
    │   │   ├── terrain.js
    │   │   ├── underground.js
    │   │   ├── water.js
    │   │   └── zone.js
    │   ├── load.js
    │   ├── map.js
    │   └── save.js
    ├── constants.js
    ├── debug
    │   └── debug.js
    ├── import
    │   ├── artwork.js
    │   ├── palette.js
    │   ├── sc2.js
    │   ├── segmentHandlers
    │   │   ├── ALTM.js
    │   │   ├── CNAM.js
    │   │   ├── MISC.js
    │   │   ├── PICT.js
    │   │   ├── SCEN.js
    │   │   ├── TEXT.js
    │   │   ├── TMPL.js
    │   │   ├── XBIT.js
    │   │   ├── XBLD.js
    │   │   ├── XCRM.js
    │   │   ├── XFIR.js
    │   │   ├── XGRP.js
    │   │   ├── XLAB.js
    │   │   ├── XMIC.js
    │   │   ├── XPLC.js
    │   │   ├── XPLT.js
    │   │   ├── XPOP.js
    │   │   ├── XROG.js
    │   │   ├── XTER.js
    │   │   ├── XTHG.js
    │   │   ├── XTRF.js
    │   │   ├── XTXT.js
    │   │   ├── XUND.js
    │   │   ├── XVAL.js
    │   │   ├── XZON.js
    │   │   ├── common.js
    │   │   └── index.js
    │   └── tiles.js
    ├── index.html
    ├── index.js
    ├── simulation
    │   ├── actors
    │   │   ├── actors.js
    │   │   └── trains.js
    │   ├── global
    │   │   ├── global.js
    │   │   └── rail.js
    │   ├── index.js
    │   └── micro
    │   │   ├── highwayTraffic.js
    │   │   ├── simulation.js
    │   │   └── traffic.js
    ├── styles
    │   └── global.css
    ├── ui
    │   ├── gui.js
    │   └── ui.js
    ├── utils.js
    ├── utils
    │   └── calculatePathBetweenCells.js
    ├── world.js
    └── world
    │   ├── events.js
    │   ├── tools
    │       ├── center.js
    │       ├── index.js
    │       ├── query.js
    │       └── roads.js
    │   └── viewport.js
├── webpack.config.js
└── yarn.lock


/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 |   "plugins": ["@babel/plugin-proposal-class-properties", "@babel/plugin-proposal-optional-chaining"],
3 |   "presets": [
4 |     "@babel/preset-env"
5 |   ]
6 | }
7 | 


--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
 1 | module.exports = {
 2 |   extends: [
 3 |     'eslint:recommended'
 4 |   ],
 5 |   "parserOptions": {
 6 |     "ecmaVersion": 6,
 7 |     "sourceType": 'module'
 8 |   },
 9 |   rules: {
10 |     "no-console": 'off',
11 |     "indent": ['error', 2],
12 |     "quotes": ['error', 'single'],
13 |     "semi": ['error', 'always'],
14 |     "one-var": ['error', 'never']
15 |   },
16 |   parser: "babel-eslint",
17 |   env: {
18 |     browser: true,
19 |     node: true,
20 |     es6: true,
21 |     commonjs: true,
22 |     "shared-node-browser": true
23 |   },
24 |   globals: {
25 |     __static: true
26 |   }
27 | }


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
 1 | assets/cities/*
 2 | !assets/cities/NEWCITY.SC2
 3 | !assets/cities/TESTCITY.SC2
 4 | assets/images/*
 5 | assets/import/*
 6 | assets/tiles/*
 7 | node_modules/
 8 | dist/*
 9 | .DS_Store
10 | .gitignore
11 | */.DS_Store
12 | .DS_Store?
13 | ._*
14 | .Spotlight-V100
15 | .Trashes
16 | Icon?
17 | ehthumbs.db
18 | Thumbs.db
19 | *.sublime-project
20 | *.sublime-workspace
21 | *.log


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
 1 | # OpenSC2K
 2 | OpenSC2K - An Open Source remake of SimCity 2000 written in JavaScript, using WebGL Canvas and [Phaser 3](https://github.com/photonstorm/phaser/).
 3 | 
 4 | ## Overview
 5 | Currently a lot remains to be implemented but the basic framework is there for importing and viewing cities. Lots of stuff remains completely unimplemented such as the actual simulation, rendering of many special case tiles and buildings and anything else that exists outside of importing and viewing.
 6 | 
 7 | Along with implementing the original functionality and features, I plan to add additional capabilities beyond the original such as larger city/map sizes, additional network types, adding buildings beyond the initial tileset limitations, action/history tracking along with replays and more.
 8 | 
 9 | I've only tested using Chrome / Firefox on macOS, but it should run fairly well on any modern browser/platform that supports WebGL. Performance should be acceptable but there is still a LOT of room for optimizations and improvements.
10 | 
11 | Due to copyrights, the original graphics and assets from SimCity 2000 cannot be provided here. I've developed and tested using the assets from SimCity 2000 Special Edition for Windows 95. Once I've got the basic engine stabilized I plan to add support for multiple versions of SimCity 2000 in the future.
12 | 
13 | **Update:** I've been working on refactoring considerable portions of the code for clarity and performance. Due to the changes a lot of existing functionality is now completely broken and will be fixed in upcoming commits.
14 | 
15 | ![Screenshot](/screenshots/1.png)
16 | 
17 | ## Installation
18 | You can use yarn (recommended) or npm to install and run. Once installed and started, open a browser to http://localhost:3000 to start the game.
19 | 
20 | ### OS X / Linux
21 | 1. `git clone https://github.com/rage8885/OpenSC2K` or download this repository
22 | 1. `cd OpenSC2K`
23 | 1. `yarn install` downloads and installs the dependancies
24 | 1. `yarn dev` to run
25 | 
26 | ## Usage
27 | By default, a test city included in the /assets/cities/ folder will load. Currently you must modify the `/src/city/load.js` file to load different cities.
28 | 
29 | Requires two files from the Windows 95 Special Edition version of SimCity 2000: `LARGE.DAT` and `PAL_MSTR.BMP`. These must be placed in the `/assets/import/` directory prior to starting the game. The files will be automatically parsed and used for all in game graphics.
30 | 
31 | ### Controls
32 |  - `WASD` to move the camera viewport
33 |  - `Q` or `E` to adjust camera zoom
34 | 
35 | ![Screenshot](/screenshots/2.png)
36 | ![Screenshot](/screenshots/3.png)
37 | ![Screenshot](/screenshots/4.png)
38 | 
39 | ## Acknowledgements
40 | Based on the work of Dale Floer
41 |  - SimCity 2000 specifications (*.sc2)
42 |  - MIF / LARGE.DAT graphics extraction
43 | <https://github.com/dfloer/SC2k-docs>
44 | 
45 | Based on the work of David Moews
46 |  - SimCity 2000 for MS-DOS file format; unofficial partial information
47 | <http://djm.cc/dmoews.html>
48 | 
49 | Portions of the SC2 import logic are based on sc2kparser created by Objelisks and distributed under the terms of the ISC license.
50 | <https://github.com/Objelisks/sc2kparser>
51 | 
52 | Includes work adapted from the Graham Scan polygon union JavaScript implementation by Lovasoa and distributed under the terms of the MIT license
53 | <https://github.com/lovasoa/graham-fast>
54 | 
55 | ## License
56 | OpenSC2K - An Open Source SimCity 2000 remake
57 | 
58 | Copyright (C) 2019 Nicholas Ochoa
59 | 
60 | This program is free software: you can redistribute it and/or modify
61 | it under the terms of the GNU General Public License as published by
62 | the Free Software Foundation, either version 3 of the License, or
63 | (at your option) any later version.
64 | 
65 | This program is distributed in the hope that it will be useful,
66 | but WITHOUT ANY WARRANTY; without even the implied warranty of
67 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
68 | GNU General Public License for more details.
69 | 
70 | You should have received a copy of the GNU General Public License
71 | along with this program.  If not, see <http://www.gnu.org/licenses/>.
72 | 
73 | SimCity 2000 is copyright Electronic Arts / Maxis. No assets, artwork or other media from the original game is included in this remake. The OpenSC2K engine is being rebuilt as a new implementation and does not use any code from the original game reproduced in any form.


--------------------------------------------------------------------------------
/assets/cities/NEWCITY.SC2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholas-ochoa/OpenSC2K/efb769bf01fb7ac56a97dd0f259a662fec36f260/assets/cities/NEWCITY.SC2


--------------------------------------------------------------------------------
/assets/cities/TESTCITY.SC2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholas-ochoa/OpenSC2K/efb769bf01fb7ac56a97dd0f259a662fec36f260/assets/cities/TESTCITY.SC2


--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
 1 | <!DOCTYPE html>
 2 | <html lang="en">
 3 |   <head>
 4 |     <meta charset="UTF-8">
 5 |     <title>OpenSC2K</title>
 6 |     <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
 7 |     <meta http-equiv="cleartype" content="on">
 8 |     <link rel="stylesheet" type="text/css" href="/lib/fomantic-ui-2.7.1/semantic.css">
 9 |     <script src="/lib/fomantic-ui-2.7.1/semantic.js"></script>
10 |   </head>
11 |   <body>
12 |     <div id="content"></div>
13 |   </body>
14 | </html>


--------------------------------------------------------------------------------
/lib/fomantic-ui-2.7.1/LICENSE.md:
--------------------------------------------------------------------------------
1 | # The MIT License
2 | 
3 | 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:
4 | 
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 | 
7 | 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.
8 | 


--------------------------------------------------------------------------------
/lib/fomantic-ui-2.7.1/themes/default/assets/fonts/brand-icons.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholas-ochoa/OpenSC2K/efb769bf01fb7ac56a97dd0f259a662fec36f260/lib/fomantic-ui-2.7.1/themes/default/assets/fonts/brand-icons.eot


--------------------------------------------------------------------------------
/lib/fomantic-ui-2.7.1/themes/default/assets/fonts/brand-icons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholas-ochoa/OpenSC2K/efb769bf01fb7ac56a97dd0f259a662fec36f260/lib/fomantic-ui-2.7.1/themes/default/assets/fonts/brand-icons.ttf


--------------------------------------------------------------------------------
/lib/fomantic-ui-2.7.1/themes/default/assets/fonts/brand-icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholas-ochoa/OpenSC2K/efb769bf01fb7ac56a97dd0f259a662fec36f260/lib/fomantic-ui-2.7.1/themes/default/assets/fonts/brand-icons.woff


--------------------------------------------------------------------------------
/lib/fomantic-ui-2.7.1/themes/default/assets/fonts/brand-icons.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholas-ochoa/OpenSC2K/efb769bf01fb7ac56a97dd0f259a662fec36f260/lib/fomantic-ui-2.7.1/themes/default/assets/fonts/brand-icons.woff2


--------------------------------------------------------------------------------
/lib/fomantic-ui-2.7.1/themes/default/assets/fonts/icons.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholas-ochoa/OpenSC2K/efb769bf01fb7ac56a97dd0f259a662fec36f260/lib/fomantic-ui-2.7.1/themes/default/assets/fonts/icons.eot


--------------------------------------------------------------------------------
/lib/fomantic-ui-2.7.1/themes/default/assets/fonts/icons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholas-ochoa/OpenSC2K/efb769bf01fb7ac56a97dd0f259a662fec36f260/lib/fomantic-ui-2.7.1/themes/default/assets/fonts/icons.ttf


--------------------------------------------------------------------------------
/lib/fomantic-ui-2.7.1/themes/default/assets/fonts/icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholas-ochoa/OpenSC2K/efb769bf01fb7ac56a97dd0f259a662fec36f260/lib/fomantic-ui-2.7.1/themes/default/assets/fonts/icons.woff


--------------------------------------------------------------------------------
/lib/fomantic-ui-2.7.1/themes/default/assets/fonts/icons.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholas-ochoa/OpenSC2K/efb769bf01fb7ac56a97dd0f259a662fec36f260/lib/fomantic-ui-2.7.1/themes/default/assets/fonts/icons.woff2


--------------------------------------------------------------------------------
/lib/fomantic-ui-2.7.1/themes/default/assets/fonts/outline-icons.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholas-ochoa/OpenSC2K/efb769bf01fb7ac56a97dd0f259a662fec36f260/lib/fomantic-ui-2.7.1/themes/default/assets/fonts/outline-icons.eot


--------------------------------------------------------------------------------
/lib/fomantic-ui-2.7.1/themes/default/assets/fonts/outline-icons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholas-ochoa/OpenSC2K/efb769bf01fb7ac56a97dd0f259a662fec36f260/lib/fomantic-ui-2.7.1/themes/default/assets/fonts/outline-icons.ttf


--------------------------------------------------------------------------------
/lib/fomantic-ui-2.7.1/themes/default/assets/fonts/outline-icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholas-ochoa/OpenSC2K/efb769bf01fb7ac56a97dd0f259a662fec36f260/lib/fomantic-ui-2.7.1/themes/default/assets/fonts/outline-icons.woff


--------------------------------------------------------------------------------
/lib/fomantic-ui-2.7.1/themes/default/assets/fonts/outline-icons.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholas-ochoa/OpenSC2K/efb769bf01fb7ac56a97dd0f259a662fec36f260/lib/fomantic-ui-2.7.1/themes/default/assets/fonts/outline-icons.woff2


--------------------------------------------------------------------------------
/lib/fomantic-ui-2.7.1/themes/default/assets/images/flags.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholas-ochoa/OpenSC2K/efb769bf01fb7ac56a97dd0f259a662fec36f260/lib/fomantic-ui-2.7.1/themes/default/assets/images/flags.png


--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "OpenSC2K",
 3 |   "description": "An Open Source remake of SimCity 2000",
 4 |   "author": "Nicholas Ochoa",
 5 |   "version": "0.6.0",
 6 |   "homepage": "https://github.com/rage8885/OpenSC2K",
 7 |   "repository": {
 8 |     "type": "git",
 9 |     "url": "https://github.com/rage8885/OpenSC2K.git"
10 |   },
11 |   "license": "GPL-3.0",
12 |   "main": "index.js",
13 |   "scripts": {
14 |     "dev": "webpack-dev-server --progress --config './webpack.config.js'"
15 |   },
16 |   "dependencies": {
17 |     "@vingle/bmp-js": "rage8885/bmp-js",
18 |     "bmp-js": "rage8885/bmp-js",
19 |     "dat.gui": "rage8885/dat.gui",
20 |     "file-saver": "^2.0.0",
21 |     "jquery": "^3.4.0",
22 |     "jszip": "^3.1.5",
23 |     "phaser": "3.16.0-rc2"
24 |   },
25 |   "devDependencies": {
26 |     "@babel/cli": "^7.2.0",
27 |     "@babel/core": "^7.2.0",
28 |     "@babel/node": "^7.2.0",
29 |     "@babel/plugin-proposal-class-properties": "^7.2.1",
30 |     "@babel/plugin-proposal-optional-chaining": "^7.2.0",
31 |     "@babel/polyfill": "^7.0.0",
32 |     "@babel/preset-env": "^7.2.0",
33 |     "babel-eslint": "^10.0.1",
34 |     "babel-loader": "^8.0.4",
35 |     "css-loader": "^2.0.0",
36 |     "eslint": "^5.10.0",
37 |     "eslint-config-standard": "^12.0.0",
38 |     "eslint-loader": "2.1.1",
39 |     "eslint-plugin-import": "^2.7.0",
40 |     "eslint-plugin-node": "^8.0.0",
41 |     "eslint-plugin-promise": "^4.0.1",
42 |     "eslint-plugin-standard": "^4.0.0",
43 |     "expose-loader": "^0.7.5",
44 |     "file-loader": "^3.0.1",
45 |     "html-webpack-plugin": "^3.0.6",
46 |     "raw-loader": "^1.0.0",
47 |     "source-map-support": "^0.5.4",
48 |     "style-loader": "^0.23.1",
49 |     "url-loader": "^1.0.1",
50 |     "webpack": "^4.1.1",
51 |     "webpack-cli": "^3.1.2",
52 |     "webpack-dev-server": "^3.1.1"
53 |   }
54 | }
55 | 


--------------------------------------------------------------------------------
/screenshots/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholas-ochoa/OpenSC2K/efb769bf01fb7ac56a97dd0f259a662fec36f260/screenshots/1.png


--------------------------------------------------------------------------------
/screenshots/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholas-ochoa/OpenSC2K/efb769bf01fb7ac56a97dd0f259a662fec36f260/screenshots/2.png


--------------------------------------------------------------------------------
/screenshots/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholas-ochoa/OpenSC2K/efb769bf01fb7ac56a97dd0f259a662fec36f260/screenshots/3.png


--------------------------------------------------------------------------------
/screenshots/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholas-ochoa/OpenSC2K/efb769bf01fb7ac56a97dd0f259a662fec36f260/screenshots/4.png


--------------------------------------------------------------------------------
/src/cell/cell.js:
--------------------------------------------------------------------------------
  1 | import tiles from './tiles';
  2 | import * as CONST from '../constants';
  3 | import position from './position';
  4 | import surrounding from './surrounding';
  5 | import related from './related';
  6 | 
  7 | export default class cell {
  8 |   #data;
  9 |   #debug = {};
 10 | 
 11 |   surrounding;
 12 |   city;
 13 |   scene;
 14 |   position;
 15 |   water;
 16 |   tiles;
 17 |   related;
 18 |   parent;
 19 |   index;
 20 | 
 21 |   constructor (options) {
 22 |     this.#data       = options.data;
 23 |     this.scene       = options.scene;
 24 |     this.city        = options.scene.city;
 25 |     this.water       = options.data.water;
 26 |     this.index       = options.index;
 27 |     this.position    = new position({ cell: this, data: this.#data });
 28 |     this.tiles       = new tiles({ cell: this, list: options.data.tiles._list });
 29 |     this.surrounding = new surrounding({ cell: this });
 30 |     
 31 |     return this;
 32 |   }
 33 | 
 34 | 
 35 |   create () {
 36 |     this.tiles.create();
 37 |     this.related = new related({ cell: this });
 38 |   }
 39 | 
 40 | 
 41 |   onPointerUp () {
 42 | 
 43 |   }
 44 | 
 45 | 
 46 |   onPointerDown () {
 47 |     console.log(this);
 48 |   }
 49 | 
 50 | 
 51 |   onPointerMove () {
 52 |     this.scene.city.map.selectedCell.x = this.x;
 53 |     this.scene.city.map.selectedCell.y = this.y;
 54 |   }
 55 | 
 56 | 
 57 |   onPointerOver () {
 58 |     this.scene.city.map.selectedCell.x = this.x;
 59 |     this.scene.city.map.selectedCell.y = this.y;
 60 | 
 61 |     this.tiles.sprites.forEach((sprite) => {
 62 |       if (sprite.visible) sprite.setTint(0xaa0000);
 63 |     });
 64 | 
 65 |     this.related.forEach((cell) => {
 66 |       cell.tiles.sprites.forEach((sprite) => {
 67 |         if (sprite.visible) sprite.setTint(0xaa0000);
 68 |       });
 69 |     });
 70 | 
 71 |     if (this.tiles.heightmap) {
 72 |       if (this.tiles.heightmap.polygon.top){
 73 |         this.tiles.heightmap.polygon.top.fillAlpha = 0.5;
 74 |       }
 75 |       if (this.tiles.heightmap.polygon.slope){
 76 |         this.tiles.heightmap.polygon.slope.fillAlpha = 0.5;
 77 |       }
 78 |     }
 79 |   }
 80 | 
 81 | 
 82 |   onPointerOut () {
 83 |     this.tiles.sprites.forEach((sprite) => {
 84 |       if (sprite.visible) sprite.clearTint();
 85 |     });
 86 | 
 87 |     this.related.forEach((cell) => {
 88 |       cell.tiles.sprites.forEach((sprite) => {
 89 |         if (sprite.visible) sprite.clearTint();
 90 |       });
 91 |     });
 92 | 
 93 |     if (this.tiles.heightmap) {
 94 |       if (this.tiles.heightmap.polygon.top){
 95 |         this.tiles.heightmap.polygon.top.fillAlpha = 1;
 96 |       }
 97 |       if (this.tiles.heightmap.polygon.slope){
 98 |         this.tiles.heightmap.polygon.slope.fillAlpha = 1;
 99 |       }
100 |     }
101 |   }
102 | 
103 | 
104 |   highlight (color = 0x00aa00) {
105 |     this.tiles.sprites.forEach((sprite) => {
106 |       if (sprite.visible) sprite.setTint(color);
107 |     });
108 |   }
109 | 
110 | 
111 |   clearHighlight () {
112 |     this.tiles.sprites.forEach((sprite) => {
113 |       if (sprite.visible) sprite.clearTint();
114 |     });
115 |   }
116 | 
117 | 
118 |   get depth () {
119 |     return this.position.depth;
120 |   }
121 | 
122 | 
123 |   set depth (depth) {
124 |     this.position.depth = depth;
125 |   }
126 | 
127 | 
128 |   get x () {
129 |     return this.position.x;
130 |   }
131 | 
132 | 
133 |   set x (x) {
134 |     this.position.x = x;
135 |   }
136 | 
137 | 
138 |   get y () {
139 |     return this.position.y;
140 |   }
141 | 
142 | 
143 |   set y (y) {
144 |     this.position.y = y;
145 |   }
146 | 
147 | 
148 |   get z () {
149 |     return this.position.z;
150 |   }
151 | 
152 | 
153 |   set z (z) {
154 |     this.position.z = z;
155 |   }
156 | 
157 | 
158 |   hide () {
159 |     this.tiles.hide();
160 |   }
161 | 
162 | 
163 |   show () {
164 |     this.tiles.show();
165 |   }
166 | 
167 | 
168 |   //
169 |   // draw a 1px box bounding box around the calculated cell position
170 |   //
171 |   debugBox () {
172 |     let bounds = {
173 |       x: this.position.topLeft.x,
174 |       y: this.position.topLeft.y,
175 |       w: this.position.right.x - this.position.left.x,
176 |       h: this.position.top.y - this.position.bottom.y,
177 |     };
178 | 
179 |     this.#debug.box = this.scene.add.rectangle(bounds.x, bounds.y, bounds.w, bounds.h, 0x00ff00, 0.10);
180 |     this.#debug.box.setOrigin(CONST.ORIGIN_X, CONST.ORIGIN_Y);
181 |     this.#debug.box.setDepth(this.depth + 1024);
182 |     this.#debug.box.setStrokeStyle(1, 0x00ff00, 0.60);
183 | 
184 |     //this.debugPoint();
185 |   }
186 | 
187 | 
188 |   debugPoint () {
189 |     //top left
190 |     this.#debug.topLeft = this.scene.add.circle(this.position.topLeft.x, this.position.topLeft.y, 3, 0xffffff, 1);
191 |     this.#debug.topLeft.setOrigin(CONST.ORIGIN_X, CONST.ORIGIN_Y);
192 |     this.#debug.topLeft.setDepth(this.depth + 1024);
193 | 
194 |     // top middle
195 |     this.#debug.topMiddle = this.scene.add.circle(this.position.center.x, this.position.top.y, 3, 0xffff00, 1);
196 |     this.#debug.topMiddle.setOrigin(CONST.ORIGIN_X, CONST.ORIGIN_Y);
197 |     this.#debug.topMiddle.setDepth(this.depth + 1024);
198 | 
199 |     // top right
200 |     this.#debug.topRight = this.scene.add.circle(this.position.topRight.x, this.position.topRight.y, 3, 0xff00ff, 1);
201 |     this.#debug.topRight.setOrigin(CONST.ORIGIN_X, CONST.ORIGIN_Y);
202 |     this.#debug.topRight.setDepth(this.depth + 1024);
203 | 
204 | 
205 |     // center left
206 |     this.#debug.centerLeft = this.scene.add.circle(this.position.left.x, this.position.center.y, 3, 0x00ffff, 1);
207 |     this.#debug.centerLeft.setOrigin(CONST.ORIGIN_X, CONST.ORIGIN_Y);
208 |     this.#debug.centerLeft.setDepth(this.depth + 1024);
209 | 
210 |     // center middle
211 |     this.#debug.centerMiddle = this.scene.add.circle(this.position.center.x, this.position.center.y, 3, 0x0000ff, 1);
212 |     this.#debug.centerMiddle.setOrigin(CONST.ORIGIN_X, CONST.ORIGIN_Y);
213 |     this.#debug.centerMiddle.setDepth(this.depth + 1024);
214 | 
215 |     // center right
216 |     this.#debug.centerRight = this.scene.add.circle(this.position.right.x, this.position.center.y, 3, 0x00ff00, 1);
217 |     this.#debug.centerRight.setOrigin(CONST.ORIGIN_X, CONST.ORIGIN_Y);
218 |     this.#debug.centerRight.setDepth(this.depth + 1024);
219 | 
220 | 
221 |     // bottom left
222 |     this.#debug.bottomLeft = this.scene.add.circle(this.position.bottomLeft.x, this.position.bottomLeft.y, 3, 0xff0000, 1);
223 |     this.#debug.bottomLeft.setOrigin(CONST.ORIGIN_X, CONST.ORIGIN_Y);
224 |     this.#debug.bottomLeft.setDepth(this.depth + 1024);
225 | 
226 |     // bottom middle
227 |     this.#debug.bottomMiddle = this.scene.add.circle(this.position.center.x, this.position.bottom.y, 3, 0x666666, 1);
228 |     this.#debug.bottomMiddle.setOrigin(CONST.ORIGIN_X, CONST.ORIGIN_Y);
229 |     this.#debug.bottomMiddle.setDepth(this.depth + 1024);
230 | 
231 |     // bottom right
232 |     this.#debug.bottomRight = this.scene.add.circle(this.position.bottomRight.x, this.position.bottomRight.y, 3, 0x000000, 1);
233 |     this.#debug.bottomRight.setOrigin(CONST.ORIGIN_X, CONST.ORIGIN_Y);
234 |     this.#debug.bottomRight.setDepth(this.depth + 1024);
235 |   }
236 | 
237 | 
238 |   //
239 |   // draw a text label with the cell location above all other objects
240 |   //
241 |   debugLabels () {
242 |     this.#debug.label = this.scene.add.text(this.position.center.x, this.position.center.y, this.x+','+this.y+','+this.z, { fontFamily: 'Verdana', fontSize: 8, color: '#ffffff' });
243 |     this.#debug.label.setDepth(this.depth + 128);
244 |     this.#debug.label.setOrigin(0.5, 0.5);
245 |   }
246 | }


--------------------------------------------------------------------------------
/src/cell/corners.js:
--------------------------------------------------------------------------------
 1 | export default class corners {
 2 |   #cell;
 3 |   #key;
 4 |   #top    = false;
 5 |   #right  = false;
 6 |   #bottom = false;
 7 |   #left   = false;
 8 |   #none   = false;
 9 | 
10 |   constructor (options) {
11 |     this.#cell   = options.cell;
12 |     this.#key    = options.cell.scene.city.corner;
13 |     this.#top    = options.corners.top    || false;
14 |     this.#right  = options.corners.right  || false;
15 |     this.#bottom = options.corners.bottom || false;
16 |     this.#left   = options.corners.left   || false;
17 |     this.#none   = options.corners.none   || false;
18 |   }
19 | 
20 |   get key () {
21 |     return this[this.#key];
22 |   }
23 | 
24 |   get top () {
25 |     return this.#top;
26 |   }
27 | 
28 |   set top (top) {
29 |     this.#top = top;
30 |   }
31 | 
32 |   get right () {
33 |     return this.#right;
34 |   }
35 | 
36 |   set right (right) {
37 |     this.#right = right;
38 |   }
39 | 
40 |   get bottom () {
41 |     return this.#bottom;
42 |   }
43 | 
44 |   set bottom (bottom) {
45 |     this.#bottom = bottom;
46 |   }
47 | 
48 |   get left () {
49 |     return this.#left;
50 |   }
51 | 
52 |   set left (left) {
53 |     this.#left = left;
54 |   }
55 | 
56 |   get none () {
57 |     return this.#none;
58 |   }
59 | 
60 |   set none (none) {
61 |     this.#none = none;
62 |   }
63 | }


--------------------------------------------------------------------------------
/src/cell/location.js:
--------------------------------------------------------------------------------
1 | import Phaser from 'phaser';
2 | 
3 | export default class location extends Phaser.Math.Vector3 {
4 |   constructor (x, y, z) {
5 |     super(x, y, z);
6 |   }
7 | }


--------------------------------------------------------------------------------
/src/cell/position.js:
--------------------------------------------------------------------------------
  1 | import Phaser from 'phaser';
  2 | import location from './location';
  3 | import corners from './corners';
  4 | import * as CONST from '../constants';
  5 | 
  6 | export default class position {
  7 |   #depth = 0;
  8 |   #depthAdjustment = 0;
  9 |   #rotate = false;
 10 |   #location;
 11 |   #cell;
 12 |   #offset;
 13 |   #seaLevel;
 14 |   #corners;
 15 | 
 16 |   constructor (options) {
 17 |     this.#cell     = options.cell;
 18 |     this.#location = new location(options.data.x, options.data.y, options.data.z);
 19 |     this.#rotate   = options.data.rotate;
 20 |     this.#corners  = new corners({ cell: this.#cell, corners: options.data.corners });
 21 | 
 22 |     this.updateOffset();
 23 |   }
 24 | 
 25 |   get depth () {
 26 |     return (this.#location.x + this.#location.y) * 64;
 27 |   }
 28 | 
 29 |   set depth (depth) {
 30 |     this.#depthAdjustment = depth;
 31 |   }
 32 | 
 33 |   get rotate () {
 34 |     return this.#rotate;
 35 |   }
 36 | 
 37 |   set rotate (rotate) {
 38 |     this.#rotate = rotate;
 39 |   }
 40 | 
 41 |   get corners () {
 42 |     return this.#corners;
 43 |   }
 44 | 
 45 |   set corners (corners) {
 46 |     this.#corners = new corners(corners);
 47 |   }
 48 | 
 49 |   get underwater () {
 50 |     if (this.z < this.#cell.scene.city.waterLevel && [CONST.TERRAIN_SUBMERGED, CONST.TERRAIN_SHORE].includes(this.#cell.water.type))
 51 |       return true;
 52 |     else
 53 |       return false;
 54 |   }
 55 | 
 56 |   get seaLevel () {
 57 |     let offset = (this.#cell.scene.city.waterLevel - this.z) * CONST.LAYER_OFFSET;
 58 | 
 59 |     if (offset < 0)
 60 |       offset = 0;
 61 | 
 62 |     return offset;
 63 |   }
 64 | 
 65 |   get offset () {
 66 |     return this.#offset;
 67 |   }
 68 | 
 69 |   updateOffset () {
 70 |     this.#offset = new location(
 71 |       (this.x - this.y) * (CONST.TILE_WIDTH / 2),
 72 |       (this.y + this.x) * (CONST.TILE_HEIGHT / 2),
 73 |       (CONST.LAYER_OFFSET * this.z) + CONST.LAYER_OFFSET
 74 |     );
 75 |   }
 76 | 
 77 |   get location () {
 78 |     return this.#location;
 79 |   }
 80 | 
 81 |   set x (x) {
 82 |     this.#location.x = x;
 83 |     this.updateOffset();
 84 |   }
 85 | 
 86 |   set y (y) {
 87 |     this.#location.y = y;
 88 |     this.updateOffset();
 89 |   }
 90 | 
 91 |   set z (z) {
 92 |     this.#location.z = z;
 93 |     this.updateOffset();
 94 |   }
 95 | 
 96 |   get x ()  {
 97 |     return this.#location.x;
 98 |   }
 99 | 
100 |   get y ()  {
101 |     return this.#location.y;
102 |   }
103 | 
104 |   get z ()  {
105 |     return this.#location.z;
106 |   }
107 | 
108 |   get top () {
109 |     return new Phaser.Math.Vector2(
110 |       this.#offset.x + (CONST.TILE_WIDTH / 2),
111 |       this.#offset.y - this.#offset.z - CONST.TILE_HEIGHT
112 |     );
113 |   }
114 | 
115 |   get topLeft () {
116 |     return new Phaser.Math.Vector2(
117 |       this.#offset.x,
118 |       this.#offset.y - this.#offset.z - CONST.TILE_HEIGHT
119 |     );
120 |   }
121 | 
122 |   get topRight () {
123 |     return new Phaser.Math.Vector2(
124 |       this.#offset.x + CONST.TILE_WIDTH,
125 |       this.#offset.y - this.#offset.z - CONST.TILE_HEIGHT
126 |     );
127 |   }
128 | 
129 | 
130 |   get center () {
131 |     return new Phaser.Math.Vector2(
132 |       this.#offset.x + (CONST.TILE_WIDTH / 2),
133 |       (this.#offset.y - this.#offset.z) - (CONST.TILE_WIDTH / 2)
134 |     );
135 |   }
136 | 
137 |   get centerLeft () {
138 |     return new Phaser.Math.Vector2(
139 |       this.#offset.x,
140 |       (this.#offset.y - this.#offset.z) - (CONST.TILE_WIDTH / 2)
141 |     );
142 |   }
143 | 
144 |   get centerRight () {
145 |     return new Phaser.Math.Vector2(
146 |       this.#offset.x + CONST.TILE_WIDTH,
147 |       (this.#offset.y - this.#offset.z) - (CONST.TILE_WIDTH / 2)
148 |     );
149 |   }
150 | 
151 | 
152 |   get bottom () {
153 |     return new Phaser.Math.Vector2(
154 |       this.#offset.x + (CONST.TILE_WIDTH / 2),
155 |       this.#offset.y - this.#offset.z
156 |     );
157 |   }
158 | 
159 |   get bottomLeft () {
160 |     return new Phaser.Math.Vector2(
161 |       this.#offset.x,
162 |       this.#offset.y - this.#offset.z
163 |     );
164 |   }
165 | 
166 |   get bottomRight () {
167 |     return new Phaser.Math.Vector2(
168 |       this.#offset.x + CONST.TILE_WIDTH,
169 |       this.#offset.y - this.#offset.z
170 |     );
171 |   }
172 | 
173 | }


--------------------------------------------------------------------------------
/src/cell/related.js:
--------------------------------------------------------------------------------
 1 | export default class related {
 2 |   #cell;
 3 |   #map;
 4 |   #tile;
 5 |   #key = false;
 6 |   #related = [];
 7 | 
 8 |   constructor (options) {
 9 |     this.#cell = options.cell;
10 |     this.#map  = options.cell.scene.city.map;
11 |     this.#tile = options.cell.tiles?.top?.tile;
12 |     this.#key  = options.cell.position.corners.key;
13 | 
14 |     this.update();
15 |     
16 |     return this.#related;
17 |   }
18 | 
19 | 
20 |   update () {
21 |     this.#related = [];
22 | 
23 |     if (!this.#key) return;
24 | 
25 |     let x = this.#cell.x;
26 |     let y = this.#cell.y;
27 | 
28 |     // create a reference to self
29 |     this.#related.push(this.#cell);
30 | 
31 |     if (this.#tile?.size == 2) {
32 |       this.#related.push(this.#map.cells[x][y-1]);
33 |       this.#related.push(this.#map.cells[x-1][y]);
34 |       this.#related.push(this.#map.cells[x-1][y-1]);
35 |     }
36 | 
37 |     if (this.#tile?.size == 3){
38 |       this.#related.push(this.#map.cells[x][y-1]);
39 |       this.#related.push(this.#map.cells[x-1][y]);
40 |       this.#related.push(this.#map.cells[x-1][y-1]);
41 |       this.#related.push(this.#map.cells[x][y-2]);
42 |       this.#related.push(this.#map.cells[x-2][y]);
43 |       this.#related.push(this.#map.cells[x-2][y-1]);
44 |       this.#related.push(this.#map.cells[x-1][y-2]);
45 |       this.#related.push(this.#map.cells[x-2][y-2]);
46 |     }
47 | 
48 |     if (this.#tile?.size == 4){
49 |       this.#related.push(this.#map.cells[x][y-1]);
50 |       this.#related.push(this.#map.cells[x-1][y]);
51 |       this.#related.push(this.#map.cells[x-1][y-1]);
52 | 
53 |       this.#related.push(this.#map.cells[x][y-2]);
54 |       this.#related.push(this.#map.cells[x-2][y]);
55 |       this.#related.push(this.#map.cells[x-2][y-1]);
56 |       this.#related.push(this.#map.cells[x-1][y-2]);
57 |       this.#related.push(this.#map.cells[x-2][y-2]);
58 | 
59 |       this.#related.push(this.#map.cells[x][y-3]);
60 |       this.#related.push(this.#map.cells[x-3][y]);
61 |       this.#related.push(this.#map.cells[x-3][y-1]);
62 |       this.#related.push(this.#map.cells[x-3][y-2]);
63 |       this.#related.push(this.#map.cells[x-1][y-3]);
64 |       this.#related.push(this.#map.cells[x-2][y-3]);
65 |       this.#related.push(this.#map.cells[x-3][y-3]);
66 |     }
67 | 
68 |     this.#related.forEach((cell) => {
69 |       cell.related = this.#related;
70 |       cell.parent = this.#cell;
71 |     });
72 | 
73 |     this.#cell.parent = null;
74 |     this.#cell.related = this.#related;
75 |   }
76 | }


--------------------------------------------------------------------------------
/src/cell/simulation.js:
--------------------------------------------------------------------------------
 1 | import traffic from '../simulation/micro/traffic';
 2 | import highwayTraffic from '../simulation/micro/highwayTraffic';
 3 | 
 4 | export default class simulation {
 5 |   constructor (options) {
 6 |     this.cell        = options.cell;
 7 |     this.simulations = {};
 8 |   }
 9 | 
10 |   create () {
11 |     if (this.cell.road)
12 |       this.simulators.traffic = new traffic({ cell: this.cell });
13 | 
14 |     if (this.cell.highway)
15 |       this.simulators.highwayTraffic = new highwayTraffic({ cell: this.cell });
16 | 
17 |     Object.keys(this.simulators).forEach((sim) => {
18 |       this.simulations[sim].create();
19 |     });
20 |   }
21 | 
22 |   update () {
23 |     Object.keys(this.simulations).forEach((sim) => {
24 |       this.simulations[sim].update();
25 |     });
26 |   }
27 | }


--------------------------------------------------------------------------------
/src/cell/surrounding.js:
--------------------------------------------------------------------------------
 1 | export default class surrounding {
 2 |   #map;
 3 |   #x;
 4 |   #y;
 5 | 
 6 |   constructor (options) {
 7 |     this.#map  = options.cell.scene.city.map;
 8 |     this.#x    = options.cell.x;
 9 |     this.#y    = options.cell.y;
10 |   }
11 | 
12 |   get n () {
13 |     return this.#map.cells?.[this.#x]?.[this.#y - 1];
14 |   }
15 | 
16 |   get s () {
17 |     return this.#map.cells?.[this.#x]?.[this.#y + 1];
18 |   }
19 | 
20 |   get e () {
21 |     return this.#map.cells?.[this.#x + 1]?.[this.#y];
22 |   }
23 | 
24 |   get w () {
25 |     return this.#map.cells?.[this.#x - 1]?.[this.#y];
26 |   }
27 | 
28 |   get c () {
29 |     return this.#map.cells?.[this.#x]?.[this.#y];
30 |   }
31 | 
32 |   get ne () {
33 |     return this.#map.cells?.[this.#x + 1]?.[this.#y - 1];
34 |   }
35 | 
36 |   get nw () {
37 |     return this.#map.cells?.[this.#x - 1]?.[this.#y - 1];
38 |   }
39 | 
40 |   get se () {
41 |     return this.#map.cells?.[this.#x + 1]?.[this.#y + 1];
42 |   }
43 | 
44 |   get sw () {
45 |     return this.#map.cells?.[this.#x - 1]?.[this.#y + 1];
46 |   }
47 | }


--------------------------------------------------------------------------------
/src/cell/tiles.js:
--------------------------------------------------------------------------------
  1 | import * as tile from './tiles/';
  2 | import * as CONST from '../constants';
  3 | 
  4 | export default class tiles {
  5 |   #cell;
  6 |   
  7 |   constructor (options) {
  8 |     this.#cell = options.cell;
  9 |     this.list = options.list;
 10 |     this.tiles = [];
 11 |     this.sprites = [];
 12 | 
 13 |     // initialize tiles
 14 |     for (let i = 0; i < this.list.length; i++) {
 15 |       if (this.list[i].id == 0) continue;
 16 | 
 17 |       if (this.list[i].type == CONST.T_SUBWAY)      continue;
 18 |       if (this.list[i].type == CONST.T_PIPE)        continue;
 19 |       if (this.list[i].type == CONST.T_UNDERGROUND) continue;
 20 |       //if (this.list[i].type == CONST.T_EDGE)        continue;
 21 |       if (this.list[i].type == CONST.T_HEIGHTMAP)   continue;
 22 |       //if (this.list[i].type == CONST.T_TERRAIN)     continue;
 23 |       //if (this.list[i].type == CONST.T_WATER)       continue;
 24 |       //if (this.list[i].type == CONST.T_ROAD)        continue;
 25 |       //if (this.list[i].type == CONST.T_RAIL)        continue;
 26 |       //if (this.list[i].type == CONST.T_POWER)       continue;
 27 |       //if (this.list[i].type == CONST.T_HIGHWAY)     continue;
 28 |       //if (this.list[i].type == CONST.T_ZONE)        continue;
 29 |       //if (this.list[i].type == CONST.T_BUILDING)    continue;
 30 | 
 31 |       this[this.list[i].type] = null;
 32 |       this.set(this.list[i].type, this.list[i].id);
 33 |     }
 34 |   }
 35 | 
 36 | 
 37 |   getId (type) {
 38 |     return this.get(type, true);
 39 |   }
 40 | 
 41 |   
 42 |   get (type, id = false) {
 43 |     if (!this[type])
 44 |       if (!id)
 45 |         return;
 46 |       else
 47 |         return 0;
 48 | 
 49 |     if (this[type])
 50 |       if (id)
 51 |         return this[type].id;
 52 |       else
 53 |         return this[type];
 54 |   }
 55 | 
 56 | 
 57 |   addSprite (sprite, type) {
 58 |     this.sprites.push(sprite);
 59 |     this.#cell.scene.city.map.addSprite(sprite, type);
 60 |   }
 61 | 
 62 | 
 63 |   topSprite () {
 64 |     this.sprites.sort((a, b) => {
 65 |       return a._depth - b._depth;
 66 |     });
 67 | 
 68 |     return this.sprites[this.sprites.length - 1];
 69 |   }
 70 | 
 71 | 
 72 |   addTile (tile) {
 73 |     this.tiles.push(tile);
 74 |   }
 75 | 
 76 | 
 77 |   get top () {
 78 |     return this.topTile();
 79 |   }
 80 | 
 81 | 
 82 |   topTile () {
 83 |     this.tiles.sort((a, b) => {
 84 |       if (a.sprite && !a.sprite.visible) return -1;
 85 |       if (b.sprite && !b.sprite.visible) return 1;
 86 | 
 87 |       return a.depth - b.depth;
 88 |     });
 89 | 
 90 |     return this.tiles[this.tiles.length - 1];
 91 |   }
 92 | 
 93 | 
 94 |   hide () {
 95 |     for (let i = 0; i < this.list.length; i++)
 96 |       if (this[this.list[i].type])
 97 |         this[this.list[i].type].hide();
 98 |   }
 99 | 
100 | 
101 |   show () {
102 |     for (let i = 0; i < this.list.length; i++)
103 |       if (this[this.list[i].type])
104 |         this[this.list[i].type].show();
105 |   }
106 | 
107 | 
108 |   has (type) {
109 |     if (this[type] && this[type].props.draw) return true;
110 | 
111 |     return false;
112 |   }
113 | 
114 | 
115 |   set (type, id) {
116 |     if (id == 0) return;
117 |     
118 |     if (this?.[type]?.sprite) this[type].sprite.destroy();
119 | 
120 |     for (let i = 0; i < this.list.length; i++)
121 |       if (this.list[i].type == type)
122 |         this.list[i].id = id;
123 | 
124 |     this[type] = new tile[type]({ cell: this.#cell, id: id });
125 |   }
126 | 
127 | 
128 |   create () {
129 |     for (let i = 0; i < this.list.length; i++)
130 |       if (this[this.list[i].type])
131 |         this[this.list[i].type].create();
132 |   }
133 | 
134 | }


--------------------------------------------------------------------------------
/src/cell/tiles/building.js:
--------------------------------------------------------------------------------
  1 | import tile from './tile';
  2 | import * as CONST from '../../constants';
  3 | 
  4 | export default class building extends tile {
  5 |   constructor (options) {
  6 |     options.type = CONST.T_BUILDING;
  7 |     options.layerDepth = CONST.DEPTH_BUILDING;
  8 |     super(options);
  9 |   }
 10 | 
 11 | 
 12 |   check () {
 13 |     if (!super.check()) return false;
 14 | 
 15 |     if (![1,2,3,4,5,6,7,8,9,10,11,12,13,112,113,114,115,116,117,118,119,120,121,
 16 |       122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,
 17 |       140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,
 18 |       158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,
 19 |       176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,
 20 |       194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,
 21 |       212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,
 22 |       230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,
 23 |       248,249,250,251,252,253,254,255].includes(this.id))
 24 |       return false;
 25 | 
 26 |     return true;
 27 |   }
 28 | 
 29 | 
 30 |   get (id) {
 31 |     let tile = super.get(id);
 32 | 
 33 |     //if (!this.flip(tile)) this.props.flip = true;
 34 | 
 35 |     return tile;
 36 |   }
 37 | 
 38 | 
 39 |   position () {
 40 |     this.x = this.cell.position.center.x + this.props.offsetX;
 41 |     this.y = this.cell.position.top.y - this.cell.position.seaLevel;
 42 |   }
 43 | 
 44 | 
 45 |   create () {
 46 |     if (!this.cell.position.corners.key || !this.props.draw) return;
 47 | 
 48 |     if (this.tile.size == 2) this.depth.additional = 1;
 49 |     if (this.tile.size == 3) this.depth.additional = 32;
 50 |     if (this.tile.size == 4) this.depth.additional = 32;
 51 | 
 52 |     super.create();
 53 | 
 54 |     if (this.props.flip) this.sprite.setFlipX(true);
 55 | 
 56 |     this.sprite.setOrigin(0.5, 1);
 57 |   }
 58 | 
 59 | 
 60 |   logic () {
 61 |     if (!this.tile.logic) return;
 62 | 
 63 |     if (this.tile.logic.create) this[this.tile.logic.create]();
 64 |   }
 65 | 
 66 | 
 67 |   // rotate pier sections to match orientation with the crane onshore
 68 |   pier () {
 69 |     let cellX = 0;
 70 |     let cellY = 0;
 71 |     let pierDirection;
 72 |     //let pierCrane = false;
 73 |     this.props.flip = false;
 74 | 
 75 |     //if (this.id == 224) pierCrane = true;
 76 | 
 77 |     // check tiles in each direction to determine pier orientation
 78 |     if (this.id == 223) {
 79 |       // north
 80 |       for (let x = 1; x < 5; x++) {
 81 |         cellX = this.cell.x + x;
 82 |         cellY = this.cell.y;
 83 | 
 84 |         if (this.map.cells[cellX][cellY].tiles.getId(CONST.T_BUILDING) == 224) {
 85 |           pierDirection = CONST.D_NORTH;
 86 |           continue;
 87 |         }
 88 |       }
 89 | 
 90 |       // west
 91 |       for (let y = 1; y < 5; y++) {
 92 |         cellX = this.cell.x;
 93 |         cellY = this.cell.y + y;
 94 | 
 95 |         if (this.map.cells[cellX][cellY].tiles.getId(CONST.T_BUILDING) == 224) {
 96 |           pierDirection = CONST.D_WEST;
 97 |           continue;
 98 |         }
 99 |       }
100 | 
101 |       // south
102 |       for (let x = -5; x < 0; x++) {
103 |         cellX = this.cell.x + x;
104 |         cellY = this.cell.y;
105 | 
106 |         if (this.map.cells[cellX][cellY].tiles.getId(CONST.T_BUILDING) == 224) {
107 |           pierDirection = CONST.D_SOUTH;
108 |           continue;
109 |         }
110 |       }
111 | 
112 |       // east
113 |       for (let y = -5; y < 0; y++) {
114 |         cellX = this.cell.x;
115 |         cellY = this.cell.y + y;
116 | 
117 |         if (this.map.cells[cellX][cellY].tiles.getId(CONST.T_BUILDING) == 224) {
118 |           pierDirection = CONST.D_EAST;
119 |           continue;
120 |         }
121 |       }
122 |     }
123 | 
124 | 
125 |     // rotate tile
126 |     if ((pierDirection == CONST.D_EAST || pierDirection == CONST.D_WEST) && [1,3].includes(this.city.cameraRotation))
127 |       this.props.flip = true;
128 | 
129 |     if ((pierDirection == CONST.D_NORTH || pierDirection == CONST.D_SOUTH) && [0,2].includes(this.city.cameraRotation))
130 |       this.props.flip = true;
131 |   }
132 | }


--------------------------------------------------------------------------------
/src/cell/tiles/edge.js:
--------------------------------------------------------------------------------
 1 | import tile from './tile';
 2 | import * as CONST from '../../constants';
 3 | 
 4 | export default class edge extends tile {
 5 |   constructor (options) {
 6 |     options.type = CONST.T_EDGE;
 7 |     options.layerDepth = CONST.DEPTH_EDGE;
 8 |     super(options);
 9 |   }
10 | 
11 | 
12 |   check () {
13 |     if (!super.check()) return false;
14 | 
15 |     if (this.cell.x != 127 && this.cell.y != 127) return false;
16 | 
17 |     return true;
18 |   }
19 | 
20 | 
21 |   hide (type) {
22 |     if (this.sprite.length > 0) {
23 |       this.sprite.forEach((sprite) => {
24 |         if (!sprite.visible || (type && sprite.subtype != type)) return;
25 | 
26 |         sprite.setVisible(false);
27 |       });
28 |     }
29 |   }
30 | 
31 | 
32 |   show (type) {
33 |     if (this.sprite.length > 0) {
34 |       this.sprite.forEach((sprite) => {
35 |         if (sprite.visible || (type && sprite.subtype != type)) return;
36 | 
37 |         sprite.setVisible(true);
38 |       });
39 |     }
40 |   }
41 | 
42 | 
43 |   calculatePosition () {
44 |     this.x = this.cell.position.topLeft.x;
45 |     this.y = this.cell.position.topLeft.y;
46 | 
47 |     if (this.cell.z < this.cell.scene.city.waterLevel && ([CONST.TERRAIN_SUBMERGED, CONST.TERRAIN_SHORE].includes(this.cell.water.type)))
48 |       this.y -= this.cell.position.seaLevel;
49 |   }
50 | 
51 | 
52 |   create () {
53 |     this.calculatePosition();
54 | 
55 |     let sprites = [];
56 |     let waterDepth = this.cell.scene.city.waterLevel - this.cell.z;
57 | 
58 |     // water
59 |     if (this.cell.water.type != CONST.TERRAIN_DRY) {
60 |       for (let i = 0; i < waterDepth; i++) {
61 |         let offset = Math.abs((CONST.LAYER_OFFSET * i) - this.cell.position.seaLevel);
62 |         let sprite = this.cell.scene.add.sprite(this.x, this.y + offset, CONST.TILE_ATLAS).play(this.get(284).image);
63 |         sprite.type = this.type;
64 |         sprite.subtype = CONST.TERRAIN_WATER;
65 |         sprite.cell = this.cell;
66 |         sprite.setScale(CONST.SCALE);
67 |         sprite.setOrigin(CONST.ORIGIN_X, CONST.ORIGIN_Y);
68 |         sprite.setDepth(this.depth.cell + this.depth.layer + this.depth.tile + this.depth.additional + (i * 2));
69 | 
70 |         this.cell.tiles.addSprite(sprite, this.type);
71 |         sprites.push(sprite);
72 |       }
73 |     }
74 | 
75 |     // bedrock
76 |     for (let i = 0; i < this.cell.z; i++) {
77 |       let offset = CONST.LAYER_OFFSET * (i + 1);
78 | 
79 |       if (this.cell.water.type != CONST.TERRAIN_DRY)
80 |         offset = CONST.LAYER_OFFSET * (i + waterDepth + 1);
81 | 
82 |       let sprite = this.cell.scene.add.sprite(this.x, this.y + offset, CONST.TILE_ATLAS, this.get(269).textures[0]);
83 |       sprite.type = this.type;
84 |       sprite.subtype = CONST.TERRAIN_BEDROCK;
85 |       sprite.cell = this.cell;
86 |       sprite.setScale(CONST.SCALE);
87 |       sprite.setOrigin(CONST.ORIGIN_X, CONST.ORIGIN_Y);
88 |       sprite.setDepth(this.depth.cell + this.depth.layer + this.depth.tile + this.depth.additional + (this.cell.z - i * 2) - 32);
89 | 
90 |       this.cell.tiles.addSprite(sprite, this.type);
91 |       sprites.push(sprite);
92 |     }
93 | 
94 |     this.sprite = sprites;
95 |   }
96 | }


--------------------------------------------------------------------------------
/src/cell/tiles/heightmap.js:
--------------------------------------------------------------------------------
  1 | import tile from './tile';
  2 | import Phaser from 'phaser';
  3 | import * as CONST from '../../constants';
  4 | 
  5 | export default class heightmap extends tile {
  6 |   constructor (options) {
  7 |     options.type = CONST.T_HEIGHTMAP;
  8 |     options.layerDepth = CONST.DEPTH_HEIGHTMAP;
  9 |     super(options);
 10 |     
 11 |     this.polygon = [];
 12 |   }
 13 | 
 14 |   get (id) {
 15 |     let tile = super.get(id);
 16 | 
 17 |     if (tile.heightmap && tile.heightmap.reference) {
 18 |       tile = super.get(tile.heightmap.reference);
 19 |       this.id = tile.id;
 20 |     }
 21 | 
 22 |     return tile;
 23 |   }
 24 | 
 25 |   check () {
 26 |     if (!super.check())
 27 |       return false;
 28 | 
 29 |     if (![256,257,258,259,260,261,262,263,264,265,266,267,268,269].includes(this.id)) return false;
 30 | 
 31 |     return true;
 32 |   }
 33 | 
 34 |   position () {
 35 |     this.x = this.cell.position.bottom.x - (this.tile.width / 2);
 36 |     this.y = this.cell.position.bottom.y - (this.tile.height) - CONST.TILE_HEIGHT;
 37 |   }
 38 | 
 39 |   hide () {
 40 |     this.polygon.forEach((face) => {
 41 |       face.setVisible(false);
 42 |     });
 43 |   }
 44 | 
 45 |   show () {
 46 |     this.polygon.forEach((face) => {
 47 |       face.setVisible(true);
 48 |     });
 49 |   }
 50 | 
 51 |   create () {
 52 |     if (!this.props.draw || !this.cell.scene.tiles[this.id].heightmap) return;
 53 | 
 54 |     let heightmap = this.cell.scene.tiles[this.id].heightmap;
 55 |     
 56 |     this.position();
 57 | 
 58 |     // colors
 59 |     let baseColor      = Phaser.Display.Color.ObjectToColor(this.color(this.cell.z));
 60 |     let baseColorUpper = Phaser.Display.Color.ObjectToColor(this.color(this.cell.z + 1));
 61 |     let alpha          = 1;
 62 | 
 63 |     let strokeColor = baseColor.clone().darken(60).color;
 64 | 
 65 |     let lower     = baseColor.clone().color;
 66 |     let upper     = baseColorUpper.clone().color;
 67 | 
 68 |     let south     = baseColor.clone().darken(10).color;
 69 |     let east      = baseColor.clone().darken(20).color;
 70 |     let west      = baseColor.clone().darken(30).color;
 71 | 
 72 |     let southEast = baseColor.clone().lighten(10).color;
 73 |     let southWest = baseColor.clone().darken(25).color;
 74 |     let northEast = baseColor.clone().darken(40).color;
 75 |     let northWest = baseColor.clone().darken(40).color;
 76 | 
 77 |     // rock faces
 78 |     let rockTop       = baseColor.clone().color;
 79 |     let rockSouthWest = baseColor.clone().darken(50).color;
 80 |     let rockSouthEast = baseColor.clone().darken(50).color;
 81 | 
 82 |     // polygons
 83 |     if (heightmap.upper)         this.polygon.push(this.cell.scene.add.polygon(this.x, this.y, heightmap.upper,         upper,         alpha));
 84 |     if (heightmap.lower)         this.polygon.push(this.cell.scene.add.polygon(this.x, this.y, heightmap.lower,         lower,         alpha));
 85 |     if (heightmap.south)         this.polygon.push(this.cell.scene.add.polygon(this.x, this.y, heightmap.south,         south,         alpha));
 86 |     if (heightmap.east)          this.polygon.push(this.cell.scene.add.polygon(this.x, this.y, heightmap.east,          east,          alpha));
 87 |     if (heightmap.west)          this.polygon.push(this.cell.scene.add.polygon(this.x, this.y, heightmap.west,          west,          alpha));
 88 |     if (heightmap.southEast)     this.polygon.push(this.cell.scene.add.polygon(this.x, this.y, heightmap.southEast,     southEast,     alpha));
 89 |     if (heightmap.southWest)     this.polygon.push(this.cell.scene.add.polygon(this.x, this.y, heightmap.southWest,     southWest,     alpha));
 90 |     if (heightmap.northEast)     this.polygon.push(this.cell.scene.add.polygon(this.x, this.y, heightmap.northEast,     northEast,     alpha));
 91 |     if (heightmap.northWest)     this.polygon.push(this.cell.scene.add.polygon(this.x, this.y, heightmap.northWest,     northWest,     alpha));
 92 |     if (heightmap.rockTop)       this.polygon.push(this.cell.scene.add.polygon(this.x, this.y, heightmap.rockTop,       rockTop,       alpha));
 93 |     if (heightmap.rockSouthWest) this.polygon.push(this.cell.scene.add.polygon(this.x, this.y, heightmap.rockSouthWest, rockSouthWest, alpha));
 94 |     if (heightmap.rockSouthEast) this.polygon.push(this.cell.scene.add.polygon(this.x, this.y, heightmap.rockSouthEast, rockSouthEast, alpha));
 95 | 
 96 |     // polygon attributes
 97 |     this.polygon.forEach((face) => {
 98 |       face.setStrokeStyle(1, strokeColor, alpha);
 99 |       face.setScale(CONST.SCALE);
100 |       face.setOrigin(0,0);
101 |       face.setDepth(this.depth);
102 |       face.setVisible(false);
103 |     });
104 |   }
105 | 
106 | 
107 |   color (height) {
108 |     if (height > 31)
109 |       height = 31;
110 | 
111 |     if (height < 0)
112 |       height = 0;
113 | 
114 |     let terrain = [];
115 |     terrain[0]  = { r: 0,   g: 4,   b: 255, a: 1 };
116 |     terrain[1]  = { r: 0,   g: 37,  b: 255, a: 1 };
117 |     terrain[2]  = { r: 0,   g: 68,  b: 255, a: 1 };
118 |     terrain[3]  = { r: 0,   g: 99,  b: 255, a: 1 };
119 |     terrain[4]  = { r: 0,   g: 131, b: 255, a: 1 };
120 |     terrain[5]  = { r: 0,   g: 163, b: 255, a: 1 };
121 |     terrain[6]  = { r: 0,   g: 195, b: 255, a: 1 };
122 |     terrain[7]  = { r: 3,   g: 227, b: 255, a: 1 };
123 |     terrain[8]  = { r: 0,   g: 255, b: 251, a: 1 };
124 |     terrain[9]  = { r: 9,   g: 255, b: 219, a: 1 };
125 |     terrain[10] = { r: 11,  g: 255, b: 187, a: 1 };
126 |     terrain[11] = { r: 12,  g: 255, b: 155, a: 1 };
127 |     terrain[12] = { r: 14,  g: 255, b: 123, a: 1 };
128 |     terrain[13] = { r: 14,  g: 255, b: 91,  a: 1 };
129 |     terrain[14] = { r: 16,  g: 255, b: 58,  a: 1 };
130 |     terrain[15] = { r: 16,  g: 255, b: 25,  a: 1 };
131 |     terrain[16] = { r: 19,  g: 255, b: 0,   a: 1 };
132 |     terrain[17] = { r: 41,  g: 255, b: 0,   a: 1 };
133 |     terrain[18] = { r: 70,  g: 255, b: 0,   a: 1 };
134 |     terrain[19] = { r: 101, g: 255, b: 0,   a: 1 };
135 |     terrain[20] = { r: 132, g: 255, b: 0,   a: 1 };
136 |     terrain[21] = { r: 164, g: 255, b: 0,   a: 1 };
137 |     terrain[22] = { r: 195, g: 255, b: 0,   a: 1 };
138 |     terrain[23] = { r: 227, g: 255, b: 0,   a: 1 };
139 |     terrain[24] = { r: 255, g: 251, b: 0,   a: 1 };
140 |     terrain[25] = { r: 255, g: 219, b: 0,   a: 1 };
141 |     terrain[26] = { r: 255, g: 187, b: 0,   a: 1 };
142 |     terrain[27] = { r: 255, g: 155, b: 0,   a: 1 };
143 |     terrain[28] = { r: 255, g: 123, b: 0,   a: 1 };
144 |     terrain[29] = { r: 255, g: 91,  b: 0,   a: 1 };
145 |     terrain[30] = { r: 255, g: 59,  b: 0,   a: 1 };
146 |     terrain[31] = { r: 255, g: 26,  b: 0,   a: 1 };
147 |     
148 |     return terrain[height];
149 |   }
150 | }


--------------------------------------------------------------------------------
/src/cell/tiles/highway.js:
--------------------------------------------------------------------------------
  1 | import tile from './tile';
  2 | import * as CONST from '../../constants';
  3 | 
  4 | export default class highway extends tile {
  5 |   onramp = false;
  6 | 
  7 |   constructor (options) {
  8 |     options.type = CONST.T_HIGHWAY;
  9 |     options.layerDepth = CONST.DEPTH_HIGHWAY;
 10 |     super(options);
 11 |   }
 12 | 
 13 | 
 14 |   check () {
 15 |     if (!super.check()) return false;
 16 | 
 17 |     if (![73,74,75,76,77,78,79,80,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107].includes(this.id)) return false;
 18 | 
 19 |     return true;
 20 |   }
 21 | 
 22 | 
 23 |   get (id) {
 24 |     let tile = super.get(id);
 25 | 
 26 |     if (this.cell.position.rotate && tile?.flip)
 27 |       this.props.flip = true;
 28 | 
 29 |     if (this.props.flip && tile?.flipMode == CONST.ALTERNATE_TILE)
 30 |       tile = super.get(tile.rotate[this.rotation]);
 31 | 
 32 |     if (tile.highwayOnramp && tile.id < 400)
 33 |       this.onramp = true;
 34 | 
 35 |     return tile;
 36 |   }
 37 | 
 38 | 
 39 |   position () {
 40 |     this.x = this.cell.position.topLeft.x + this.props.offsetX;
 41 |     this.y = this.cell.position.topLeft.y - this.cell.position.seaLevel;
 42 | 
 43 |     if (this.tile.size == 2) {
 44 |       this.x -= (CONST.TILE_WIDTH / 2);
 45 |       this.depth.additional = -1;
 46 |     }
 47 |   }
 48 | 
 49 | 
 50 |   create () {
 51 |     if ((!this.cell.position.corners.key && ![93,94,95,96].includes(this.id)) || !this.props.draw) return;
 52 | 
 53 |     if (this.cell.position.underwater)
 54 |       this.props.offsetY -= this.cell.position.seaLevel;
 55 |     
 56 |     super.create();
 57 | 
 58 |     if (this.props.flip) this.sprite.setFlipX(true);
 59 |   }
 60 | 
 61 | 
 62 |   simulation () {
 63 |     let tile;
 64 |     let animationKey;
 65 | 
 66 |     if (!this.cell.microSims || !this.cell.microSims.simulators || !this.cell.microSims.simulators.highwayTraffic) return;
 67 | 
 68 |     let density = this.cell.microSims.simulators.highwayTraffic.getTrafficDensity();
 69 |     if (!density) return;
 70 |     
 71 |     let trafficTile = this.tile.traffic[density];
 72 |     if (!trafficTile) return;
 73 | 
 74 |     if (typeof trafficTile === 'number')
 75 |       tile = this.getTile(trafficTile);
 76 |     else
 77 |       tile = this.getTile(trafficTile[0]);
 78 | 
 79 |     if (!tile) return;
 80 | 
 81 |     // position at the bottom right x/y of the tile
 82 |     // when the size of the tiles differ
 83 |     let offsetX = this.tile.width - tile.width;
 84 |     let offsetY = this.tile.height - tile.height;
 85 |     
 86 |     // set traffic direction
 87 |     if (this.cell.microSims.simulators.highwayTraffic.calculateTrafficDirection())
 88 |       animationKey = tile.image;
 89 |     else
 90 |       animationKey = tile.image+'_R';
 91 | 
 92 |     this.highwayTraffic = this.scene.add.sprite(this.x + offsetX, this.y + offsetY, CONST.TILE_ATLAS).play(animationKey);
 93 |     this.highwayTraffic.cell = this.cell;
 94 |     this.highwayTraffic.setScale(CONST.SCALE);
 95 |     this.highwayTraffic.setOrigin(CONST.ORIGIN_X, CONST.ORIGIN_Y);
 96 |     this.highwayTraffic.setDepth(this.sprite.depth + 1);
 97 | 
 98 |     if (this.tile.traffic.flip) this.highwayTraffic.setFlipX(true);
 99 |     if (this.props.flip)        this.highwayTraffic.setFlipX(true);
100 | 
101 |     this.cell.addSprite(this.highwayTraffic, CONST.T_HIGHWAY_TRAFFIC);
102 |     this.cell.microSims.simulators.highwayTraffic.addSprite(this.highwayTraffic);
103 |   }
104 | }


--------------------------------------------------------------------------------
/src/cell/tiles/index.js:
--------------------------------------------------------------------------------
 1 | import building from './building';
 2 | import terrain from './terrain';
 3 | import edge from './edge';
 4 | import water from './water';
 5 | import zone from './zone';
 6 | import road from './road';
 7 | import rail from './rail';
 8 | import highway from './highway';
 9 | import power from './power';
10 | import pipe from './pipe';
11 | import subway from './subway';
12 | import heightmap from './heightmap';
13 | import underground from './underground';
14 | 
15 | export { building, terrain, edge, water, zone, road, rail, highway, power, pipe, subway, heightmap, underground };


--------------------------------------------------------------------------------
/src/cell/tiles/pipe.js:
--------------------------------------------------------------------------------
 1 | import tile from './tile';
 2 | import * as CONST from '../../constants';
 3 | 
 4 | export default class pipe extends tile {
 5 |   constructor (options) {
 6 |     options.type = CONST.T_PIPE;
 7 |     options.layerDepth = CONST.DEPTH_PIPE;
 8 |     super(options);
 9 |   }
10 | 
11 |   check () {
12 |     if (!super.check()) return false;
13 | 
14 |     if (![334,335,336,337,338,339,340,341,342,343,344,345,346,347,
15 |       348,349,350,351,451,452,453,454,455,456,457,458,459,460,
16 |       461,462,463,464,465,466,467].includes(this.id))
17 |       return false;
18 | 
19 |     return true;
20 |   }
21 | 
22 |   create () {
23 |     super.create();
24 | 
25 |     if (this.sprite) this.sprite.setVisible(false); // hidden by default
26 |   }
27 | }


--------------------------------------------------------------------------------
/src/cell/tiles/power.js:
--------------------------------------------------------------------------------
 1 | import tile from './tile';
 2 | import * as CONST from '../../constants';
 3 | 
 4 | export default class power extends tile {
 5 |   constructor (options) {
 6 |     options.type = CONST.T_POWER;
 7 |     options.layerDepth = CONST.DEPTH_POWER;
 8 |     super(options);
 9 |   }
10 | 
11 |   check () {
12 |     if (!super.check()) return false;
13 | 
14 |     if (![14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,67,68,92].includes(this.id)) return false;
15 | 
16 |     return true;
17 |   }
18 | 
19 |   get (id) {
20 |     let tile = super.get(id);
21 | 
22 |     if (this.cell.position.rotate && tile?.flip)
23 |       this.props.flip = true;
24 | 
25 |     if (this.props.flip && tile?.flipMode == CONST.ALTERNATE_TILE)
26 |       tile = super.get(tile.rotate[this.rotation]);
27 | 
28 |     return tile;
29 |   }
30 | 
31 |   create () {
32 |     if (!this.props.draw || !this.check()) return;
33 | 
34 |     if (this.cell.position.underwater)
35 |       this.props.offsetY -= this.cell.position.seaLevel;
36 | 
37 |     if (this.cell.tiles.has(CONST.T_TERRAIN) && this.cell.tiles.getId(CONST.T_TERRAIN) == 269)
38 |       this.props.offsetY -= CONST.LAYER_OFFSET;
39 | 
40 |     super.create();
41 | 
42 |     if (this.props.flip)
43 |       this.sprite.setFlipX(true);
44 |   }
45 | }


--------------------------------------------------------------------------------
/src/cell/tiles/rail.js:
--------------------------------------------------------------------------------
 1 | import tile from './tile';
 2 | import * as CONST from '../../constants';
 3 | 
 4 | export default class rail extends tile {
 5 |   constructor (options) {
 6 |     options.type = CONST.T_RAIL;
 7 |     options.layerDepth = CONST.DEPTH_RAIL;
 8 |     super(options);
 9 |   }
10 | 
11 |   check () {
12 |     if (!super.check()) return false;
13 | 
14 |     if (![44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,71,72,90,91,108,109,110,111].includes(this.id)) return false;
15 | 
16 |     return true;
17 |   }
18 | 
19 |   get (id) {
20 |     let tile = super.get(id);
21 | 
22 |     if (this.cell.position.rotate && tile?.flip)
23 |       this.props.flip = true;
24 | 
25 |     if (this.props.flip && tile?.flipMode == CONST.ALTERNATE_TILE)
26 |       tile = super.get(tile.rotate[this.rotation]);
27 | 
28 |     return tile;
29 |   }
30 | 
31 |   create () {
32 |     if (!this.props.draw) return;
33 | 
34 |     if (this.cell.position.underwater)
35 |       this.props.offsetY -= this.cell.position.seaLevel;
36 | 
37 |     if (this.cell.tiles.has(CONST.T_TERRAIN) && this.cell.tiles.getId(CONST.T_TERRAIN) == 269)
38 |       this.props.offsetY -= CONST.LAYER_OFFSET;
39 | 
40 |     super.create();
41 | 
42 |     if (this.props.flip)
43 |       this.sprite.setFlipX(true);
44 |   }
45 | }


--------------------------------------------------------------------------------
/src/cell/tiles/road.js:
--------------------------------------------------------------------------------
 1 | import tile from './tile';
 2 | import * as CONST from '../../constants';
 3 | 
 4 | export default class road extends tile {
 5 |   constructor (options) {
 6 |     options.type = CONST.T_ROAD;
 7 |     options.layerDepth = CONST.DEPTH_ROAD;
 8 |     super(options);
 9 |     this.traffic = null;
10 |   }
11 | 
12 |   check () {
13 |     if (!super.check()) return false;
14 | 
15 |     if (![29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,63,64,65,66,69,70,81,82,83,84,85,86,87,88,89].includes(this.id)) return false;
16 | 
17 |     return true;
18 |   }
19 | 
20 |   get (id) {
21 |     let tile = super.get(id);
22 | 
23 |     if (this.cell.position.rotate && tile?.flip)
24 |       this.props.flip = true;
25 | 
26 |     if (this.props.flip && tile?.flipMode == CONST.ALTERNATE_TILE)
27 |       tile = super.get(tile.rotate[this.rotation]);
28 | 
29 |     return tile;
30 |   }
31 | 
32 |   create () {
33 |     if (!this.props.draw || !this.check()) return;
34 | 
35 |     if (this.cell.position.underwater)
36 |       this.props.offsetY -= this.cell.position.seaLevel;
37 | 
38 |     if (this.cell.tiles.has(CONST.T_TERRAIN) && this.cell.tiles.getId(CONST.T_TERRAIN) == 269)
39 |       this.props.offsetY -= CONST.LAYER_OFFSET;
40 | 
41 |     if (this.tile.depthAdjustment)
42 |       this.depth.additional = this.tile.depthAdjustment;
43 | 
44 |     super.create();
45 | 
46 |     if (this.props.flip)
47 |       this.sprite.setFlipX(true);
48 |   }
49 | 
50 |   simulation () {
51 |     if (!this.cell.microSims || !this.cell.microSims.simulators || !this.cell.microSims.simulators.traffic)
52 |       return;
53 | 
54 |     let density = this.cell.microSims.simulators.traffic.getTrafficDensity();
55 | 
56 |     if (!density)
57 |       return;
58 | 
59 |     let tile = this.get(this.tile.traffic[density]);
60 | 
61 |     // position at the bottom right x/y of the tile
62 |     // when the size of the tiles differ
63 |     let offsetX = this.tile.width - tile.width;
64 |     let offsetY = this.tile.height - tile.height;
65 | 
66 |     this.traffic = this.scene.add.sprite(this.x + offsetX, this.y + offsetY, CONST.TILE_ATLAS).play(tile.image);
67 |     this.traffic.cell = this.cell;
68 |     this.traffic.setScale(CONST.SCALE);
69 |     this.traffic.setOrigin(CONST.ORIGIN_X, CONST.ORIGIN_Y);
70 |     this.traffic.setDepth(this.cell.depth + this.depth + 1);
71 | 
72 |     if (this.props.flip) this.traffic.setFlipX(true);
73 | 
74 |     this.cell.addSprite(this.traffic, CONST.T_ROAD_TRAFFIC);
75 |     this.cell.microSims.simulators.traffic.addSprite(this.traffic);
76 |   }
77 | }


--------------------------------------------------------------------------------
/src/cell/tiles/subway.js:
--------------------------------------------------------------------------------
 1 | import tile from './tile';
 2 | 
 3 | export default class subway extends tile {
 4 |   constructor (options) {
 5 |     options.type = CONST.T_SUBWAY;
 6 |     options.layerDepth = CONST.DEPTH_SUBWAY;
 7 |     super(options);
 8 |   }
 9 | 
10 |   check () {
11 |     if (!super.check())
12 |       return false;
13 | 
14 |     if (![319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,349,350,352,353].includes(this.id))
15 |       return false;
16 | 
17 |     return true;
18 |   }
19 | 
20 |   position () {
21 |     this.x = this.cell.position.topLeft.x - (this.tile.width / 2) << 0;
22 |     this.y = this.cell.position.topLeft.y - (this.tile.height) - this.offset << 0;
23 |   }
24 |   
25 |   create () {
26 |     super.create();
27 | 
28 |     if (this.sprite)
29 |       this.sprite.setVisible(false); // hidden by default
30 |   }
31 | }


--------------------------------------------------------------------------------
/src/cell/tiles/terrain.js:
--------------------------------------------------------------------------------
 1 | import tile from './tile';
 2 | import * as CONST from '../../constants';
 3 | 
 4 | export default class terrain extends tile {
 5 |   constructor (options) {
 6 |     options.type = CONST.T_TERRAIN;
 7 |     options.layerDepth = CONST.DEPTH_TERRAIN;
 8 |     super(options);
 9 |   }
10 | 
11 | 
12 |   check () {
13 |     if (!super.check()) return false;
14 | 
15 |     if (![256,257,258,259,260,261,262,263,264,265,266,267,268,269].includes(this.id)) return false;
16 | 
17 |     return true;
18 |   }
19 | 
20 | 
21 |   show () {
22 |     if (this.sprite?.visible) return;
23 | 
24 |     if (this.cell.water.type == CONST.TERRAIN_DRY || this.map.layers[this.type].showUnderwater)
25 |       this.sprite.setVisible(true);
26 |   }
27 | 
28 | 
29 |   create () {
30 |     //if (this.cell.tiles.hasBuilding() && !this.cell.building.tile.requiresTerrain)
31 |     //  return false;
32 | 
33 |     super.create();
34 | 
35 |     if (this.sprite && this.cell.water.type != CONST.TERRAIN_DRY)
36 |       this.sprite.setVisible(false);
37 |   }
38 | }


--------------------------------------------------------------------------------
/src/cell/tiles/tile.js:
--------------------------------------------------------------------------------
  1 | import Phaser from 'phaser';
  2 | import * as CONST from '../../constants';
  3 | 
  4 | export default class tile {
  5 |   #debug = {};
  6 | 
  7 |   id;
  8 |   cell;
  9 |   map;
 10 |   tile;
 11 |   depth;
 12 |   rotation;
 13 |   sprite;
 14 |   type;
 15 |   x;
 16 |   y;
 17 | 
 18 |   props = {
 19 |     draw: false,
 20 |     flip: false,
 21 |     offsetX: 0,
 22 |     offsetY: 0,
 23 |     hitbox: null
 24 |   };
 25 | 
 26 | 
 27 |   constructor (options) {
 28 |     this.cell      = options.cell;
 29 |     this.map       = options.cell.scene.city.map;
 30 |     this.rotation  = options.cell.scene.city.rotation;
 31 |     this.id        = options.id;
 32 |     this.type      = options.type;
 33 |     this.tile      = this.get(options.id);
 34 |     this.x         = options.x || 0;
 35 |     this.y         = options.y || 0;
 36 |     this.depth     = {
 37 |       cell:       options.cell.depth,
 38 |       layer:      options.layerDepth || 0,
 39 |       tile:       options.tileDepth || 0,
 40 |       additional: options.additionalDepth || 0,
 41 |     };
 42 | 
 43 |     if (!this.check()) return;
 44 | 
 45 |     this.props.draw = true;
 46 |   }
 47 | 
 48 | 
 49 |   get (id) {
 50 |     id = this.cell?.scene?.tiles?.[id]?.rotate[this.rotation];
 51 | 
 52 |     if (!id) return false;
 53 | 
 54 |     return this.cell.scene.tiles[id];
 55 |   }
 56 | 
 57 | 
 58 |   check () {
 59 |     if (!this.cell) return false;
 60 |     if (!this.tile) return false;
 61 | 
 62 |     return true;
 63 |   }
 64 | 
 65 | 
 66 |   hide () {
 67 |     if (this.sprite) this.sprite.setVisible(false);
 68 |   }
 69 | 
 70 | 
 71 |   show () {
 72 |     if (this.sprite) this.sprite.setVisible(true);
 73 |   }
 74 | 
 75 | 
 76 |   refresh () {
 77 |     this.hide();
 78 |     this.show();
 79 |   }
 80 | 
 81 | 
 82 |   position () {
 83 |     this.x = this.cell.position.topLeft.x + this.props.offsetX;
 84 |     this.y = this.cell.position.topLeft.y + this.props.offsetY;
 85 |   }
 86 | 
 87 | 
 88 |   create () {
 89 |     if (!this.props.draw) return;
 90 | 
 91 |     this.logic();
 92 |     this.position();
 93 | 
 94 |     if (this.tile.baseLayer) {
 95 |       let tile = this.get(this.tile.baseLayer);
 96 |       this.cell.tiles.set(this.tile.subtype, tile.id);
 97 |       this.cell.tiles[this.tile.subtype].depthAdjustment = -2;
 98 |       this.cell.tiles[this.tile.subtype].create();
 99 |     }
100 | 
101 |     if (this.tile.frames > 1)
102 |       this.sprite = this.cell.scene.add.sprite(this.x, this.y, CONST.TILE_ATLAS).play(this.tile.image);
103 |     else
104 |       this.sprite = this.cell.scene.add.sprite(this.x, this.y, CONST.TILE_ATLAS, this.tile.textures[0]);
105 | 
106 |     this.sprite.cell = this.cell;
107 |     this.sprite.type = this.type;
108 |     this.sprite.setScale(CONST.SCALE);
109 |     this.sprite.setOrigin(CONST.ORIGIN_X, CONST.ORIGIN_Y);
110 |     this.sprite.setDepth(this.depth.cell + this.depth.layer + this.depth.tile + this.depth.additional);
111 | 
112 |     this.cell.tiles.addSprite(this.sprite, this.type);
113 |     this.cell.tiles.addTile(this);
114 | 
115 |     this.events();
116 |     //this.debugBox();
117 |     //this.debugHitbox();
118 |   }
119 | 
120 |   events () {
121 |     if (!this.sprite) return;
122 | 
123 |     this.props.hitbox = this.tile.hitbox;
124 | 
125 |     this.sprite.setInteractive(this.props.hitbox, Phaser.Geom.Polygon.Contains);
126 |     this.sprite.on(CONST.E_POINTER_OVER, this.cell.onPointerOver, this.cell);
127 |     this.sprite.on(CONST.E_POINTER_OUT,  this.cell.onPointerOut,  this.cell);
128 |     this.sprite.on(CONST.E_POINTER_MOVE, this.cell.onPointerMove, this.cell);
129 |     this.sprite.on(CONST.E_POINTER_DOWN, this.cell.onPointerDown, this.cell);
130 |     this.sprite.on(CONST.E_POINTER_UP,   this.cell.onPointerUp,   this.cell);
131 |   }
132 | 
133 |   logic () {
134 |     return;
135 |   }
136 | 
137 |   simulation () {
138 |     return;
139 |   }
140 | 
141 |   debugBox () {
142 |     let bounds = this.sprite.getBounds();
143 |     let center = this.sprite.getCenter();
144 | 
145 |     this.#debug.box = this.cell.scene.add.rectangle(bounds.x, bounds.y, bounds.width, bounds.height, 0x00ffff, 0);
146 |     this.#debug.box.setOrigin(CONST.ORIGIN_X, CONST.ORIGIN_Y);
147 |     this.#debug.box.setDepth(this.depth + 2);
148 |     this.#debug.box.setStrokeStyle(1, 0x00ffff, 0.5);
149 | 
150 |     this.#debug.center = this.cell.scene.add.circle(center.x, center.y, 1, 0x00ffff, 0.75);
151 |     this.#debug.center.setOrigin(CONST.ORIGIN_X, CONST.ORIGIN_Y);
152 |     this.#debug.center.setDepth(this.sprite.depth + 2);
153 |   }
154 | 
155 |   debugHitbox () {
156 |     this.#debug.hitbox = this.cell.scene.add.polygon(this.x, this.y, this.props.hitbox.points, 0xff00ff, 0);
157 |     this.#debug.hitbox.setDepth(this.sprite.depth + 16);
158 |     this.#debug.hitbox.setScale(CONST.SCALE);
159 |     this.#debug.hitbox.setOrigin(CONST.ORIGIN_X, CONST.ORIGIN_Y);
160 |     this.#debug.hitbox.setStrokeStyle(1, 0xff00ff, 0.5);
161 |   }
162 | }


--------------------------------------------------------------------------------
/src/cell/tiles/underground.js:
--------------------------------------------------------------------------------
 1 | import tile from './tile';
 2 | import * as CONST from '../../constants';
 3 | 
 4 | export default class underground extends tile {
 5 |   constructor (options) {
 6 |     options.type = CONST.T_UNDERGROUND;
 7 |     options.layerDepth = CONST.DEPTH_UNDERGROUND;
 8 |     super(options);
 9 |   }
10 | 
11 |   check () {
12 |     if (!super.check()) return false;
13 | 
14 |     if (![305,306,307,308,309,310,311,312,313,314,315,316,317,318].includes(this.id)) return false;
15 | 
16 |     return true;
17 |   }
18 | 
19 |   position () {
20 |     this.x = this.cell.position.topLeft.x - (this.tile.width / 2) << 0;
21 |     this.y = this.cell.position.topLeft.y - (this.tile.height) - this.offset << 0;
22 |     this.depth = this.cell.depth || 0;
23 |   }
24 |   
25 |   create () {
26 |     super.create();
27 | 
28 |     if (this.sprite) this.sprite.setVisible(false); // hidden by default
29 |   }
30 | }


--------------------------------------------------------------------------------
/src/cell/tiles/water.js:
--------------------------------------------------------------------------------
 1 | import tile from './tile';
 2 | import * as CONST from '../../constants';
 3 | 
 4 | export default class water extends tile {
 5 |   constructor (options) {
 6 |     options.type = CONST.T_WATER;
 7 |     options.layerDepth = CONST.DEPTH_WATER;
 8 |     super(options);
 9 |   }
10 | 
11 |   check () {
12 |     if (!super.check()) return false;
13 | 
14 |     if (![270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290].includes(this.id)) return false;
15 | 
16 |     if (this.cell.water.type == CONST.TERRAIN_DRY) return false;
17 | 
18 |     return true;
19 |   }
20 | 
21 |   position () {
22 |     this.x = this.cell.position.topLeft.x;
23 |     this.y = this.cell.position.topLeft.y - this.cell.position.seaLevel;
24 |   }
25 | 
26 |   create () {
27 |     if (this.cell.water.type == CONST.TERRAIN_WATERFALL)
28 |       this.depthAdjustment++;
29 | 
30 |     super.create();
31 |   }
32 | }


--------------------------------------------------------------------------------
/src/cell/tiles/zone.js:
--------------------------------------------------------------------------------
 1 | import tile from './tile';
 2 | import * as CONST from '../../constants';
 3 | 
 4 | export default class zone extends tile {
 5 |   constructor (options) {
 6 |     options.type = CONST.T_ZONE;
 7 |     options.layerDepth = CONST.DEPTH_ZONE;
 8 |     super(options);
 9 |   }
10 | 
11 |   check () {
12 |     if (!super.check()) return false;
13 | 
14 |     return true;
15 |   }
16 | 
17 |   create () {
18 |     super.create();
19 |     
20 |     if (this.cell.tiles.has(CONST.T_BUILDING)) this.sprite.setVisible(false);
21 |   }
22 | }


--------------------------------------------------------------------------------
/src/city/city.js:
--------------------------------------------------------------------------------
 1 | import map from './map';
 2 | import load from './load';
 3 | //import save from './save';
 4 | //import simulator from '../simulator/simulator';
 5 | import * as CONST from '../constants';
 6 | 
 7 | export default class city {
 8 |   constructor (options) {
 9 |     this.scene = options.scene;
10 |     this.load = new load({ scene: this.scene });
11 |     //this.save = new save({ scene: this.scene });
12 |     this.corner;
13 |     this.map = new map({ scene: this.scene });
14 |   }
15 | 
16 |   create () {
17 |     this.name       = this.scene.importedData.info.name       || 'Default City';
18 |     this.rotation   = this.scene.importedData.info.rotation   || 0;
19 |     this.waterLevel = this.scene.importedData.info.waterLevel || 4;
20 | 
21 |     if (this.rotation == 0) this.corner = CONST.CORNER_BOTTOM;
22 |     if (this.rotation == 1) this.corner = CONST.CORNER_LEFT;
23 |     if (this.rotation == 2) this.corner = CONST.CORNER_TOP;
24 |     if (this.rotation == 3) this.corner = CONST.CORNER_RIGHT;
25 | 
26 |     this.map.create();
27 |     //this.simulator = new simulator({ scene: this.scene });
28 | 
29 |     this.initialized = true;
30 |   }
31 | 
32 |   update () {
33 |     if (!this.initialized) return;
34 | 
35 |     this.map.update();
36 |   }
37 | 
38 |   shutdown () {
39 |     this.initialized = false;
40 | 
41 |     if (this.map) this.map.shutdown();
42 |   }
43 | }


--------------------------------------------------------------------------------
/src/city/layers/building.js:
--------------------------------------------------------------------------------
1 | import * as CONST from '../../constants';
2 | import layer from './layer';
3 | 
4 | export default class building extends layer {
5 |   constructor (options) {
6 |     options.type = CONST.T_BUILDING;
7 |     super(options);
8 |   }
9 | }


--------------------------------------------------------------------------------
/src/city/layers/edge.js:
--------------------------------------------------------------------------------
 1 | import * as CONST from '../../constants';
 2 | import layer from './layer';
 3 | 
 4 | export default class edge extends layer {
 5 |   constructor (options) {
 6 |     options.type = CONST.T_EDGE;
 7 |     super(options);
 8 |   }
 9 | 
10 | 
11 |   hide (type) {
12 |     this.visible = false;
13 | 
14 |     this.list.forEach((tile) => {
15 |       tile.hide(type);
16 |     });
17 | 
18 |     this.events.emit(CONST.E_MAP_LAYER_HIDE, this.type);
19 |   }
20 | 
21 | 
22 |   show (type) {
23 |     this.visible = true;
24 | 
25 |     this.list.forEach((tile) => {
26 |       tile.show(type);
27 |     });
28 | 
29 |     this.events.emit(CONST.E_MAP_LAYER_SHOW, this.type);
30 |   }
31 | 
32 | 
33 |   onHide (type) {
34 |     if (type == CONST.T_WATER)     this.hide(CONST.TERRAIN_WATER);
35 |     if (type == CONST.T_TERRAIN)   this.hide(CONST.TERRAIN_BEDROCK);
36 |     if (type == CONST.T_HEIGHTMAP) this.show(false);
37 |   }
38 | 
39 | 
40 |   onShow (type) {
41 |     if (type == CONST.T_WATER)     this.show(CONST.TERRAIN_WATER);
42 |     if (type == CONST.T_TERRAIN)   this.show(CONST.TERRAIN_BEDROCK);
43 |     if (type == CONST.T_HEIGHTMAP) this.hide(false);
44 |   }
45 | }


--------------------------------------------------------------------------------
/src/city/layers/heightmap.js:
--------------------------------------------------------------------------------
 1 | import * as CONST from '../../constants';
 2 | import layer from './layer';
 3 | 
 4 | export default class heightmap extends layer {
 5 |   constructor (options) {
 6 |     options.type = CONST.T_HEIGHTMAP;
 7 |     super(options);
 8 |     this.visible = false;
 9 |   }
10 | }


--------------------------------------------------------------------------------
/src/city/layers/highway.js:
--------------------------------------------------------------------------------
1 | import * as CONST from '../../constants';
2 | import layer from './layer';
3 | 
4 | export default class highway extends layer {
5 |   constructor (options) {
6 |     options.type = CONST.T_HIGHWAY;
7 |     super(options);
8 |   }
9 | }


--------------------------------------------------------------------------------
/src/city/layers/index.js:
--------------------------------------------------------------------------------
 1 | import building from './building';
 2 | import terrain from './terrain';
 3 | import edge from './edge';
 4 | import water from './water';
 5 | import zone from './zone';
 6 | import road from './road';
 7 | import rail from './rail';
 8 | import highway from './highway';
 9 | import power from './power';
10 | import pipe from './pipe';
11 | import subway from './subway';
12 | import heightmap from './heightmap';
13 | import underground from './underground';
14 | 
15 | export { building, terrain, edge, water, zone, road, rail, highway, power, pipe, subway, heightmap, underground };


--------------------------------------------------------------------------------
/src/city/layers/layer.js:
--------------------------------------------------------------------------------
 1 | import * as CONST from '../../constants';
 2 | 
 3 | export default class layer {
 4 |   constructor (options) {
 5 |     this.scene   = options.scene;
 6 |     this.type    = options.type;
 7 |     this.map     = options.scene.city.map;
 8 |     this.events  = options.scene.events;
 9 |     this.visible = options.visible || true;
10 |     this.list    = [];
11 | 
12 |     this.map.list.forEach((cell) => {
13 |       if (!cell || !cell.tiles)
14 |         return;
15 | 
16 |       if (cell.tiles[this.type])
17 |         this.list.push(cell.tiles[this.type]);
18 |     });
19 | 
20 |     this.events.on(CONST.E_MAP_LAYER_HIDE, this.onHide, this);
21 |     this.events.on(CONST.E_MAP_LAYER_SHOW, this.onShow, this);
22 |   }
23 | 
24 |   toggle () {
25 |     if (this.visible)
26 |       this.hide();
27 |     else
28 |       this.show();
29 |   }
30 | 
31 |   hide (emitEvents = true) {
32 |     this.visible = false;
33 | 
34 |     this.list.forEach((tile) => {
35 |       tile.hide();
36 |     });
37 | 
38 |     if (emitEvents) this.events.emit(CONST.E_MAP_LAYER_HIDE, this.type);
39 |   }
40 | 
41 |   show (emitEvents = true) {
42 |     this.visible = true;
43 | 
44 |     this.list.forEach((tile) => {
45 |       tile.show();
46 |     });
47 | 
48 |     if (emitEvents) this.events.emit(CONST.E_MAP_LAYER_SHOW, this.type);
49 |   }
50 | 
51 |   refresh () {
52 |     this.hide();
53 |     this.show();
54 |   }
55 | 
56 |   onHide (type) {
57 |     return;
58 |   }
59 | 
60 |   onShow (type) {
61 |     return;
62 |   }
63 | }


--------------------------------------------------------------------------------
/src/city/layers/pipe.js:
--------------------------------------------------------------------------------
1 | import * as CONST from '../../constants';
2 | import layer from './layer';
3 | 
4 | export default class pipe extends layer {
5 |   constructor (options) {
6 |     options.type = CONST.T_PIPE;
7 |     super(options);
8 |   }
9 | }


--------------------------------------------------------------------------------
/src/city/layers/power.js:
--------------------------------------------------------------------------------
1 | import * as CONST from '../../constants';
2 | import layer from './layer';
3 | 
4 | export default class power extends layer {
5 |   constructor (options) {
6 |     options.type = CONST.T_POWER;
7 |     super(options);
8 |   }
9 | }


--------------------------------------------------------------------------------
/src/city/layers/rail.js:
--------------------------------------------------------------------------------
1 | import * as CONST from '../../constants';
2 | import layer from './layer';
3 | 
4 | export default class rail extends layer {
5 |   constructor (options) {
6 |     options.type = CONST.T_RAIL;
7 |     super(options);
8 |   }
9 | }


--------------------------------------------------------------------------------
/src/city/layers/road.js:
--------------------------------------------------------------------------------
1 | import * as CONST from '../../constants';
2 | import layer from './layer';
3 | 
4 | export default class road extends layer {
5 |   constructor (options) {
6 |     options.type = CONST.T_ROAD;
7 |     super(options);
8 |   }
9 | }


--------------------------------------------------------------------------------
/src/city/layers/subway.js:
--------------------------------------------------------------------------------
1 | import * as CONST from '../../constants';
2 | import layer from './layer';
3 | 
4 | export default class subway extends layer {
5 |   constructor (options) {
6 |     options.type = CONST.T_SUBWAY;
7 |     super(options);
8 |   }
9 | }


--------------------------------------------------------------------------------
/src/city/layers/terrain.js:
--------------------------------------------------------------------------------
 1 | import * as CONST from '../../constants';
 2 | import layer from './layer';
 3 | 
 4 | export default class terrain extends layer {
 5 |   constructor (options) {
 6 |     options.type = CONST.T_TERRAIN;
 7 |     super(options);
 8 |     this.showUnderwater = false;
 9 |   }
10 | 
11 | 
12 |   onHide (type) {
13 |     if (type == CONST.T_HEIGHTMAP) this.show(false);
14 | 
15 |     if (type == CONST.T_WATER) {
16 |       this.showUnderwater = true;
17 |       this.show();
18 |     }
19 |   }
20 | 
21 | 
22 |   onShow (type) {
23 |     if (type == CONST.T_HEIGHTMAP) this.hide(false);
24 | 
25 |     if (type == CONST.T_WATER) {
26 |       this.showUnderwater = false;
27 |       this.show();
28 |     }
29 |   }
30 | }


--------------------------------------------------------------------------------
/src/city/layers/underground.js:
--------------------------------------------------------------------------------
1 | import * as CONST from '../../constants';
2 | import layer from './layer';
3 | 
4 | export default class underground extends layer {
5 |   constructor (options) {
6 |     options.type = CONST.T_UNDERGROUND;
7 |     super(options);
8 |   }
9 | }


--------------------------------------------------------------------------------
/src/city/layers/water.js:
--------------------------------------------------------------------------------
 1 | import * as CONST from '../../constants';
 2 | import layer from './layer';
 3 | 
 4 | export default class water extends layer {
 5 |   constructor (options) {
 6 |     options.type = CONST.T_WATER;
 7 |     super(options);
 8 |   }
 9 | 
10 | 
11 |   onHide (type) {
12 |     if (type == CONST.T_HEIGHTMAP) this.show(false);
13 |   }
14 | 
15 | 
16 |   onShow (type) {
17 |     if (type == CONST.T_HEIGHTMAP) this.hide(false);
18 |   }
19 | }


--------------------------------------------------------------------------------
/src/city/layers/zone.js:
--------------------------------------------------------------------------------
1 | import * as CONST from '../../constants';
2 | import layer from './layer';
3 | 
4 | export default class zone extends layer {
5 |   constructor (options) {
6 |     options.type = CONST.T_ZONE;
7 |     super(options);
8 |   }
9 | }


--------------------------------------------------------------------------------
/src/city/load.js:
--------------------------------------------------------------------------------
 1 | //import jszip from 'jszip';
 2 | import * as CONST from '../constants';
 3 | import sc2 from '../import/sc2';
 4 | 
 5 | export default class load {
 6 |   constructor (options) {
 7 |     this.scene   = options.scene;
 8 |     this.sc2     = new sc2();
 9 |     this.file    = 'Default.sc2';
10 |   }
11 | 
12 | 
13 |   open () {
14 |     if (!document.querySelector('#fileOpen')) {
15 |       let input = document.createElement('input');
16 | 
17 |       input.id = 'fileOpen';
18 |       input.type = 'file';
19 |       input.onchange = (event) => {
20 |         this.file = event.target.files[0].name;
21 |         this.load(event.target.files[0]);
22 |       };
23 | 
24 |       document.body.appendChild(input);
25 |     }
26 | 
27 |     let event = new MouseEvent('click', {
28 |       view: window,
29 |       bubbles: true
30 |     });
31 | 
32 |     let fileOpen = document.querySelector('#fileOpen');
33 |     fileOpen.dispatchEvent(event);
34 |   }
35 | 
36 | 
37 |   async loadDefaultCity () {
38 |     return new Promise((resolve) => {
39 |       this.file = 'CAPEQUES.SC2'; //r3
40 |       //this.file = 'BAYVIEW.SC2'; //r2, bridges
41 |       //this.file = 'EGYPTFAL.SC2'; //r1
42 |       //this.file = 'NEWCITY.SC2'; //r0
43 |       //this.file = 'TOKYO.SC2'; // rails
44 | 
45 |       // primary test city
46 |       this.file = 'TESTCITY.SC2';
47 | 
48 |       // scenario test cities
49 |       //this.file = 'test/scenario/FLINT.SCN';
50 |       //this.file = 'test/scenario/ATLANTA.SCN';
51 |       //this.file = 'test/scenario/CHICAGO.SCN';
52 | 
53 |       // coordinate test city
54 |       //this.file = 'test/coords/CQST.SC2';
55 |       //this.file = 'test/tunnels/TUNNELS.SC2';
56 | 
57 |       // rotation test cities
58 |       //this.file = 'test/rotation/CQ1R.SC2';
59 |       //this.file = 'test/rotation/CQ2R.SC2';
60 |       //this.file = 'test/rotation/CQ3R.SC2';
61 |       //this.file = 'test/rotation/CQ4R.SC2';
62 | 
63 |       this.scene.load.binary(CONST.CITY, CONST.CITIES_PATH+ this.file);
64 |       this.scene.load.start();
65 | 
66 |       this.scene.load.once(CONST.E_LOAD_COMPLETE, () => {
67 |         this.parseCity().then((data) => {
68 |           this.scene.importedData = data;
69 |           resolve();
70 |         });
71 |       });
72 | 
73 | 
74 |     });
75 |   }
76 | 
77 | 
78 |   async parseCity () {
79 |     return new Promise((resolve, reject) => {
80 |       let data = Buffer.from(this.scene.cache.binary.get(CONST.CITY));
81 | 
82 |       // sc2 file, first four bytes are ascii "FORM"
83 |       if (data.readUInt32BE(0x00) == 0x464F524D)
84 |         resolve(this.sc2.import(data));
85 |       else
86 |         reject(new Error('Invalid file format'));
87 |     });
88 |   }
89 | }


--------------------------------------------------------------------------------
/src/city/map.js:
--------------------------------------------------------------------------------
  1 | import cell from '../cell/cell';
  2 | import * as CONST from '../constants';
  3 | import * as layers from './layers/';
  4 | 
  5 | export default class map {
  6 |   #scene;
  7 | 
  8 |   grid = [];
  9 |   cells = [];
 10 |   list = [];
 11 |   selectedCell = { x: 0, y: 0 };
 12 |   sprites = { all: [] };
 13 |   layers = [];
 14 | 
 15 |   constructor (options) {
 16 |     this.#scene        = options.scene;
 17 |   }
 18 | 
 19 | 
 20 |   create () {
 21 |     for (let i = 0; i < this.#scene.importedData.cells.length; i++) {
 22 |       let c = new cell({
 23 |         scene: this.#scene,
 24 |         data: this.#scene.importedData.cells[i],
 25 |         index: i
 26 |       });
 27 | 
 28 |       if (!this.cells[c.x])      this.cells[c.x]      = [];
 29 |       if (!this.cells[c.x][c.y]) this.cells[c.x][c.y] = [];
 30 | 
 31 |       this.cells[c.x][c.y] = c;
 32 |       this.list.push(this.cells[c.x][c.y]);
 33 |     }
 34 | 
 35 |     // create map cells
 36 |     for (let x = 0; x < CONST.MAP_SIZE; x++)
 37 |       for (let y = 0; y < CONST.MAP_SIZE; y++)
 38 |         this.cells[x][y].create();
 39 | 
 40 |     // create layers
 41 |     this.layers.terrain   = new layers.terrain({ scene: this.#scene });
 42 |     this.layers.water     = new layers.water({ scene: this.#scene });
 43 |     this.layers.edge      = new layers.edge({ scene: this.#scene });
 44 |     this.layers.heightmap = new layers.heightmap({ scene: this.#scene });
 45 |     this.layers.zone      = new layers.zone({ scene: this.#scene });
 46 |     this.layers.building  = new layers.building({ scene: this.#scene });
 47 |     this.layers.road      = new layers.road({ scene: this.#scene });
 48 |     this.layers.rail      = new layers.rail({ scene: this.#scene });
 49 |     this.layers.power     = new layers.power({ scene: this.#scene });
 50 |     this.layers.highway   = new layers.highway({ scene: this.#scene });
 51 |     this.layers.subway    = new layers.subway({ scene: this.#scene });
 52 |     this.layers.pipe      = new layers.pipe({ scene: this.#scene });
 53 | 
 54 |     // do an initial object cull after rendering map
 55 |     this.#scene.viewport.cullObjects();
 56 |   }
 57 | 
 58 | 
 59 |   rotateCW () {
 60 |     let cells = this.cells;
 61 |     let tempCells = [];
 62 | 
 63 |     for (let x = 0; x < CONST.MAP_SIZE; x++) {
 64 |       for (let y = 0; y < CONST.MAP_SIZE; y++) {
 65 |         if (!tempCells[x])    tempCells[x]    = [];
 66 |         if (!tempCells[x][y]) tempCells[x][y] = [];
 67 | 
 68 |         let newX = y;
 69 |         let newY = CONST.MAP_SIZE - x - 1;
 70 | 
 71 |         tempCells[newX][newY] = cells[x][y];
 72 |       }
 73 |     }
 74 | 
 75 |     this.cells = tempCells;
 76 |   }
 77 | 
 78 | 
 79 |   rotateCCW () {
 80 |     let cells = this.cells;
 81 |     let tempCells = [];
 82 | 
 83 |     for (let x = 0; x < CONST.MAP_SIZE; x++) {
 84 |       for (let y = 0; y < CONST.MAP_SIZE; y++) {
 85 |         if (!tempCells[x])    tempCells[x]    = [];
 86 |         if (!tempCells[x][y]) tempCells[x][y] = [];
 87 | 
 88 |         let newX = CONST.MAP_SIZE - y - 1;
 89 |         let newY = x;
 90 | 
 91 |         tempCells[newX][newY] = cells[x][y];
 92 |       }
 93 |     }
 94 | 
 95 |     this.cells = tempCells;
 96 |   }
 97 | 
 98 | 
 99 |   update () {
100 | 
101 |   }
102 | 
103 | 
104 |   shutdown () {
105 |     if (this.list.length > 0)
106 |       this.list.forEach((cell) => {
107 |         cell.shutdown();
108 |       });
109 | 
110 |     if (this.sprites.all.length > 0)
111 |       this.sprites.all.forEach((sprite) => {
112 |         sprite.destroy();
113 |       });
114 |   }
115 | 
116 | 
117 |   addSprite (sprite, type) {
118 |     if (!this.sprites[type])
119 |       this.sprites[type] = [];
120 | 
121 |     this.sprites[type].push(sprite);
122 |     this.sprites.all.push(sprite);
123 |   }
124 | }


--------------------------------------------------------------------------------
/src/city/save.js:
--------------------------------------------------------------------------------
 1 | import jszip from 'jszip';
 2 | import fileSaver from 'file-saver';
 3 | 
 4 | export default class save {
 5 |   constructor (options) {
 6 |     this.scene = options.scene;
 7 |     this.globals = this.scene.sys.game.globals;
 8 |     this.city = this.scene.city;
 9 |   }
10 | 
11 |   export () {
12 |     this.data = {
13 |       info: {
14 |         name: this.city.name,
15 |         rotation: this.city.rotation,
16 |         waterLevel: this.city.waterLevel,
17 |         width: this.city.width,
18 |         height: this.city.height,
19 |       },
20 |       cells: []
21 |     };
22 |     
23 |     this.createCellList();
24 |     this.downloadSave();
25 |   }
26 | 
27 |   createCellList () {
28 |     for (let x = 0; x < this.city.width; x++) {
29 |       for (let y = 0; y < this.city.height; y++) {
30 |         let mapCell = this.city.map.getCell(x, y);
31 |         let cell = {
32 |           x: mapCell.x,
33 |           y: mapCell.y,
34 |           z: mapCell.z,
35 |           cornersTopLeft: mapCell.properties.cornersTopLeft,
36 |           cornersTopRight: mapCell.properties.cornersTopRight,
37 |           cornersBottomLeft: mapCell.properties.cornersBottomLeft,
38 |           cornersBottomRight: mapCell.properties.cornersBottomRight,
39 |           conductive: mapCell.properties.conductive,
40 |           powered: mapCell.properties.powered,
41 |           piped: mapCell.properties.piped,
42 |           watered: mapCell.properties.watered,
43 |           rotate: mapCell.properties.rotate,
44 |           landValueMask: mapCell.properties.landValueMask,
45 |           saltWater: mapCell.properties.saltWater,
46 |           waterCovered: mapCell.properties.waterCovered,
47 |           missileSilo: mapCell.properties.missileSilo,
48 |           waterLevel: mapCell.properties.waterLevel,
49 |           surfaceWater: mapCell.properties.surfaceWater,
50 |           tunnelLevel: mapCell.properties.tunnelLevel,
51 |           altWaterCovered: mapCell.properties.altWaterCovered,
52 |           terrainWaterLevel: mapCell.properties.terrainWaterLevel,
53 |           tiles: {
54 |             building: mapCell.getBuildingTileId(),
55 |             zone: mapCell.getZoneTileId(),
56 |             water: mapCell.getWaterTileId(),
57 |             road: mapCell.getRoadTileId(),
58 |             rail: mapCell.getRailTileId(),
59 |             power: mapCell.getPowerTileId(),
60 |             highway: mapCell.getHighwayTileId(),
61 |             terrain: mapCell.getTerrainTileId(),
62 |             heightmap: mapCell.getHeightmapTileId(),
63 |             subway: mapCell.getSubwayTileId(),
64 |             pipe: mapCell.getPipeTileId(),
65 |           }
66 |         };
67 | 
68 |         this.data.cells.push(cell);
69 |       }
70 |     }
71 |   }
72 | 
73 |   downloadSave () {
74 |     let json = JSON.stringify(this.data);
75 |     let zip = new jszip();
76 |     let options = {
77 |       type: 'blob',
78 |       compression: 'DEFLATE',
79 |       compressionOptions: {
80 |         level: 9
81 |       }
82 |     };
83 | 
84 |     zip.file('data.json', json);
85 |     zip.generateAsync(options).then((content) => {
86 |       fileSaver.saveAs(content, this.data.info.name + '.opensc2k');
87 |     });
88 |   }
89 | }


--------------------------------------------------------------------------------
/src/constants.js:
--------------------------------------------------------------------------------
  1 | // todo: split these out into separate constant files per section of code
  2 | 
  3 | // engine config
  4 | export const SCALE          = 2;
  5 | export const ORIGIN_X       = 0;
  6 | export const ORIGIN_Y       = 1;
  7 | export const TILE_WIDTH     = 64;
  8 | export const TILE_HEIGHT    = 32;
  9 | export const LAYER_OFFSET   = 24;
 10 | export const MAP_SIZE       = 128;
 11 | export const TILE_ATLAS     = 'tiles';
 12 | export const CAMERA_NAME    = 'viewport';
 13 | 
 14 | 
 15 | // paths
 16 | export const ASSETS_PATH    = '/assets/import/';
 17 | export const CITIES_PATH    = '/assets/cities/';
 18 | 
 19 | 
 20 | // event types
 21 | export const E_POINTER_OVER   = 'pointerover';
 22 | export const E_POINTER_OUT    = 'pointerout';
 23 | export const E_POINTER_MOVE   = 'pointermove';
 24 | export const E_POINTER_DOWN   = 'pointerdown';
 25 | export const E_POINTER_UP     = 'pointerup';
 26 | export const E_RESIZE         = 'resize';
 27 | export const E_LOAD_COMPLETE  = 'postprocess';
 28 | export const E_MAP_LAYER_HIDE = 'mapLayerHide';
 29 | export const E_MAP_LAYER_SHOW = 'mapLayerShow';
 30 | 
 31 | 
 32 | // tile types
 33 | export const T_SUBWAY       = 'subway';
 34 | export const T_PIPE         = 'pipe';
 35 | export const T_UNDERGROUND  = 'underground';
 36 | export const T_EDGE         = 'edge';
 37 | export const T_HEIGHTMAP    = 'heightmap';
 38 | export const T_TERRAIN      = 'terrain';
 39 | export const T_WATER        = 'water';
 40 | export const T_ROAD         = 'road';
 41 | export const T_RAIL         = 'rail';
 42 | export const T_POWER        = 'power';
 43 | export const T_HIGHWAY      = 'highway';
 44 | export const T_ZONE         = 'zone';
 45 | export const T_BUILDING     = 'building';
 46 | 
 47 | 
 48 | // visible tile type id
 49 | export const T_TERRAIN_ID   = 0;
 50 | export const T_WATER_ID     = 1;
 51 | export const T_ROAD_ID      = 2;
 52 | export const T_RAIL_ID      = 3;
 53 | export const T_POWER_ID     = 4;
 54 | export const T_HIGHWAY_ID   = 5;
 55 | export const T_ZONE_ID      = 6;
 56 | export const T_BUILDING_ID  = 7;
 57 | 
 58 | 
 59 | // additional types
 60 | export const T_HIGHWAY_TRAFFIC = 'highwayTraffic';
 61 | export const T_ROAD_TRAFFIC    = 'roadTraffic';
 62 | 
 63 | 
 64 | // tile relative depths
 65 | export const DEPTH_SUBWAY       = 2;
 66 | export const DEPTH_PIPE         = 4;
 67 | export const DEPTH_UNDERGROUND  = 6;
 68 | export const DEPTH_EDGE         = 8;
 69 | export const DEPTH_HEIGHTMAP    = 10;
 70 | export const DEPTH_TERRAIN      = 12;
 71 | export const DEPTH_WATER        = 14;
 72 | export const DEPTH_ROAD         = 16;
 73 | export const DEPTH_RAIL         = 18;
 74 | export const DEPTH_POWER        = 20;
 75 | export const DEPTH_HIGHWAY      = 22;
 76 | export const DEPTH_ZONE         = 24;
 77 | export const DEPTH_BUILDING     = 26;
 78 | 
 79 | 
 80 | // terrain types
 81 | export const TERRAIN_SURFACE    = 'surface';
 82 | export const TERRAIN_WATERFALL  = 'waterfall';
 83 | export const TERRAIN_SUBMERGED  = 'submerged';
 84 | export const TERRAIN_SHORE      = 'shore';
 85 | export const TERRAIN_DRY        = 'dry';
 86 | export const TERRAIN_WATER      = 'water';
 87 | export const TERRAIN_BEDROCK    = 'bedrock';
 88 | 
 89 | 
 90 | // directions
 91 | export const D_NORTH            = 'n';
 92 | export const D_SOUTH            = 's';
 93 | export const D_EAST             = 'e';
 94 | export const D_WEST             = 'w';
 95 | 
 96 | 
 97 | // corner key tiles
 98 | export const CORNER_TOP         = 'top';
 99 | export const CORNER_LEFT        = 'left';
100 | export const CORNER_BOTTOM      = 'bottom';
101 | export const CORNER_RIGHT       = 'right';
102 | 
103 | 
104 | // misc
105 | export const ALTERNATE_TILE     = 'alternateTile';
106 | 
107 | 
108 | // tools
109 | export const TOOL_QUERY         = 'query';
110 | export const TOOL_CENTER        = 'center';
111 | export const TOOL_ROADS         = 'roads';
112 | 
113 | 
114 | // file identifiers
115 | export const CITY               = 'CITY';
116 | export const PAL_MSTR_BMP       = 'PAL_MSTR_BMP';
117 | export const LARGE_DAT          = 'LARGE_DAT';
118 | 
119 | 
120 | // sc2k files to import
121 | export const FILE_PAL_MSTR_BMP = 'PAL_MSTR.BMP';
122 | export const FILE_LARGE_DAT    = 'LARGE.DAT';
123 | 
124 | // sc2k sha1 hashes
125 | // windows 95 special edition version x.y


--------------------------------------------------------------------------------
/src/debug/debug.js:
--------------------------------------------------------------------------------
  1 | import dat from 'dat.gui';
  2 | 
  3 | export default class debug {
  4 |   constructor (options) {
  5 |     this.scene = options.scene;
  6 |     this.globals = this.scene.globals;
  7 |     this.toggleLayer = this.toggleLayerInit();
  8 | 
  9 |     this.gui = new dat.GUI();
 10 |     this.gui.closed = false;
 11 | 
 12 |     let f1 = this.gui.addFolder('Performance');
 13 |     f1.add(this.scene.sys.game.loop, 'actualFps', 'FPS').listen();
 14 |     f1.add(this.scene.viewport, 'objectsRendered', 'Objects Rendered').listen();
 15 |     f1.open();
 16 | 
 17 |     let g1 = this.gui.addFolder('Cursor');
 18 |     g1.add(this.scene.input, 'x', 'X').listen();
 19 |     g1.add(this.scene.input, 'y', 'Y').listen();
 20 |     g1.add(this.scene.viewport.worldPoint, 'x', 'WorldPoint X').listen();
 21 |     g1.add(this.scene.viewport.worldPoint, 'y', 'WorldPoint Y').listen();
 22 |     g1.add(this.scene.city.map.selectedCell, 'x', 'Cell X').listen();
 23 |     g1.add(this.scene.city.map.selectedCell, 'y', 'Cell Y').listen();
 24 |     //g1.open();
 25 |     
 26 |     let g2 = this.gui.addFolder('Camera');
 27 |     g2.add(this.scene.cameras.main, 'scrollX', 'X').listen();
 28 |     g2.add(this.scene.cameras.main, 'scrollY', 'Y').listen();
 29 |     g2.add(this.scene.cameras.main, 'zoom', 'Zoom', 0.1, 2).step(0.1).listen();
 30 |     g2.open();
 31 | 
 32 |     let g3 = this.gui.addFolder('World');
 33 |     g3.add(this, 'sleepWakeWorld', 'Sleep / Wake');
 34 |     //g3.add(this.scene.city.load, 'open', 'Open City');
 35 |     //g3.add(this.city.save, 'saveCity', 'Save City');
 36 |     g3.add(this.toggleLayer, 'terrain', 'Toggle Terrain');
 37 |     g3.add(this.toggleLayer, 'water', 'Toggle Water');
 38 |     g3.add(this.toggleLayer, 'heightmap', 'Toggle Height Map');
 39 |     //g3.add(this.toggleLayer, 'road', 'Toggle Road');
 40 |     //g3.add(this.toggleLayer, 'power', 'Toggle Power');
 41 |     //g3.add(this.toggleLayer, 'building', 'Toggle Building');
 42 |     //g3.add(this.toggleLayer, 'zone', 'Toggle Zone');
 43 |     //g3.add(this.toggleLayer, 'rail', 'Toggle Rail');
 44 |     //g3.add(this.toggleLayer, 'highway', 'Toggle Highway');
 45 |     //g3.add(this.toggleLayer, 'subway', 'Toggle Subway');
 46 |     //g3.add(this.toggleLayer, 'pipes', 'Toggle Pipes');
 47 |     //g3.open();
 48 |   }
 49 | 
 50 |   toggleLayerInit () {
 51 |     return {
 52 |       terrain: () => {
 53 |         this.scene.city.map.layers.terrain.toggle();
 54 |       },
 55 |       heightmap: () => {
 56 |         this.scene.city.map.layers.heightmap.toggle();
 57 |       },
 58 |       water: () => {
 59 |         this.scene.city.map.layers.water.toggle();
 60 |       },
 61 |       road: () => {
 62 |         this.scene.city.map.layers.road.toggle();
 63 |       },
 64 |       power: () => {
 65 |         this.scene.city.map.layers.power.toggle();
 66 |       },
 67 |       building: () => {
 68 |         this.scene.city.map.layers.building.toggle();
 69 |       },
 70 |       zone: () => {
 71 |         this.scene.city.map.layers.zone.toggle();
 72 |       },
 73 |       rail: () => {
 74 |         this.scene.city.map.layers.rail.toggle();
 75 |       },
 76 |       highway: () => {
 77 |         this.scene.city.map.layers.highway.toggle();
 78 |       },
 79 |       subway: () => {
 80 |         this.scene.city.map.layers.subway.toggle();
 81 |       },
 82 |       pipes: () => {
 83 |         this.scene.city.map.layers.pipes.toggle();
 84 |       },
 85 |     };
 86 |   }
 87 | 
 88 |   shutdown () {
 89 |     if (this.gui)
 90 |       this.gui.destroy();
 91 |   }
 92 | 
 93 |   loadCity () {
 94 |     this.scene.city.globals.loadCity();
 95 |   }
 96 | 
 97 |   saveCity () {
 98 |     this.scene.city.globals.saveCity();
 99 |   }
100 | 
101 |   sleepWakeWorld () {
102 |     if (this.scene.isSleeping)
103 |       this.scene.wake();
104 |     else
105 |       this.scene.sleep();
106 |   }
107 | }


--------------------------------------------------------------------------------
/src/import/artwork.js:
--------------------------------------------------------------------------------
  1 | import Phaser from 'phaser';
  2 | import data from './tiles';
  3 | import * as CONST from '../constants';
  4 | import { polygonUnion } from '../utils';
  5 | 
  6 | export default class artwork {
  7 |   constructor (options) {
  8 |     this.scene       = options.scene;
  9 |     this.data        = this.scene.cache.binary.get(CONST.LARGE_DAT);
 10 |     this.palette     = this.scene.palette;
 11 |     this.tiles       = data;
 12 |     this.textureSize = 4096;
 13 |     this.texture     = this.scene.textures.createCanvas('temp', this.textureSize, this.textureSize);
 14 |     this.json        = {};
 15 |     this.canvas      = null;
 16 | 
 17 |     this.polygonUnion = polygonUnion;
 18 | 
 19 |     this.parse();
 20 |     this.createTexture();
 21 |   }
 22 | 
 23 | 
 24 |   //
 25 |   // parse dat file into raw image data for each frame
 26 |   //
 27 |   parse () {
 28 |     let view       = new DataView(this.data);
 29 |     let imageCount = view.getUint16(0x00);
 30 |     let img        = new DataView(this.data, 2, imageCount * 10);
 31 | 
 32 |     // calculate image ids, offsets and dimensions
 33 |     // each image header is stored as a 10 byte chunk
 34 |     // only store unique images (1204 and 1183 are duplicated)
 35 |     for (let offset = 0; offset < imageCount * 10; offset += 10) {
 36 |       let id = img.getUint16(offset) - 1000;
 37 |       let data = {};
 38 | 
 39 |       data.imageName  = img.getUint16(offset);
 40 |       data.startBytes = img.getUint32(offset + 2);
 41 |       data.height     = img.getUint16(offset + 6);
 42 |       data.width      = img.getUint16(offset + 8);
 43 | 
 44 |       // use the offset start of the next frame to determine the end of this frame
 45 |       if (offset + 10 <= img.byteLength - 2)
 46 |         data.nextId = img.getUint16(offset + 10) - 1000;
 47 | 
 48 |       this.tiles[id].data = data;
 49 |     }
 50 | 
 51 |     // calculate image ending offset
 52 |     // separate loop so we can easily get the end byte of the following frame
 53 |     for (let i = 1; i < this.tiles.length; i++) {
 54 |       let tile = this.tiles[i];
 55 | 
 56 |       // image block data
 57 |       tile.data.endBytes = (tile.data.nextId !== undefined ? this.tiles[tile.data.nextId].data.startBytes : this.data.byteLength);
 58 |       tile.data.size     = tile.data.endBytes - tile.data.startBytes;
 59 |       tile.data.rawData  = new DataView(this.data.slice(tile.data.startBytes, tile.data.endBytes));
 60 |       tile.data.block    = this.block(tile.data);
 61 | 
 62 |       tile.loaded   = false;
 63 |       tile.animated = this.isAnimatedImage(tile.data.block);
 64 |       tile.frames   = tile.frames || this.getFrameCount(tile.data);
 65 |       tile.width    = tile.data.width * CONST.SCALE;
 66 |       tile.height   = tile.data.height * CONST.SCALE;
 67 |       tile.rotate   = tile.rotate || [tile.id, tile.id, tile.id, tile.id];
 68 |       tile.hitbox   = this.shape(tile.hitbox || tile.heightmap || this.tiles[256].heightmap);
 69 | 
 70 |       tile.textures = [];
 71 |       
 72 |       for (let t = 0; t <= tile.frames; t++)
 73 |         tile.textures.push(tile.image+'_'+t);
 74 | 
 75 |       this.tiles[i] = tile;
 76 |     }
 77 | 
 78 |     delete this.tiles[0];
 79 |   }
 80 | 
 81 | 
 82 |   //
 83 |   // converts x/y data array to a Phaser polygon
 84 |   //
 85 |   shape (hitbox) {
 86 |     let polygon = [];
 87 | 
 88 |     if (hitbox.reference)
 89 |       hitbox = this.tiles[hitbox.reference].hitbox || this.tiles[hitbox.reference].heightmap;
 90 | 
 91 |     if (hitbox instanceof Phaser.Geom.Polygon)
 92 |       hitbox = { upper: hitbox.points };
 93 | 
 94 |     // merge all sides of the shape into a single array of points
 95 |     let shape = [].concat(
 96 |       (hitbox.lower         ? hitbox.lower         : []),
 97 |       (hitbox.upper         ? hitbox.upper         : []),
 98 |       (hitbox.south         ? hitbox.south         : []),
 99 |       (hitbox.east          ? hitbox.east          : []),
100 |       (hitbox.west          ? hitbox.west          : []),
101 |       (hitbox.southEast     ? hitbox.southEast     : []),
102 |       (hitbox.southWest     ? hitbox.southWest     : []),
103 |       (hitbox.northEast     ? hitbox.northEast     : []),
104 |       (hitbox.northWest     ? hitbox.northWest     : []),
105 |       (hitbox.rockTop       ? hitbox.rockTop       : []),
106 |       (hitbox.rockSouthWest ? hitbox.rockSouthWest : []),
107 |       (hitbox.rockSouthEast ? hitbox.rockSouthEast : []),
108 |     );
109 | 
110 |     // combine into a single polygon with an exterior wall
111 |     shape = polygonUnion(shape, shape);
112 | 
113 |     for (let i = 0; i < shape.length; i++)
114 |       polygon.push(new Phaser.Geom.Point((shape[i].x), (shape[i].y)));
115 | 
116 |     return new Phaser.Geom.Polygon(polygon);
117 |   }
118 | 
119 | 
120 | 
121 |   //
122 |   // get the lowest common multiplier for all palette animation sequences
123 |   //
124 |   getFrameCount (image) {
125 |     let frames = [];
126 | 
127 |     for (let y = 0; y < image.block.length; y++)
128 |       for (let x = 0; x < image.block[y].pixels.length; x++)
129 |         frames.push(this.palette.getFrameCountFromIndex(image.block[y].pixels[x]));
130 | 
131 |     if (frames.length <= 1)
132 |       return 1;
133 |     else
134 |       return this.lcm.apply(null, frames);
135 |   }
136 | 
137 | 
138 |   //
139 |   // check if image contains any palette indexes that cycle with each frame (animated)
140 |   //
141 |   isAnimatedImage (image) {
142 |     for (var y = 0; y < image.length; y++)
143 |       for (var x = 0; x < image[y].pixels.length; x++)
144 |         if (this.palette.animatedIndexes.includes(image[y].pixels[x]))
145 |           return true;
146 | 
147 |     return false;
148 |   }
149 | 
150 | 
151 |   //
152 |   // processes image bytes into individual image data rows / chunks
153 |   //
154 |   block (image) {
155 |     let offset = 0;
156 |     let img    = [];
157 | 
158 |     while (offset <= image.size) {
159 |       let row = {};
160 | 
161 |       row.length = image.rawData.getUint8(offset);
162 |       row.more   = image.rawData.getUint8(offset + 1);
163 | 
164 |       offset += 2;
165 |       row.pixels = this.imageRow(image.rawData.buffer.slice(offset, offset + row.length));
166 | 
167 |       img.push(row);
168 | 
169 |       if (row.more == 2)
170 |         break;
171 | 
172 |       offset += row.length;
173 |     }
174 | 
175 |     return img;
176 |   }
177 | 
178 | 
179 |   //
180 |   // process image rows / chunks
181 |   //
182 |   imageRow (data) {
183 |     let bytes   = new DataView(data);
184 |     let padding = 0;
185 |     let length  = 0;
186 |     let extra   = 0;
187 |     let pixels  = null;
188 |     let mode    = null;
189 |     let image   = [];
190 |     let offset  = 0;
191 |     let header  = 0;
192 | 
193 |     if (bytes.byteLength == 0)
194 |       return image;
195 | 
196 |     // loop through the row chunks
197 |     while (offset < bytes.byteLength - 1) {
198 |       // special case for multi-chunk rows, drop first byte if zero
199 |       if (bytes.getUint8(offset + 0x00) == 0x00 && offset > 0)
200 |         offset++;
201 | 
202 |       // get chunk mode
203 |       mode = bytes.getUint8(offset + 0x01);
204 | 
205 |       if (mode == 0x00 || mode == 0x03) {
206 |         padding = bytes.getUint8(offset + 0x00); // padding pixels from the left edge
207 |         length  = bytes.getUint8(offset + 0x02); // pixels in the row to draw
208 |         extra   = bytes.getUint8(offset + 0x03); // extra bit / flag
209 | 
210 |         if (length == 0 && extra == 0x00) {
211 |           header = 0x06;
212 |           length = bytes.getUint8(offset + 0x04);
213 |           extra  = bytes.getUint8(offset + 0x05);
214 |           pixels = new DataView(bytes.buffer.slice(offset + header, offset + header + length));
215 |         } else {
216 |           header = 0x04;
217 |           pixels = new DataView(bytes.buffer.slice(offset + header, offset + header + length));
218 |         }
219 | 
220 |       } else if (mode == 0x04) {
221 |         header = 0x02;
222 |         length = bytes.getUint8(offset + 0x00);
223 |         pixels = new DataView(bytes.buffer.slice(offset + header, offset + header + length));
224 |       }
225 | 
226 |       // byte offset for the next loop
227 |       offset += header + length;
228 | 
229 |       // save padding pixels (transparent) as null
230 |       for (let i = 0; i < padding; i++)
231 |         image.push(null);
232 | 
233 |       // save pixel data afterwards
234 |       for (let i = 0; i < pixels.byteLength; i++)
235 |         image.push(pixels.getUint8(i));
236 |     }
237 | 
238 |     return image;
239 |   }
240 | 
241 | 
242 |   createTexture () {
243 |     let x           = 1;
244 |     let y           = 1;
245 |     let maxWidth    = 16;
246 |     let maxHeight   = 8;
247 |     let rowMaxY     = 0;
248 |     let padding     = 1;
249 |     let imageData   = this.texture.getData(0, 0, this.textureSize, this.textureSize);
250 |     let buffer      = new ArrayBuffer(imageData.data.length);
251 |     let buffer8     = new Uint8ClampedArray(buffer);
252 |     let buffer32    = new Uint32Array(buffer);
253 | 
254 | 
255 |     // looping 128 times here to sort tiles by size
256 |     // this shuffles the smaller tiles to the front of the tilemap
257 |     for (let loop = 0; loop < 128; loop++) {
258 |       // loop for each tile
259 |       for (let i = 1; i < this.tiles.length; i++) {
260 |         let tile = this.tiles[i];
261 | 
262 |         // skip tiles that were already flagged as loaded
263 |         if (tile.loaded) continue;
264 | 
265 |         // skip anything that exceeds the current maximum
266 |         if (tile.data.width > maxWidth || tile.data.height > maxHeight) continue;
267 | 
268 |         // loop on every frame
269 |         for (let f = 0; f < tile.frames; f++) {
270 |           // max tile height in this row
271 |           if (tile.data.height > rowMaxY)
272 |             rowMaxY = tile.data.height;
273 | 
274 |           // exceeds tilemap width, start a new row
275 |           if (x + tile.data.width > this.textureSize) {
276 |             x = 1;
277 |             y += rowMaxY + padding;
278 |             rowMaxY = 0;
279 |           }
280 | 
281 |           // drop any colors?
282 |           // used to foribly remove certain palette indexes
283 |           // from tiles (example: traffic tiles)
284 |           if (tile.importOptions && tile.importOptions.dropColor)
285 |             tile.importOptions.dropColor.forEach((index, i) => {
286 |               if (Number.isInteger(index))
287 |                 tile.importOptions.dropColor[i] = this.palette.getColorString(index, 0);
288 |             });
289 | 
290 |           for (let ty = 0; ty < tile.data.block.length; ty++) {
291 |             for (let tx = 0; tx < tile.data.block[ty].pixels.length; tx++) {
292 |               // palette index value
293 |               let index = tile.data.block[ty].pixels[tx];
294 | 
295 |               // drop out specific palette colors and transparency
296 |               if (tile.importOptions && tile.importOptions.dropColor && tile.importOptions.dropColor.includes(this.palette.getColorString(index, f)))
297 |                 index = null;
298 | 
299 |               // set color and canvas x/y index
300 |               let color = this.palette.getColor(index, f);
301 |               let cx = x + tx;
302 |               let cy = y + ty;
303 |               let idx = cy * this.textureSize + cx;
304 | 
305 |               buffer32[idx] = (color.alpha << 24) | (color.blue << 16) | (color.green << 8) | (color.red << 0);
306 |             }
307 |           }
308 | 
309 |           // add tilemap data
310 |           this.json[tile.data.imageName + '_' + f] = {
311 |             frame: { x: x, y: y, w: tile.data.width, h: tile.data.height },
312 |             rotated: false,
313 |             trimmed: false,
314 |             spriteSourceSize: { x: 0, y: 0, w: tile.data.width, h: tile.data.height },
315 |             sourceSize: { w: tile.data.width, h: tile.data.height }
316 |           };
317 | 
318 |           // move drawing position + padding
319 |           x += tile.data.width + padding;
320 |           
321 |           // flag tile as loaded if the frame count matches the current frame
322 |           // or if the tile has no frames
323 |           if (tile.frames == f + 1 || tile.frames == 1)
324 |             tile.loaded = true;
325 |         }
326 |       }
327 | 
328 |       // increase tile size next loop
329 |       maxWidth = maxWidth + 4;
330 |       maxHeight = maxHeight + 4;
331 |     }
332 | 
333 |     // save buffer to texture and wrap json object
334 |     imageData.data.set(buffer8);
335 |     this.texture.putData(imageData, 0, 0);
336 |     this.texture.refresh();
337 |     this.canvas = this.texture.getCanvas();
338 |     this.json = { frames: this.json };
339 | 
340 | 
341 |     // load texture atlas
342 |     this.scene.textures.addAtlas(CONST.TILE_ATLAS, this.canvas, this.json);
343 | 
344 | 
345 |     // remove temp canvas
346 |     this.scene.textures.remove('temp');
347 | 
348 | 
349 |     // add animations
350 |     for (let i = 1; i < this.tiles.length; i++) {
351 |       let tile = this.tiles[i];
352 | 
353 |       // set up animations
354 |       if (tile.frames > 1) {
355 |         this.scene.anims.create({
356 |           key: tile.data.imageName,
357 |           frames: this.scene.anims.generateFrameNames(CONST.TILE_ATLAS, {
358 |             prefix: tile.data.imageName + '_',
359 |             start: (tile.reverseAnimation ? tile.frames : 0),
360 |             end: (tile.reverseAnimation ? 0 : tile.frames)
361 |           }),
362 |           repeat: -1,
363 |           frameRate: tile.frameRate || 2,
364 |           delay: tile.animationDelay || 0
365 |         });
366 | 
367 |         this.scene.anims.create({
368 |           key: tile.data.imageName+'_R',
369 |           frames: this.scene.anims.generateFrameNames(CONST.TILE_ATLAS, {
370 |             prefix: tile.data.imageName + '_',
371 |             start: (tile.reverseAnimation ? 0 : tile.frames),
372 |             end: (tile.reverseAnimation ? tile.frames : 0)
373 |           }),
374 |           repeat: -1,
375 |           frameRate: tile.frameRate || 2,
376 |           delay: tile.animationDelay || 0
377 |         });
378 |       }
379 | 
380 |       this.tiles[i] = tile;
381 |     }
382 |   }
383 | 
384 | 
385 |   lcm (min, max) {
386 |     function range (min, max) {
387 |       let out = [];
388 |   
389 |       for (let i = min; i <= max; i++)
390 |         out.push(i);
391 | 
392 |       return out;
393 |     }
394 | 
395 |     function gcd (a, b) {
396 |       return !b ? a : gcd(b, a % b);
397 |     }
398 | 
399 |     function lcm (a, b) {
400 |       return (a * b) / gcd(a, b);
401 |     }
402 | 
403 |     let multiple = min;
404 | 
405 |     range(min, max).forEach(function(n) {
406 |       multiple = lcm(multiple, n);
407 |     });
408 | 
409 |     return multiple;
410 |   }
411 | }


--------------------------------------------------------------------------------
/src/import/sc2.js:
--------------------------------------------------------------------------------
  1 | import * as CONST from '../constants';
  2 | import * as segmentHandlers from './segmentHandlers/';
  3 | import { bytesToAscii } from './segmentHandlers/common';
  4 | 
  5 | export default class sc2 {
  6 |   import (buffer) {
  7 |     let size = CONST.MAP_SIZE;
  8 |     let x = 0;
  9 |     let y = 0;
 10 |     let map = {
 11 |       cells: [],
 12 |       _segmentData: {}
 13 |     };
 14 | 
 15 |     for (let i = 0; i < size * size; i++) {
 16 |       map.cells.push({ x: x, y: y, _segmentData: {} });
 17 | 
 18 |       if (y === 127) {
 19 |         y = 0;
 20 |         x += 1;
 21 |       } else {
 22 |         y += 1;
 23 |       }
 24 |     }
 25 | 
 26 | 
 27 |     // iterate through each data segment and parse
 28 |     let bytes = buffer.subarray(12);
 29 |     let segments = this.splitSegments(bytes);
 30 | 
 31 |     Object.keys(segments).sort().forEach((title) => {
 32 |       let type = title;
 33 | 
 34 |       // handle special case for scenario
 35 |       // text sections
 36 |       if (title.startsWith('TEXT'))
 37 |         type = 'TEXT';
 38 |         
 39 |       let handler = segmentHandlers[type];
 40 | 
 41 |       if (handler)
 42 |         handler(segments[title], map);
 43 |       else
 44 |         console.log('Unknown Segment:',type);
 45 |     });
 46 | 
 47 | 
 48 |     // city metadata
 49 |     map.info = {
 50 |       name:        map._segmentData.CNAM.text || 'Default',
 51 |       height:      size,
 52 |       width:       size,
 53 |       rotation:    map._segmentData.MISC.rotation,
 54 |       waterLevel:  map._segmentData.MISC.globalSeaLevel,
 55 |     };
 56 | 
 57 | 
 58 |     // populate data cells
 59 |     for (let i = 0; i < map.cells.length; i++) {
 60 |       let cell = map.cells[i];
 61 |       let data = cell._segmentData;
 62 | 
 63 |       cell.tiles = { _list: [] };
 64 | 
 65 |       if (data.XTER.terrain)  cell.tiles._list.push({ id: data.XTER.terrain,  type: CONST.T_TERRAIN });
 66 |       if (data.XTER.water)    cell.tiles._list.push({ id: data.XTER.water,    type: CONST.T_WATER });
 67 | 
 68 |       if (cell.x == 0 || cell.x == 127 || cell.y == 0 || cell.y == 127)
 69 |         if (data.XTER.terrain)  cell.tiles._list.push({ id: data.XTER.terrain,  type: CONST.T_EDGE });
 70 | 
 71 |       //if (data.XTER.terrain)  cell.tiles._list.push({ id: data.XTER.terrain,  type: CONST.T_HEIGHTMAP });
 72 |       
 73 |       //if (data.XZON.zone)     cell.tiles._list.push({ id: data.XZON.zone,     type: CONST.T_ZONE });
 74 | 
 75 |       if (data.XBLD.id != 0) cell.tiles._list.push(data.XBLD);
 76 | 
 77 |       //if (data.XUND.subway)   cell.tiles._list.push({ id: data.XUND.subway,   type: CONST.T_SUBWAY });
 78 |       //if (data.XUND.pipes)    cell.tiles._list.push({ id: data.XUND.pipes,    type: CONST.T_PIPES });
 79 | 
 80 |       cell.corners = {
 81 |         left:   data.XZON.left,
 82 |         top:    data.XZON.top,
 83 |         bottom: data.XZON.bottom,
 84 |         right:  data.XZON.right,
 85 |         none:   data.XZON.none,
 86 |       };
 87 | 
 88 |       cell.zone = {
 89 |         id:   data.XZON.zone,
 90 |         type: data.XZON.zoneType,
 91 |       };
 92 | 
 93 |       cell.z      = data.ALTM.altitude;
 94 |       cell.rotate = data.XBIT.rotate;
 95 | 
 96 |       cell.power = {
 97 |         wired:   data.XBIT.wired,
 98 |         powered: data.XBIT.powered,
 99 |       };
100 | 
101 |       cell.pipes = {
102 |         piped:   data.XBIT.piped,
103 |         watered: data.XBIT.watered,
104 |       };
105 | 
106 |       cell.water = {
107 |         type:       data.XTER.type,
108 |         covered:    data.XBIT.waterCovered,
109 |         salt:       data.XBIT.saltWater,
110 |       };
111 | 
112 |       map.cells[i] = cell;
113 |     }
114 | 
115 |     return map;
116 |   }
117 | 
118 | 
119 |   splitSegments (bytes) {
120 |     let segments = {};
121 | 
122 |     while (bytes.length > 0) {
123 |       let title    = bytesToAscii(bytes.subarray(0x00, 0x04));
124 |       let length   = new DataView(bytes.subarray(0x04, 0x08).buffer).getUint32(bytes.subarray(0x04, 0x08).byteOffset);
125 |       let contents = bytes.subarray(0x08, 0x08 + length);
126 | 
127 |       if (!['ALTM','CNAM','TEXT','PICT','SCEN','TMPL'].includes(title))
128 |         contents = this.decompressSegment(contents);
129 | 
130 |       // can have multilpe TEXT segments
131 |       if (title == 'TEXT') {
132 |         let type = bytes.subarray(0x08, 0x09);
133 | 
134 |         if (type == 0x80)
135 |           title = 'TEXT_1';
136 |         else if (type == 0x81)
137 |           title = 'TEXT_2';
138 |       }
139 | 
140 |       segments[title] = contents;
141 |       bytes = bytes.subarray(0x08 + length);
142 |     }
143 | 
144 |     return segments;
145 |   }
146 | 
147 | 
148 |   decompressSegment (bytes) {
149 |     let output = [];
150 |     let dataCount = 0;
151 | 
152 |     for (let i = 0; i < bytes.length; i++) {
153 |       if (dataCount > 0) {
154 |         output.push(bytes[i]);
155 |         dataCount -= 1;
156 |         continue;
157 |       }
158 | 
159 |       // data bytes
160 |       if (bytes[i] < 128) {
161 |         dataCount = bytes[i];
162 |       
163 |       // run-length encoded byte
164 |       } else {
165 |         let repeatCount = bytes[i] - 127;
166 |         let repeated = bytes[i + 1];
167 | 
168 |         for (let i = 0; i < repeatCount; i++)
169 |           output.push(repeated);
170 |         
171 |         // skip the next byte
172 |         i += 1;
173 |       }
174 |     }
175 | 
176 |     return Uint8Array.from(output);
177 |   }
178 | }


--------------------------------------------------------------------------------
/src/import/segmentHandlers/ALTM.js:
--------------------------------------------------------------------------------
 1 | import { bin2str } from './common';
 2 | 
 3 | export default (data, map) => {
 4 |   let view = new DataView(data.buffer, data.byteOffset, data.byteLength);
 5 | 
 6 |   for (let i = 0; i < data.byteLength / 2; i++) {
 7 |     let bits = view.getUint16(i * 2);
 8 |     let altm = {};
 9 | 
10 |     // how many levels below altitude should we display a grey block
11 |     // for a tunnel?
12 |     altm.tunnelLevels    = (((bits >> 8) & 0b11111100) >> 2);
13 | 
14 |     // related to tunnel?
15 |     // appears to be set to 1 for hydroelectric dams and nearby
16 |     // surface water tiles
17 |     // not used for now
18 |     //altm.unknownBits     = ((bits >> 8) & 0b00000011);
19 | 
20 |     // level / altitude
21 |     altm.altitude        = (bits & 0b0000000000011111);
22 | 
23 |     // unknown, not always accurate (rely on XTER value instead)
24 |     // not used for now
25 |     //altm.waterLevel      = (bits & 0b0000000001100000) >> 5;
26 | 
27 |     // unknown, not always accurate (rely on XTER value instead)
28 |     // not used for now
29 |     //altm.waterCovered    = (bits & 0b0000000010000000) >> 7;
30 | 
31 |     // raw binary values as strings for research/debug
32 |     altm.binaryText = {
33 |       bits:             bin2str(bits, 16),
34 |       first8bits:       bin2str((bits & 0b1111111100000000) >> 8, 8),
35 |       last8bits:        bin2str((bits & 0b0000000011111111), 8),
36 |       //tunnelLevelsBits: bin2str(altm.tunnelLevels, 8),
37 |       //unknownBits:      bin2str(altm.unknownBits,  8),
38 |       //altitudeBits:     bin2str(altm.altitude,     8),
39 |       //waterLevelBits:   bin2str(altm.waterLevel,   8),
40 |       //waterCoveredBits: bin2str(altm.waterCovered, 8),
41 |     };
42 | 
43 |     map.cells[i]._segmentData.ALTM = altm;
44 |   }
45 | };


--------------------------------------------------------------------------------
/src/import/segmentHandlers/CNAM.js:
--------------------------------------------------------------------------------
 1 | import { bytesToAscii } from './common';
 2 | 
 3 | export default (data, map) => {
 4 |   let text = data.subarray(1, data.indexOf(0));
 5 | 
 6 |   map._segmentData.CNAM = {
 7 |     text: bytesToAscii(text),
 8 |     data: data,
 9 |     raw: text,
10 |   };
11 | };


--------------------------------------------------------------------------------
/src/import/segmentHandlers/PICT.js:
--------------------------------------------------------------------------------
 1 | export default (data, map) => {
 2 |   let pict = {};
 3 | 
 4 |   // similar format to game artwork
 5 |   // 4 byte header PICT
 6 |   // 4 byte length
 7 |   // 4 byte fixed '80 00 00 00'
 8 |   // 2 byte image width
 9 |   // 2 byes image height
10 |   // [length] bytes = image data, 1 byte per pixel using palette lookup
11 | 
12 |   pict.raw = data;
13 | 
14 |   map._segmentData.PICT = pict;
15 | };


--------------------------------------------------------------------------------
/src/import/segmentHandlers/SCEN.js:
--------------------------------------------------------------------------------
 1 | export default (data, map) => {
 2 |   let scen = {};
 3 | 
 4 |   scen.disaster = {
 5 |     disasterType: new DataView(data.slice(4, 6).buffer).getUint16(0),
 6 |     disasterX:    new DataView(data.slice(6, 7).buffer).getUint8(0),
 7 |     disasterY:    new DataView(data.slice(7, 8).buffer).getUint8(0),
 8 |   };
 9 | 
10 |   scen.timeLimitMonths = new DataView(data.slice(8, 10).buffer).getUint16(0);
11 | 
12 |   scen.populationGoals = {
13 |     city:        new DataView(data.slice(10, 14).buffer).getUint32(0),
14 |     residential: new DataView(data.slice(14, 18).buffer).getUint32(0),
15 |     commercial:  new DataView(data.slice(18, 22).buffer).getUint32(0),
16 |     industrial:  new DataView(data.slice(22, 26).buffer).getUint32(0),
17 |   };
18 | 
19 |   scen.fundGoal        = new DataView(data.slice(26, 30).buffer).getUint32(0);
20 |   scen.landValueGoal   = new DataView(data.slice(30, 34).buffer).getUint32(0);
21 |   scen.educationGoal   = new DataView(data.slice(34, 38).buffer).getUint32(0);
22 |   scen.pollutionLimit  = new DataView(data.slice(38, 42).buffer).getUint32(0);
23 |   scen.crimeLimit      = new DataView(data.slice(42, 46).buffer).getUint32(0);
24 |   scen.trafficLimit    = new DataView(data.slice(46, 50).buffer).getUint32(0);
25 | 
26 |   scen.buildItem1      = new DataView(data.slice(50, 51).buffer).getUint8(0);
27 |   scen.buildItem2      = new DataView(data.slice(51, 52).buffer).getUint8(0);
28 | 
29 |   if (data.byteLength > 52) {
30 |     scen.item1Tiles      = new DataView(data.slice(52, 54).buffer).getUint16(0);
31 |     scen.item2Tiles      = new DataView(data.slice(54, 56).buffer).getUint16(0);
32 |   }
33 | 
34 |   scen.raw = data;
35 | 
36 |   map._segmentData.SCEN = scen;
37 | };


--------------------------------------------------------------------------------
/src/import/segmentHandlers/TEXT.js:
--------------------------------------------------------------------------------
 1 | import { bytesToAscii } from './common';
 2 | 
 3 | export default (data, map) => {
 4 |   let text = {};
 5 | 
 6 |   text.text = bytesToAscii(data.subarray(4)).replace(/[\r]/g, ' ');
 7 |   text.raw = data;
 8 | 
 9 |   map._segmentData.TEXT = text;
10 | };


--------------------------------------------------------------------------------
/src/import/segmentHandlers/TMPL.js:
--------------------------------------------------------------------------------
1 | export default (data, map) => {
2 |   let tmpl = {};
3 | 
4 |   tmpl.raw = data;
5 | 
6 |   map._segmentData.TMPL = tmpl;
7 | };


--------------------------------------------------------------------------------
/src/import/segmentHandlers/XBIT.js:
--------------------------------------------------------------------------------
 1 | import { bin2str } from './common';
 2 | 
 3 | export default (data, map) => {
 4 |   let view = new Uint8Array(data);
 5 | 
 6 |   view.forEach((bits, i) => {
 7 |     let xbit = {};
 8 | 
 9 |     // can this tile receive power?
10 |     xbit.wired         = (bits & 0b10000000) !== 0;
11 | 
12 |     // does this tile HAVE power?
13 |     xbit.powered       = (bits & 0b01000000) !== 0;
14 | 
15 |     // can this tile receive piped water?
16 |     xbit.piped         = (bits & 0b00100000) !== 0;
17 | 
18 |     // does this tile HAVE piped water?
19 |     xbit.watered       = (bits & 0b00010000) !== 0;
20 | 
21 |     // mask for XVAL
22 |     // not currently used
23 |     //xbit.xvalMask      = (bits & 0b00001000) !== 0;
24 | 
25 |     // is this tile covered by water?
26 |     xbit.waterCovered  = (bits & 0b00000100) !== 0;
27 | 
28 |     // rotate tile by 90 degrees?
29 |     xbit.rotate        = (bits & 0b00000010) !== 0;
30 | 
31 |     // is tile salt water or fresh water?
32 |     xbit.saltWater     = (bits & 0b00000001) !== 0;
33 | 
34 |     xbit.binaryText = {
35 |       bits: bin2str(bits, 8),
36 |     };
37 | 
38 |     map.cells[i]._segmentData.XBIT = xbit;
39 |   });
40 | };


--------------------------------------------------------------------------------
/src/import/segmentHandlers/XBLD.js:
--------------------------------------------------------------------------------
 1 | import { bin2str } from './common';
 2 | import tiles from '../tiles';
 3 | 
 4 | export default (data, map) => {
 5 |   let view = new Uint8Array(data);
 6 | 
 7 |   view.forEach((bits, i) => {
 8 |     let xbld = {
 9 |       id: 0,
10 |       type: null,
11 |       subtype: null,
12 |       desc: null
13 |     };
14 | 
15 |     if (tiles[bits]) {
16 |       xbld.id      = bits;
17 |       xbld.type    = tiles[bits].type    || xbld.type;
18 |       xbld.subtype = tiles[bits].subtype || xbld.subtype;
19 |     }
20 | 
21 |     if (xbld.id > 0)
22 |       xbld.desc = tiles[xbld.id];
23 | 
24 |     // raw binary values as strings for research/debug
25 |     xbld.binaryText = {
26 |       bits: bin2str(bits, 8)
27 |     };
28 | 
29 |     map.cells[i]._segmentData.XBLD = xbld;
30 |   });
31 | };


--------------------------------------------------------------------------------
/src/import/segmentHandlers/XCRM.js:
--------------------------------------------------------------------------------
 1 | import { resize } from './common';
 2 | 
 3 | export default (data, map) => {
 4 |   let view = new Uint8Array(data);
 5 |   let xcrm = [];
 6 | 
 7 |   view.forEach((bits, i) => {
 8 |     xcrm[i] = bits;
 9 |   });
10 | 
11 |   // resize data array from 64x64 to 128x128
12 |   xcrm = resize(xcrm, 64, 128);
13 | 
14 |   xcrm.forEach((data, i) => {
15 |     map.cells[i]._segmentData.XCRM = xcrm[i];
16 |   });
17 | };


--------------------------------------------------------------------------------
/src/import/segmentHandlers/XFIR.js:
--------------------------------------------------------------------------------
 1 | import { resize } from './common';
 2 | 
 3 | export default (data, map) => {
 4 |   let view = new Uint8Array(data);
 5 |   let xfir = [];
 6 | 
 7 |   view.forEach((bits, i) => {
 8 |     xfir[i] = bits;
 9 |   });
10 | 
11 |   // resize data array from 64x64 to 128x128
12 |   xfir = resize(xfir, 32, 128);
13 | 
14 |   xfir.forEach((data, i) => {
15 |     map.cells[i]._segmentData.XFIR = xfir[i];
16 |   });
17 | };


--------------------------------------------------------------------------------
/src/import/segmentHandlers/XGRP.js:
--------------------------------------------------------------------------------
1 | export default (data, map) => {
2 |   return;
3 | }


--------------------------------------------------------------------------------
/src/import/segmentHandlers/XLAB.js:
--------------------------------------------------------------------------------
 1 | import { bytesToAscii } from './common';
 2 | 
 3 | export default (data, map) => {
 4 |   let xlab = [];
 5 | 
 6 |   for (let i = 0; i < 256; i++) {
 7 |     let offset = i * 25;
 8 |     let length = data[offset];
 9 |     let text = data.subarray(offset + 1, offset + 1 + length);
10 | 
11 |     xlab[i] = {
12 |       text: bytesToAscii(text),
13 |       offset: offset,
14 |       length: length,
15 |       raw: text,
16 |     };
17 |   }
18 | 
19 |   map._segmentData.XLAB = xlab;
20 | };


--------------------------------------------------------------------------------
/src/import/segmentHandlers/XMIC.js:
--------------------------------------------------------------------------------
 1 | export default (data, map) => {
 2 |   let view = new DataView(data.buffer, data.byteOffset, data.byteLength);
 3 |   
 4 |   map._segmentData.XMIC = [];
 5 | 
 6 |   for (let i = 0; i < data.byteLength; i++) {
 7 |     let offset = i * 8;
 8 | 
 9 |     if (offset >= data.byteLength)
10 |       break;
11 | 
12 |     let xmic = {};
13 | 
14 |     xmic.building = view.getUint8(offset);
15 | 
16 |     xmic.data1 = xmic.building == 0 ? 0 : view.getUint8(offset + 1);
17 |     xmic.data2 = xmic.building == 0 ? 0 : view.getUint16(offset + 2);
18 |     xmic.data3 = xmic.building == 0 ? 0 : view.getUint16(offset + 4);
19 |     xmic.data4 = xmic.building == 0 ? 0 : view.getUint16(offset + 6);
20 | 
21 |     map._segmentData.XMIC.push(xmic);
22 |   }
23 | };


--------------------------------------------------------------------------------
/src/import/segmentHandlers/XPLC.js:
--------------------------------------------------------------------------------
 1 | import { resize } from './common';
 2 | 
 3 | export default (data, map) => {
 4 |   let view = new Uint8Array(data);
 5 |   let xplc = [];
 6 | 
 7 |   view.forEach((bits, i) => {
 8 |     xplc[i] = bits;
 9 |   });
10 | 
11 |   // resize data array from 64x64 to 128x128
12 |   xplc = resize(xplc, 32, 128);
13 | 
14 |   xplc.forEach((data, i) => {
15 |     map.cells[i]._segmentData.XPLC = xplc[i];
16 |   });
17 | };


--------------------------------------------------------------------------------
/src/import/segmentHandlers/XPLT.js:
--------------------------------------------------------------------------------
 1 | import { resize } from './common';
 2 | 
 3 | export default (data, map) => {
 4 |   let view = new Uint8Array(data);
 5 |   let xplt = [];
 6 | 
 7 |   view.forEach((bits, i) => {
 8 |     xplt[i] = bits;
 9 |   });
10 | 
11 |   // resize data array from 64x64 to 128x128
12 |   xplt = resize(xplt, 64, 128);
13 | 
14 |   xplt.forEach((data, i) => {
15 |     map.cells[i]._segmentData.XPLT = xplt[i];
16 |   });
17 | };


--------------------------------------------------------------------------------
/src/import/segmentHandlers/XPOP.js:
--------------------------------------------------------------------------------
 1 | import { resize } from './common';
 2 | 
 3 | export default (data, map) => {
 4 |   let view = new Uint8Array(data);
 5 |   let xpop = [];
 6 | 
 7 |   view.forEach((bits, i) => {
 8 |     xpop[i] = bits;
 9 |   });
10 | 
11 |   // resize data array from 64x64 to 128x128
12 |   xpop = resize(xpop, 32, 128);
13 | 
14 |   xpop.forEach((data, i) => {
15 |     map.cells[i]._segmentData.XPOP = xpop[i];
16 |   });
17 | };


--------------------------------------------------------------------------------
/src/import/segmentHandlers/XROG.js:
--------------------------------------------------------------------------------
 1 | import { resize } from './common';
 2 | 
 3 | export default (data, map) => {
 4 |   let view = new Uint8Array(data);
 5 |   let xrog = [];
 6 | 
 7 |   view.forEach((bits, i) => {
 8 |     xrog[i] = bits;
 9 |   });
10 |   
11 |   // resize data array from 64x64 to 128x128
12 |   xrog = resize(xrog, 32, 128);
13 | 
14 |   xrog.forEach((data, i) => {
15 |     map.cells[i]._segmentData.XROG = xrog[i];
16 |   });
17 | };


--------------------------------------------------------------------------------
/src/import/segmentHandlers/XTER.js:
--------------------------------------------------------------------------------
  1 | import { bin2str } from './common';
  2 | import * as CONST from '../../constants';
  3 | 
  4 | export default (data, map) => {
  5 |   data.forEach((bits, i) => {
  6 |     let xter = {};
  7 | 
  8 |     xter.terrain = xterMap[bits].terrain;
  9 |     xter.water   = xterMap[bits].water;
 10 |     xter.type    = xterMap[bits].type;
 11 | 
 12 |     // raw binary values as strings for research/debug
 13 |     xter.binaryText = {
 14 |       bits: bin2str(bits, 8)
 15 |     };
 16 | 
 17 |     map.cells[i]._segmentData.XTER = xter;
 18 |   });
 19 | };
 20 | 
 21 | 
 22 | // terrain tile id is set in all cases
 23 | // so we know what type of terrain tile to
 24 | // display when water is hidden or heightmap
 25 | // is displayed
 26 | let xterMap = {
 27 |   // land
 28 |   0x00: { terrain: 256, water: null, type: CONST.TERRAIN_DRY },
 29 |   0x01: { terrain: 257, water: null, type: CONST.TERRAIN_DRY },
 30 |   0x02: { terrain: 258, water: null, type: CONST.TERRAIN_DRY },
 31 |   0x03: { terrain: 259, water: null, type: CONST.TERRAIN_DRY },
 32 |   0x04: { terrain: 260, water: null, type: CONST.TERRAIN_DRY },
 33 |   0x05: { terrain: 261, water: null, type: CONST.TERRAIN_DRY },
 34 |   0x06: { terrain: 262, water: null, type: CONST.TERRAIN_DRY },
 35 |   0x07: { terrain: 263, water: null, type: CONST.TERRAIN_DRY },
 36 |   0x08: { terrain: 264, water: null, type: CONST.TERRAIN_DRY },
 37 |   0x09: { terrain: 265, water: null, type: CONST.TERRAIN_DRY },
 38 |   0x0A: { terrain: 266, water: null, type: CONST.TERRAIN_DRY },
 39 |   0x0B: { terrain: 267, water: null, type: CONST.TERRAIN_DRY },
 40 |   0x0C: { terrain: 268, water: null, type: CONST.TERRAIN_DRY },
 41 |   0x0D: { terrain: 269, water: null, type: CONST.TERRAIN_DRY },
 42 | 
 43 |   // underwater
 44 |   0x10: { terrain: 256, water: 270, type: CONST.TERRAIN_SUBMERGED },
 45 |   0x11: { terrain: 257, water: 270, type: CONST.TERRAIN_SUBMERGED },
 46 |   0x12: { terrain: 258, water: 270, type: CONST.TERRAIN_SUBMERGED },
 47 |   0x13: { terrain: 259, water: 270, type: CONST.TERRAIN_SUBMERGED },
 48 |   0x14: { terrain: 260, water: 270, type: CONST.TERRAIN_SUBMERGED },
 49 |   0x15: { terrain: 261, water: 270, type: CONST.TERRAIN_SUBMERGED },
 50 |   0x16: { terrain: 262, water: 270, type: CONST.TERRAIN_SUBMERGED },
 51 |   0x17: { terrain: 263, water: 270, type: CONST.TERRAIN_SUBMERGED },
 52 |   0x18: { terrain: 264, water: 270, type: CONST.TERRAIN_SUBMERGED },
 53 |   0x19: { terrain: 265, water: 270, type: CONST.TERRAIN_SUBMERGED },
 54 |   0x1A: { terrain: 266, water: 270, type: CONST.TERRAIN_SUBMERGED },
 55 |   0x1B: { terrain: 267, water: 270, type: CONST.TERRAIN_SUBMERGED },
 56 |   0x1C: { terrain: 268, water: 270, type: CONST.TERRAIN_SUBMERGED },
 57 |   0x1D: { terrain: 269, water: 270, type: CONST.TERRAIN_SUBMERGED },
 58 | 
 59 |   // shoreline
 60 |   0x20: { terrain: 256, water: 270, type: CONST.TERRAIN_SHORE },
 61 |   0x21: { terrain: 257, water: 271, type: CONST.TERRAIN_SHORE },
 62 |   0x22: { terrain: 258, water: 272, type: CONST.TERRAIN_SHORE },
 63 |   0x23: { terrain: 259, water: 273, type: CONST.TERRAIN_SHORE },
 64 |   0x24: { terrain: 260, water: 274, type: CONST.TERRAIN_SHORE },
 65 |   0x25: { terrain: 261, water: 275, type: CONST.TERRAIN_SHORE },
 66 |   0x26: { terrain: 262, water: 276, type: CONST.TERRAIN_SHORE },
 67 |   0x27: { terrain: 263, water: 277, type: CONST.TERRAIN_SHORE },
 68 |   0x28: { terrain: 264, water: 278, type: CONST.TERRAIN_SHORE },
 69 |   0x29: { terrain: 265, water: 279, type: CONST.TERRAIN_SHORE },
 70 |   0x2A: { terrain: 266, water: 280, type: CONST.TERRAIN_SHORE },
 71 |   0x2B: { terrain: 267, water: 281, type: CONST.TERRAIN_SHORE },
 72 |   0x2C: { terrain: 268, water: 282, type: CONST.TERRAIN_SHORE },
 73 |   0x2D: { terrain: 269, water: 283, type: CONST.TERRAIN_SHORE },
 74 | 
 75 |   // surface water
 76 |   0x30: { terrain: 256, water: 270, type: CONST.TERRAIN_SURFACE },
 77 |   0x31: { terrain: 256, water: 271, type: CONST.TERRAIN_SURFACE },
 78 |   0x32: { terrain: 256, water: 272, type: CONST.TERRAIN_SURFACE },
 79 |   0x33: { terrain: 256, water: 273, type: CONST.TERRAIN_SURFACE },
 80 |   0x34: { terrain: 256, water: 274, type: CONST.TERRAIN_SURFACE },
 81 |   0x35: { terrain: 256, water: 275, type: CONST.TERRAIN_SURFACE },
 82 |   0x36: { terrain: 256, water: 276, type: CONST.TERRAIN_SURFACE },
 83 |   0x37: { terrain: 256, water: 277, type: CONST.TERRAIN_SURFACE },
 84 |   0x38: { terrain: 256, water: 278, type: CONST.TERRAIN_SURFACE },
 85 |   0x39: { terrain: 256, water: 279, type: CONST.TERRAIN_SURFACE },
 86 |   0x3A: { terrain: 256, water: 280, type: CONST.TERRAIN_SURFACE },
 87 |   0x3B: { terrain: 256, water: 281, type: CONST.TERRAIN_SURFACE },
 88 |   0x3C: { terrain: 256, water: 282, type: CONST.TERRAIN_SURFACE },
 89 |   0x3D: { terrain: 256, water: 283, type: CONST.TERRAIN_SURFACE },
 90 | 
 91 |   // waterfall
 92 |   0x3E: { terrain: 269, water: 284, type: CONST.TERRAIN_WATERFALL },
 93 | 
 94 |   // streams
 95 |   0x40: { terrain: 256, water: 285, type: CONST.TERRAIN_SURFACE },
 96 |   0x41: { terrain: 256, water: 286, type: CONST.TERRAIN_SURFACE },
 97 |   0x42: { terrain: 256, water: 287, type: CONST.TERRAIN_SURFACE },
 98 |   0x43: { terrain: 256, water: 288, type: CONST.TERRAIN_SURFACE },
 99 |   0x44: { terrain: 256, water: 289, type: CONST.TERRAIN_SURFACE },
100 |   0x45: { terrain: 256, water: 290, type: CONST.TERRAIN_SURFACE },
101 | };


--------------------------------------------------------------------------------
/src/import/segmentHandlers/XTHG.js:
--------------------------------------------------------------------------------
 1 | export default (data, map) => {
 2 |   let view = new DataView(data.buffer, data.byteOffset, data.byteLength);
 3 | 
 4 |   map._segmentData.XTHG = [];
 5 | 
 6 |   for (let i = 0; i < data.byteLength; i++) {
 7 |     let offset = i * 12;
 8 | 
 9 |     if (offset >= data.byteLength)
10 |       break;
11 | 
12 |     let xthg = {};
13 | 
14 |     xthg.id = view.getUint8(offset + 0);
15 |     xthg.type = types[xthg.id];
16 |     xthg.actor = actors[xthg.id];
17 | 
18 |     xthg.direction = xthg.type == 'actor' ? direction[view.getUint8(offset + 1)] : view.getUint8(offset + 1);
19 | 
20 |     xthg.x = view.getUint8(offset + 3);
21 |     xthg.y = view.getUint8(offset + 4);
22 |     xthg.z = view.getUint8(offset + 5);
23 | 
24 |     xthg.data2 = view.getUint8(offset + 2); // identifier? sequence number? type?
25 |     xthg.data7 = view.getUint8(offset + 6);
26 |     xthg.data8 = view.getUint8(offset + 7);
27 |     xthg.data9 = view.getUint8(offset + 8);
28 |     xthg.dataA = view.getUint8(offset + 9);
29 |     xthg.dataB = view.getUint8(offset + 10);
30 |     xthg.dataC = view.getUint8(offset + 11);
31 | 
32 |     map._segmentData.XTHG.push(xthg);
33 |   }
34 | };
35 | 
36 | let direction = {
37 |   0x00: 'N',
38 |   0x01: 'NE',
39 |   0x02: 'E',
40 |   0x03: 'SE',
41 |   0x04: 'S',
42 |   0x05: 'SW',
43 |   0x06: 'W',
44 |   0x07: 'NW',
45 |   0x08: 'EIGHT!'
46 | };
47 | 
48 | let types = {
49 |   0x00: null,
50 |   0x01: 'actor',
51 |   0x02: 'actor',
52 |   0x03: 'actor',
53 |   0x04: null,
54 |   0x05: 'actor',
55 |   0x06: null,
56 |   0x07: 'deploy',
57 |   0x08: 'deploy',
58 |   0x09: 'actor',
59 |   0x0A: 'actor',
60 |   0x0B: 'actor',
61 |   0x0C: null,
62 |   0x0D: null,
63 |   0x0E: 'deploy',
64 |   0x0F: 'actor',
65 |   0x10: 'actor',
66 | };
67 | 
68 | let actors = {
69 |   0x00: null,
70 |   0x01: 'Airplane',
71 |   0x02: 'Helicopter',
72 |   0x03: 'Cargo Ship',
73 |   0x04: null,
74 |   0x05: 'Monster',
75 |   0x06: null,
76 |   0x07: 'Deploy Police',
77 |   0x08: 'Deploy Fire',
78 |   0x09: 'Sailboat',
79 |   0x0A: 'Train Engine',
80 |   0x0B: 'Train Car',
81 |   0x0C: null,
82 |   0x0D: null,
83 |   0x0E: 'Deploy Military',
84 |   0x0F: 'Tornado',
85 |   0x10: 'Maxis Man',
86 | };


--------------------------------------------------------------------------------
/src/import/segmentHandlers/XTRF.js:
--------------------------------------------------------------------------------
 1 | import { resize } from './common';
 2 | 
 3 | export default (data, map) => {
 4 |   let view = new Uint8Array(data);
 5 |   let xtrf = [];
 6 | 
 7 |   view.forEach((bits, i) => {
 8 |     xtrf[i] = bits;
 9 |   });
10 | 
11 |   // resize data array from 64x64 to 128x128
12 |   xtrf = resize(xtrf, 64, 128);
13 | 
14 |   xtrf.forEach((data, i) => {
15 |     map.cells[i]._segmentData.XTRF = xtrf[i];
16 |   });
17 | };


--------------------------------------------------------------------------------
/src/import/segmentHandlers/XTXT.js:
--------------------------------------------------------------------------------
  1 | export default (data, map) => {
  2 |   let view = new Uint8Array(data);
  3 | 
  4 |   view.forEach((bits, i) => {
  5 |     let xtxt = {};
  6 | 
  7 |     xtxt.data = bits;
  8 |     xtxt.type = types[bits].type;
  9 |     xtxt.label = types[bits].label;
 10 | 
 11 |     if (xtxt.type == 'sign') {
 12 |       xtxt.sign = map._segmentData.XLAB[bits];
 13 |     }
 14 | 
 15 |     if (xtxt.type == 'microsim') {
 16 |       xtxt.microsimId = bits - 51;
 17 |       xtxt.microsimLabel = map._segmentData.XLAB[bits];
 18 | 
 19 |       let xmic = map._segmentData.XMIC[xtxt.microsimId];
 20 | 
 21 |       if (xmic !== undefined) {
 22 |         xtxt.microsimBuilding = xmic.building;
 23 |         xtxt.microsimData1 = xmic.data1;
 24 |         xtxt.microsimData2 = xmic.data2;
 25 |         xtxt.microsimData3 = xmic.data3;
 26 |         xtxt.microsimData4 = xmic.data4;
 27 |       }
 28 |     }
 29 | 
 30 |     if (xtxt.type == 'disaster'){
 31 |       xtxt.disaster = true;
 32 |     }
 33 | 
 34 |     map.cells[i]._segmentData.XTXT = xtxt;
 35 |   });
 36 | };
 37 | 
 38 | let types = {
 39 |   0x00: { type: null, label: null, reference: null },
 40 |   0x01: { type: 'sign', label: 'Sign 0', reference: 'XLAB' },
 41 |   0x02: { type: 'sign', label: 'Sign 1', reference: 'XLAB' },
 42 |   0x03: { type: 'sign', label: 'Sign 2', reference: 'XLAB' },
 43 |   0x04: { type: 'sign', label: 'Sign 3', reference: 'XLAB' },
 44 |   0x05: { type: 'sign', label: 'Sign 4', reference: 'XLAB' },
 45 |   0x06: { type: 'sign', label: 'Sign 5', reference: 'XLAB' },
 46 |   0x07: { type: 'sign', label: 'Sign 6', reference: 'XLAB' },
 47 |   0x08: { type: 'sign', label: 'Sign 7', reference: 'XLAB' },
 48 |   0x09: { type: 'sign', label: 'Sign 8', reference: 'XLAB' },
 49 |   0x0A: { type: 'sign', label: 'Sign 9', reference: 'XLAB' },
 50 |   0x0B: { type: 'sign', label: 'Sign 10', reference: 'XLAB' },
 51 |   0x0C: { type: 'sign', label: 'Sign 11', reference: 'XLAB' },
 52 |   0x0D: { type: 'sign', label: 'Sign 12', reference: 'XLAB' },
 53 |   0x0E: { type: 'sign', label: 'Sign 13', reference: 'XLAB' },
 54 |   0x0F: { type: 'sign', label: 'Sign 14', reference: 'XLAB' },
 55 |   0x10: { type: 'sign', label: 'Sign 15', reference: 'XLAB' },
 56 |   0x11: { type: 'sign', label: 'Sign 16', reference: 'XLAB' },
 57 |   0x12: { type: 'sign', label: 'Sign 17', reference: 'XLAB' },
 58 |   0x13: { type: 'sign', label: 'Sign 18', reference: 'XLAB' },
 59 |   0x14: { type: 'sign', label: 'Sign 19', reference: 'XLAB' },
 60 |   0x15: { type: 'sign', label: 'Sign 20', reference: 'XLAB' },
 61 |   0x16: { type: 'sign', label: 'Sign 21', reference: 'XLAB' },
 62 |   0x17: { type: 'sign', label: 'Sign 22', reference: 'XLAB' },
 63 |   0x18: { type: 'sign', label: 'Sign 23', reference: 'XLAB' },
 64 |   0x19: { type: 'sign', label: 'Sign 24', reference: 'XLAB' },
 65 |   0x1A: { type: 'sign', label: 'Sign 25', reference: 'XLAB' },
 66 |   0x1B: { type: 'sign', label: 'Sign 26', reference: 'XLAB' },
 67 |   0x1C: { type: 'sign', label: 'Sign 27', reference: 'XLAB' },
 68 |   0x1D: { type: 'sign', label: 'Sign 28', reference: 'XLAB' },
 69 |   0x1E: { type: 'sign', label: 'Sign 29', reference: 'XLAB' },
 70 |   0x1F: { type: 'sign', label: 'Sign 30', reference: 'XLAB' },
 71 |   0x20: { type: 'sign', label: 'Sign 31', reference: 'XLAB' },
 72 |   0x21: { type: 'sign', label: 'Sign 32', reference: 'XLAB' },
 73 |   0x22: { type: 'sign', label: 'Sign 33', reference: 'XLAB' },
 74 |   0x23: { type: 'sign', label: 'Sign 34', reference: 'XLAB' },
 75 |   0x24: { type: 'sign', label: 'Sign 35', reference: 'XLAB' },
 76 |   0x25: { type: 'sign', label: 'Sign 36', reference: 'XLAB' },
 77 |   0x26: { type: 'sign', label: 'Sign 37', reference: 'XLAB' },
 78 |   0x27: { type: 'sign', label: 'Sign 38', reference: 'XLAB' },
 79 |   0x28: { type: 'sign', label: 'Sign 39', reference: 'XLAB' },
 80 |   0x29: { type: 'sign', label: 'Sign 40', reference: 'XLAB' },
 81 |   0x2A: { type: 'sign', label: 'Sign 41', reference: 'XLAB' },
 82 |   0x2B: { type: 'sign', label: 'Sign 42', reference: 'XLAB' },
 83 |   0x2C: { type: 'sign', label: 'Sign 43', reference: 'XLAB' },
 84 |   0x2D: { type: 'sign', label: 'Sign 44', reference: 'XLAB' },
 85 |   0x2E: { type: 'sign', label: 'Sign 45', reference: 'XLAB' },
 86 |   0x2F: { type: 'sign', label: 'Sign 46', reference: 'XLAB' },
 87 |   0x30: { type: 'sign', label: 'Sign 47', reference: 'XLAB' },
 88 |   0x31: { type: 'sign', label: 'Sign 48', reference: 'XLAB' },
 89 |   0x32: { type: 'sign', label: 'Sign 49', reference: 'XLAB' },
 90 |   0x33: { type: 'microsim', label: 'Unknown Microsimulator 0', reference: 'XMIC' },
 91 |   0x34: { type: 'microsim', label: 'Bus System Microsimulator 1', reference: 'XMIC' },
 92 |   0x35: { type: 'microsim', label: 'Rail System Microsimulator 2', reference: 'XMIC' },
 93 |   0x36: { type: 'microsim', label: 'Subway System Microsimulator 3', reference: 'XMIC' },
 94 |   0x37: { type: 'microsim', label: 'Wind Power Microsimulator 4', reference: 'XMIC' },
 95 |   0x38: { type: 'microsim', label: 'Hydro Power Microsimulator 5', reference: 'XMIC' },
 96 |   0x39: { type: 'microsim', label: 'Park System Microsimulator 6', reference: 'XMIC' },
 97 |   0x3A: { type: 'microsim', label: 'Museum System Microsimulator 7', reference: 'XMIC' },
 98 |   0x3B: { type: 'microsim', label: 'Library System Microsimulator 8', reference: 'XMIC' },
 99 |   0x3C: { type: 'microsim', label: 'Marina System Microsimulator 9', reference: 'XMIC' },
100 |   0x3D: { type: 'microsim', label: 'Microsimulator 10', reference: 'XMIC' },
101 |   0x3E: { type: 'microsim', label: 'Microsimulator 11', reference: 'XMIC' },
102 |   0x3F: { type: 'microsim', label: 'Microsimulator 12', reference: 'XMIC' },
103 |   0x40: { type: 'microsim', label: 'Microsimulator 13', reference: 'XMIC' },
104 |   0x41: { type: 'microsim', label: 'Microsimulator 14', reference: 'XMIC' },
105 |   0x42: { type: 'microsim', label: 'Microsimulator 15', reference: 'XMIC' },
106 |   0x43: { type: 'microsim', label: 'Microsimulator 16', reference: 'XMIC' },
107 |   0x44: { type: 'microsim', label: 'Microsimulator 17', reference: 'XMIC' },
108 |   0x45: { type: 'microsim', label: 'Microsimulator 18', reference: 'XMIC' },
109 |   0x46: { type: 'microsim', label: 'Microsimulator 19', reference: 'XMIC' },
110 |   0x47: { type: 'microsim', label: 'Microsimulator 20', reference: 'XMIC' },
111 |   0x48: { type: 'microsim', label: 'Microsimulator 21', reference: 'XMIC' },
112 |   0x49: { type: 'microsim', label: 'Microsimulator 22', reference: 'XMIC' },
113 |   0x4A: { type: 'microsim', label: 'Microsimulator 23', reference: 'XMIC' },
114 |   0x4B: { type: 'microsim', label: 'Microsimulator 24', reference: 'XMIC' },
115 |   0x4C: { type: 'microsim', label: 'Microsimulator 25', reference: 'XMIC' },
116 |   0x4D: { type: 'microsim', label: 'Microsimulator 26', reference: 'XMIC' },
117 |   0x4E: { type: 'microsim', label: 'Microsimulator 27', reference: 'XMIC' },
118 |   0x4F: { type: 'microsim', label: 'Microsimulator 28', reference: 'XMIC' },
119 |   0x50: { type: 'microsim', label: 'Microsimulator 29', reference: 'XMIC' },
120 |   0x51: { type: 'microsim', label: 'Microsimulator 30', reference: 'XMIC' },
121 |   0x52: { type: 'microsim', label: 'Microsimulator 31', reference: 'XMIC' },
122 |   0x53: { type: 'microsim', label: 'Microsimulator 32', reference: 'XMIC' },
123 |   0x54: { type: 'microsim', label: 'Microsimulator 33', reference: 'XMIC' },
124 |   0x55: { type: 'microsim', label: 'Microsimulator 34', reference: 'XMIC' },
125 |   0x56: { type: 'microsim', label: 'Microsimulator 35', reference: 'XMIC' },
126 |   0x57: { type: 'microsim', label: 'Microsimulator 36', reference: 'XMIC' },
127 |   0x58: { type: 'microsim', label: 'Microsimulator 37', reference: 'XMIC' },
128 |   0x59: { type: 'microsim', label: 'Microsimulator 38', reference: 'XMIC' },
129 |   0x5A: { type: 'microsim', label: 'Microsimulator 39', reference: 'XMIC' },
130 |   0x5B: { type: 'microsim', label: 'Microsimulator 40', reference: 'XMIC' },
131 |   0x5C: { type: 'microsim', label: 'Microsimulator 41', reference: 'XMIC' },
132 |   0x5D: { type: 'microsim', label: 'Microsimulator 42', reference: 'XMIC' },
133 |   0x5E: { type: 'microsim', label: 'Microsimulator 43', reference: 'XMIC' },
134 |   0x5F: { type: 'microsim', label: 'Microsimulator 44', reference: 'XMIC' },
135 |   0x60: { type: 'microsim', label: 'Microsimulator 45', reference: 'XMIC' },
136 |   0x61: { type: 'microsim', label: 'Microsimulator 46', reference: 'XMIC' },
137 |   0x62: { type: 'microsim', label: 'Microsimulator 47', reference: 'XMIC' },
138 |   0x63: { type: 'microsim', label: 'Microsimulator 48', reference: 'XMIC' },
139 |   0x64: { type: 'microsim', label: 'Microsimulator 49', reference: 'XMIC' },
140 |   0x65: { type: 'microsim', label: 'Microsimulator 50', reference: 'XMIC' },
141 |   0x66: { type: 'microsim', label: 'Microsimulator 51', reference: 'XMIC' },
142 |   0x67: { type: 'microsim', label: 'Microsimulator 52', reference: 'XMIC' },
143 |   0x68: { type: 'microsim', label: 'Microsimulator 53', reference: 'XMIC' },
144 |   0x69: { type: 'microsim', label: 'Microsimulator 54', reference: 'XMIC' },
145 |   0x6A: { type: 'microsim', label: 'Microsimulator 55', reference: 'XMIC' },
146 |   0x6B: { type: 'microsim', label: 'Microsimulator 56', reference: 'XMIC' },
147 |   0x6C: { type: 'microsim', label: 'Microsimulator 57', reference: 'XMIC' },
148 |   0x6D: { type: 'microsim', label: 'Microsimulator 58', reference: 'XMIC' },
149 |   0x6E: { type: 'microsim', label: 'Microsimulator 59', reference: 'XMIC' },
150 |   0x6F: { type: 'microsim', label: 'Microsimulator 60', reference: 'XMIC' },
151 |   0x70: { type: 'microsim', label: 'Microsimulator 61', reference: 'XMIC' },
152 |   0x71: { type: 'microsim', label: 'Microsimulator 62', reference: 'XMIC' },
153 |   0x72: { type: 'microsim', label: 'Microsimulator 63', reference: 'XMIC' },
154 |   0x73: { type: 'microsim', label: 'Microsimulator 64', reference: 'XMIC' },
155 |   0x74: { type: 'microsim', label: 'Microsimulator 65', reference: 'XMIC' },
156 |   0x75: { type: 'microsim', label: 'Microsimulator 66', reference: 'XMIC' },
157 |   0x76: { type: 'microsim', label: 'Microsimulator 67', reference: 'XMIC' },
158 |   0x77: { type: 'microsim', label: 'Microsimulator 68', reference: 'XMIC' },
159 |   0x78: { type: 'microsim', label: 'Microsimulator 69', reference: 'XMIC' },
160 |   0x79: { type: 'microsim', label: 'Microsimulator 70', reference: 'XMIC' },
161 |   0x7A: { type: 'microsim', label: 'Microsimulator 71', reference: 'XMIC' },
162 |   0x7B: { type: 'microsim', label: 'Microsimulator 72', reference: 'XMIC' },
163 |   0x7C: { type: 'microsim', label: 'Microsimulator 73', reference: 'XMIC' },
164 |   0x7D: { type: 'microsim', label: 'Microsimulator 74', reference: 'XMIC' },
165 |   0x7E: { type: 'microsim', label: 'Microsimulator 75', reference: 'XMIC' },
166 |   0x7F: { type: 'microsim', label: 'Microsimulator 76', reference: 'XMIC' },
167 |   0x80: { type: 'microsim', label: 'Microsimulator 77', reference: 'XMIC' },
168 |   0x81: { type: 'microsim', label: 'Microsimulator 78', reference: 'XMIC' },
169 |   0x82: { type: 'microsim', label: 'Microsimulator 79', reference: 'XMIC' },
170 |   0x83: { type: 'microsim', label: 'Microsimulator 80', reference: 'XMIC' },
171 |   0x84: { type: 'microsim', label: 'Microsimulator 81', reference: 'XMIC' },
172 |   0x85: { type: 'microsim', label: 'Microsimulator 82', reference: 'XMIC' },
173 |   0x86: { type: 'microsim', label: 'Microsimulator 83', reference: 'XMIC' },
174 |   0x87: { type: 'microsim', label: 'Microsimulator 84', reference: 'XMIC' },
175 |   0x88: { type: 'microsim', label: 'Microsimulator 85', reference: 'XMIC' },
176 |   0x89: { type: 'microsim', label: 'Microsimulator 86', reference: 'XMIC' },
177 |   0x8A: { type: 'microsim', label: 'Microsimulator 87', reference: 'XMIC' },
178 |   0x8B: { type: 'microsim', label: 'Microsimulator 88', reference: 'XMIC' },
179 |   0x8C: { type: 'microsim', label: 'Microsimulator 89', reference: 'XMIC' },
180 |   0x8D: { type: 'microsim', label: 'Microsimulator 90', reference: 'XMIC' },
181 |   0x8E: { type: 'microsim', label: 'Microsimulator 91', reference: 'XMIC' },
182 |   0x8F: { type: 'microsim', label: 'Microsimulator 92', reference: 'XMIC' },
183 |   0x90: { type: 'microsim', label: 'Microsimulator 93', reference: 'XMIC' },
184 |   0x91: { type: 'microsim', label: 'Microsimulator 94', reference: 'XMIC' },
185 |   0x92: { type: 'microsim', label: 'Microsimulator 95', reference: 'XMIC' },
186 |   0x93: { type: 'microsim', label: 'Microsimulator 96', reference: 'XMIC' },
187 |   0x94: { type: 'microsim', label: 'Microsimulator 97', reference: 'XMIC' },
188 |   0x95: { type: 'microsim', label: 'Microsimulator 98', reference: 'XMIC' },
189 |   0x96: { type: 'microsim', label: 'Microsimulator 99', reference: 'XMIC' },
190 |   0x97: { type: 'microsim', label: 'Microsimulator 100', reference: 'XMIC' },
191 |   0x98: { type: 'microsim', label: 'Microsimulator 101', reference: 'XMIC' },
192 |   0x99: { type: 'microsim', label: 'Microsimulator 102', reference: 'XMIC' },
193 |   0x9A: { type: 'microsim', label: 'Microsimulator 103', reference: 'XMIC' },
194 |   0x9B: { type: 'microsim', label: 'Microsimulator 104', reference: 'XMIC' },
195 |   0x9C: { type: 'microsim', label: 'Microsimulator 105', reference: 'XMIC' },
196 |   0x9D: { type: 'microsim', label: 'Microsimulator 106', reference: 'XMIC' },
197 |   0x9E: { type: 'microsim', label: 'Microsimulator 107', reference: 'XMIC' },
198 |   0x9F: { type: 'microsim', label: 'Microsimulator 108', reference: 'XMIC' },
199 |   0xA0: { type: 'microsim', label: 'Microsimulator 109', reference: 'XMIC' },
200 |   0xA1: { type: 'microsim', label: 'Microsimulator 110', reference: 'XMIC' },
201 |   0xA2: { type: 'microsim', label: 'Microsimulator 111', reference: 'XMIC' },
202 |   0xA3: { type: 'microsim', label: 'Microsimulator 112', reference: 'XMIC' },
203 |   0xA4: { type: 'microsim', label: 'Microsimulator 113', reference: 'XMIC' },
204 |   0xA5: { type: 'microsim', label: 'Microsimulator 114', reference: 'XMIC' },
205 |   0xA6: { type: 'microsim', label: 'Microsimulator 115', reference: 'XMIC' },
206 |   0xA7: { type: 'microsim', label: 'Microsimulator 116', reference: 'XMIC' },
207 |   0xA8: { type: 'microsim', label: 'Microsimulator 117', reference: 'XMIC' },
208 |   0xA9: { type: 'microsim', label: 'Microsimulator 118', reference: 'XMIC' },
209 |   0xAA: { type: 'microsim', label: 'Microsimulator 119', reference: 'XMIC' },
210 |   0xAB: { type: 'microsim', label: 'Microsimulator 120', reference: 'XMIC' },
211 |   0xAC: { type: 'microsim', label: 'Microsimulator 121', reference: 'XMIC' },
212 |   0xAD: { type: 'microsim', label: 'Microsimulator 122', reference: 'XMIC' },
213 |   0xAE: { type: 'microsim', label: 'Microsimulator 123', reference: 'XMIC' },
214 |   0xAF: { type: 'microsim', label: 'Microsimulator 124', reference: 'XMIC' },
215 |   0xB0: { type: 'microsim', label: 'Microsimulator 125', reference: 'XMIC' },
216 |   0xB1: { type: 'microsim', label: 'Microsimulator 126', reference: 'XMIC' },
217 |   0xB2: { type: 'microsim', label: 'Microsimulator 127', reference: 'XMIC' },
218 |   0xB3: { type: 'microsim', label: 'Microsimulator 128', reference: 'XMIC' },
219 |   0xB4: { type: 'microsim', label: 'Microsimulator 129', reference: 'XMIC' },
220 |   0xB5: { type: 'microsim', label: 'Microsimulator 130', reference: 'XMIC' },
221 |   0xB6: { type: 'microsim', label: 'Microsimulator 131', reference: 'XMIC' },
222 |   0xB7: { type: 'microsim', label: 'Microsimulator 132', reference: 'XMIC' },
223 |   0xB8: { type: 'microsim', label: 'Microsimulator 133', reference: 'XMIC' },
224 |   0xB9: { type: 'microsim', label: 'Microsimulator 134', reference: 'XMIC' },
225 |   0xBA: { type: 'microsim', label: 'Microsimulator 135', reference: 'XMIC' },
226 |   0xBB: { type: 'microsim', label: 'Microsimulator 136', reference: 'XMIC' },
227 |   0xBC: { type: 'microsim', label: 'Microsimulator 137', reference: 'XMIC' },
228 |   0xBD: { type: 'microsim', label: 'Microsimulator 138', reference: 'XMIC' },
229 |   0xBE: { type: 'microsim', label: 'Microsimulator 139', reference: 'XMIC' },
230 |   0xBF: { type: 'microsim', label: 'Microsimulator 140', reference: 'XMIC' },
231 |   0xC0: { type: 'microsim', label: 'Microsimulator 141', reference: 'XMIC' },
232 |   0xC1: { type: 'microsim', label: 'Microsimulator 142', reference: 'XMIC' },
233 |   0xC2: { type: 'microsim', label: 'Microsimulator 143', reference: 'XMIC' },
234 |   0xC3: { type: 'microsim', label: 'Microsimulator 144', reference: 'XMIC' },
235 |   0xC4: { type: 'microsim', label: 'Microsimulator 145', reference: 'XMIC' },
236 |   0xC5: { type: 'microsim', label: 'Microsimulator 146', reference: 'XMIC' },
237 |   0xC6: { type: 'microsim', label: 'Microsimulator 147', reference: 'XMIC' },
238 |   0xC7: { type: 'microsim', label: 'Microsimulator 148', reference: 'XMIC' },
239 |   0xC8: { type: 'microsim', label: 'Microsimulator 149', reference: 'XMIC' },
240 |   0xC9: { type: 'actor', label: 'Actor 0', reference: 'XTHG' },
241 |   0xCA: { type: 'actor', label: 'Actor 1', reference: 'XTHG' },
242 |   0xCB: { type: 'actor', label: 'Actor 2', reference: 'XTHG' },
243 |   0xCC: { type: 'actor', label: 'Actor 3', reference: 'XTHG' },
244 |   0xCD: { type: 'actor', label: 'Actor 4', reference: 'XTHG' },
245 |   0xCE: { type: 'actor', label: 'Actor 5', reference: 'XTHG' },
246 |   0xCF: { type: 'actor', label: 'Actor 6', reference: 'XTHG' },
247 |   0xD0: { type: 'actor', label: 'Actor 7', reference: 'XTHG' },
248 |   0xD1: { type: 'actor', label: 'Actor 8', reference: 'XTHG' },
249 |   0xD2: { type: 'actor', label: 'Actor 9', reference: 'XTHG' },
250 |   0xD3: { type: 'actor', label: 'Actor 10', reference: 'XTHG' },
251 |   0xD4: { type: 'actor', label: 'Actor 11', reference: 'XTHG' },
252 |   0xD5: { type: 'actor', label: 'Actor 12', reference: 'XTHG' },
253 |   0xD6: { type: 'actor', label: 'Actor 13', reference: 'XTHG' },
254 |   0xD7: { type: 'actor', label: 'Actor 14', reference: 'XTHG' },
255 |   0xD8: { type: 'actor', label: 'Actor 15', reference: 'XTHG' },
256 |   0xD9: { type: 'actor', label: 'Actor 16', reference: 'XTHG' },
257 |   0xDA: { type: 'actor', label: 'Actor 17', reference: 'XTHG' },
258 |   0xDB: { type: 'actor', label: 'Actor 18', reference: 'XTHG' },
259 |   0xDC: { type: 'actor', label: 'Actor 19', reference: 'XTHG' },
260 |   0xDD: { type: 'actor', label: 'Actor 20', reference: 'XTHG' },
261 |   0xDE: { type: 'actor', label: 'Actor 21', reference: 'XTHG' },
262 |   0xDF: { type: 'actor', label: 'Actor 22', reference: 'XTHG' },
263 |   0xE0: { type: 'actor', label: 'Actor 23', reference: 'XTHG' },
264 |   0xE1: { type: 'actor', label: 'Actor 24', reference: 'XTHG' },
265 |   0xE2: { type: 'actor', label: 'Actor 25', reference: 'XTHG' },
266 |   0xE3: { type: 'actor', label: 'Actor 26', reference: 'XTHG' },
267 |   0xE4: { type: 'actor', label: 'Actor 27', reference: 'XTHG' },
268 |   0xE5: { type: 'actor', label: 'Actor 28', reference: 'XTHG' },
269 |   0xE6: { type: 'actor', label: 'Actor 29', reference: 'XTHG' },
270 |   0xE7: { type: 'actor', label: 'Actor 30', reference: 'XTHG' },
271 |   0xE8: { type: 'actor', label: 'Actor 31', reference: 'XTHG' },
272 |   0xE9: { type: 'actor', label: 'Actor 32', reference: 'XTHG' },
273 |   0xEA: { type: 'actor', label: 'Actor 33', reference: 'XTHG' },
274 |   0xEB: { type: 'actor', label: 'Actor 34', reference: 'XTHG' },
275 |   0xEC: { type: 'actor', label: 'Actor 35', reference: 'XTHG' },
276 |   0xED: { type: 'actor', label: 'Actor 36', reference: 'XTHG' },
277 |   0xEE: { type: 'actor', label: 'Actor 37', reference: 'XTHG' },
278 |   0xEF: { type: 'actor', label: 'Actor 38', reference: 'XTHG' },
279 |   0xF0: { type: 'actor', label: 'Actor 39', reference: 'XTHG' },
280 |   0xF1: { type: 'unknown', label: 'Unknown Reference 0 - 0xF1', reference: null },
281 |   0xF2: { type: 'unknown', label: 'Unknown Reference 1 - 0xF2', reference: null },
282 |   0xF3: { type: 'unknown', label: 'Unknown Reference 2 - 0xF3', reference: null },
283 |   0xF4: { type: 'unknown', label: 'Unknown Reference 3 - 0xF4', reference: null },
284 |   0xF5: { type: 'unknown', label: 'Unknown Reference 4 - 0xF5', reference: null },
285 |   0xF6: { type: 'unknown', label: 'Unknown Reference 5 - 0xF6', reference: null },
286 |   0xF7: { type: 'unknown', label: 'Unknown Reference 6 - 0xF7', reference: null },
287 |   0xF8: { type: 'unknown', label: 'Unknown Reference 7 - 0xF8', reference: null },
288 |   0xF9: { type: 'unknown', label: 'Unknown Reference 8 - 0xF9', reference: null },
289 |   0xFA: { type: 'sign', label: 'Neighbor Connection', reference: null },
290 |   0xFB: { type: 'disaster', label: 'Toxic Cloud', reference: 'XTHG' },
291 |   0xFC: { type: 'disaster', label: 'Flood', reference: 'XTHG' },
292 |   0xFD: { type: 'disaster', label: 'Rioters 1', reference: 'XTHG' },
293 |   0xFE: { type: 'disaster', label: 'Rioters 2', reference: 'XTHG' },
294 |   0xFF: { type: 'disaster', label: 'Fire', reference: 'XTHG' },
295 | };


--------------------------------------------------------------------------------
/src/import/segmentHandlers/XUND.js:
--------------------------------------------------------------------------------
 1 | import { bin2str } from './common';
 2 | import tiles from '../tiles';
 3 | 
 4 | export default (data, map) => {
 5 |   let view = new Uint8Array(data);
 6 | 
 7 |   view.forEach((bits, i) => {
 8 |     let xund = { desc: null };
 9 | 
10 |     xund.id = bits;
11 | 
12 |     if (xund.id > 0) {
13 |       xund.type     = tiles[xund.id].type    || null;
14 |       xund.subtype  = tiles[xund.id].subtype || null;
15 |       xund.desc = tiles[xund.id];
16 |     }
17 | 
18 |     // raw binary values as strings for research/debug
19 |     xund.binaryText = {
20 |       bits: bin2str(bits, 8)
21 |     };
22 | 
23 |     map.cells[i]._segmentData.XUND = xund;
24 |   });
25 | };


--------------------------------------------------------------------------------
/src/import/segmentHandlers/XVAL.js:
--------------------------------------------------------------------------------
 1 | import { resize } from './common';
 2 | 
 3 | export default (data, map) => {
 4 |   let view = new Uint8Array(data);
 5 |   let xval = [];
 6 | 
 7 |   view.forEach((bits, i) => {
 8 |     xval[i] = bits;
 9 |   });
10 | 
11 |   // resize data array from 64x64 to 128x128
12 |   xval = resize(xval, 64, 128);
13 | 
14 |   xval.forEach((data, i) => {
15 |     map.cells[i]._segmentData.XVAL = xval[i];
16 |   });
17 | };


--------------------------------------------------------------------------------
/src/import/segmentHandlers/XZON.js:
--------------------------------------------------------------------------------
 1 | import { bin2str } from './common';
 2 | 
 3 | export default (data, map) => {
 4 |   let view = new Uint8Array(data);
 5 | 
 6 |   view.forEach((bits, i) => {
 7 |     let xzon = {};
 8 | 
 9 |     // indicates the tile is a key / corner tile
10 |     // for a building larger than 1x1 tile
11 |     xzon.top    = (bits & 0b00010000) !== 0;
12 |     xzon.right  = (bits & 0b00100000) !== 0;
13 |     xzon.bottom = (bits & 0b01000000) !== 0;
14 |     xzon.left   = (bits & 0b10000000) !== 0;
15 | 
16 |     // indicates the tile has no key / corners set
17 |     xzon.none   = (bits & 0b11110000) === 0;
18 | 
19 |     // tile zone id and type
20 |     xzon.zone        = xzonMap[bits & 0b00001111].id;
21 |     xzon.zoneType    = xzonMap[bits & 0b00001111].type;
22 | 
23 |     // raw binary values as strings for research/debug
24 |     xzon.binaryText = {
25 |       bits:       bin2str(bits, 8),
26 |       first4bits: bin2str((bits & 0b11110000) >> 4, 4),
27 |       last4bits:  bin2str(bits & 0b00001111, 4)
28 |     };
29 | 
30 |     map.cells[i]._segmentData.XZON = xzon;
31 |   });
32 | };
33 | 
34 | let xzonMap = {
35 |   0x00: { id: null, type: null },
36 |   0x01: { id: 291,  type: 'l_res' },
37 |   0x02: { id: 292,  type: 'd_res' },
38 |   0x03: { id: 293,  type: 'l_comm' },
39 |   0x04: { id: 294,  type: 'd_comm' },
40 |   0x05: { id: 295,  type: 'l_ind' },
41 |   0x06: { id: 296,  type: 'd_ind' },
42 |   0x07: { id: 297,  type: 'mil' },
43 |   0x08: { id: 298,  type: 'air' },
44 |   0x09: { id: 299,  type: 'sea' },
45 | };


--------------------------------------------------------------------------------
/src/import/segmentHandlers/common.js:
--------------------------------------------------------------------------------
 1 | //
 2 | // resize a bitmap data array using nearest neighbor algorithm
 3 | //
 4 | function resize(data, sourceSize, destSize) {
 5 |   let resizedData = [];
 6 | 
 7 |   for (let i = 0; i < destSize; i++) {
 8 |     for (let j = 0; j < destSize; j++) {
 9 |       let x = Math.floor(j * sourceSize / destSize);
10 |       let y = Math.floor(i * sourceSize / destSize);
11 | 
12 |       resizedData[(i * destSize + j)] = data[(y * sourceSize + x)];
13 |     }
14 |   }
15 | 
16 |   return resizedData;
17 | }
18 | 
19 | 
20 | //
21 | // converts byte array to an ascii string
22 | //
23 | function bytesToAscii(bytes) {
24 |   return Array.prototype.map.call(bytes, x => String.fromCharCode(x)).join('');
25 | }
26 | 
27 | 
28 | //
29 | // converts numeric values to a base2 string of specified length
30 | //
31 | function bin2str(bin, length) {
32 |   return bin.toString(2).padStart(length, '0');
33 | }
34 | 
35 | export { resize, bytesToAscii, bin2str };


--------------------------------------------------------------------------------
/src/import/segmentHandlers/index.js:
--------------------------------------------------------------------------------
 1 | import ALTM from './ALTM';
 2 | import CNAM from './CNAM';
 3 | import MISC from './MISC';
 4 | import PICT from './PICT';
 5 | import SCEN from './SCEN';
 6 | import TEXT from './TEXT';
 7 | import TMPL from './TMPL';
 8 | import XBIT from './XBIT';
 9 | import XBLD from './XBLD';
10 | import XCRM from './XCRM';
11 | import XFIR from './XFIR';
12 | import XGRP from './XGRP';
13 | import XLAB from './XLAB';
14 | import XMIC from './XMIC';
15 | import XPLC from './XPLC';
16 | import XPLT from './XPLT';
17 | import XPOP from './XPOP';
18 | import XROG from './XROG';
19 | import XTER from './XTER';
20 | import XTHG from './XTHG';
21 | import XTRF from './XTRF';
22 | import XTXT from './XTXT';
23 | import XUND from './XUND';
24 | import XVAL from './XVAL';
25 | import XZON from './XZON';
26 | 
27 | export { ALTM, CNAM, MISC, PICT, SCEN, TEXT, TMPL, XBIT, XBLD, XCRM, XFIR, XGRP, XLAB, XMIC, XPLC, XPLT, XPOP, XROG, XTER, XTHG, XTRF, XTXT, XUND, XVAL, XZON };


--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
 1 | <!DOCTYPE html>
 2 | <html lang="en">
 3 |   <head>
 4 |     <meta charset="UTF-8">
 5 |     <title>OpenSC2K</title>
 6 |     <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
 7 |     <meta http-equiv="cleartype" content="on">
 8 |   </head>
 9 |   <body>
10 |     <div id="content"></div>
11 |   </body>
12 | </html>


--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
 1 | import Phaser from 'phaser';
 2 | import world from './world';
 3 | import $ from 'jquery';
 4 | import jQuery from 'jquery';
 5 | import './styles/global.css';
 6 | 
 7 | var config = {
 8 |   gameTitle: 'OpenSC2K',
 9 |   gameURL: 'https://github.com/rage8885/OpenSC2K',
10 |   type: Phaser.WEBGL,
11 |   resolution: 1,
12 |   autoRound: true,
13 |   disableContextMenu: false,
14 |   banner: true,
15 |   audio: {
16 |     noAudio: true,
17 |   },
18 |   render: {
19 |     antialias: false,
20 |     pixelArt: true,
21 |     batchSize: 32767
22 |   },
23 |   scale: {
24 |     mode: Phaser.DOM.RESIZE,
25 |     parent: 'content',
26 |     width: window.innerWidth,
27 |     height: window.innerHeight,
28 |   },
29 |   scene: [
30 |     world
31 |   ]
32 | };
33 | 
34 | window.$ = $;
35 | window.jQuery = jQuery;
36 | window.game = new Phaser.Game(config);


--------------------------------------------------------------------------------
/src/simulation/actors/actors.js:
--------------------------------------------------------------------------------
 1 | export default class actor {
 2 |   constructor (options) {
 3 |     this.scene = options.scene;
 4 |     this.common = this.scene.sys.game.common;
 5 | 
 6 |     this.sprite = undefined;
 7 |     this.cell = undefined;
 8 | 
 9 |     this.depth = 10000;
10 | 
11 |     this.tick = 0; // current tick
12 |     this.tickFrequency = 10; // how often to update
13 | 
14 |     this.x = 0;
15 |     this.y = 0;
16 |     this.z = 0;
17 |   }
18 | 
19 |   spawn () {
20 |     return;
21 |   }
22 | 
23 |   update () {
24 |     
25 |   }
26 | 
27 |   sleep () {
28 | 
29 |   }
30 | 
31 |   wake () {
32 | 
33 |   }
34 | 
35 |   hide () {
36 | 
37 |   }
38 | 
39 |   show () {
40 |     
41 |   }
42 | 
43 |   destroy () {
44 |     this.sprites = [];
45 |   }
46 | 
47 |   calculatePosition () {
48 |     this.offset = 0;
49 |     this.x = this.cell.position.bottom.x - (this.tile.width / 2) << 0;
50 |     this.y = this.cell.position.bottom.y - (this.tile.height) - this.offset << 0;
51 |   }
52 | 
53 |   getTile (tileId) {
54 |     if (!this.common.tiles[tileId])
55 |       return false;
56 | 
57 |     tileId = this.common.tiles[tileId].rotate[this.scene.city.cameraRotation];
58 | 
59 |     return this.common.tiles[tileId];
60 |   }
61 | 
62 |   create () {
63 |     this.calculatePosition();
64 | 
65 |     if (this.tile.frames > 1)
66 |       this.sprite = this.scene.add.sprite(this.x, this.y, this.common.tilemap).play(this.tile.image);
67 |     else
68 |       this.sprite = this.scene.add.sprite(this.x, this.y, this.common.tilemap, this.tile.textures[0]);
69 | 
70 |     //console.log(this.sprite);
71 | 
72 |     this.sprite.cell = this.cell;
73 |     this.sprite.type = this.type;
74 | 
75 |     this.sprite.setScale(this.common.scale);
76 |     this.sprite.setOrigin(0, 0);
77 |     this.sprite.setDepth(this.cell.depth + this.depth + (this.tile.depthAdjustment || 0));
78 |   }
79 | 
80 |   addSprite (sprite) {
81 |     this.sprites.push(sprite);
82 |   }
83 | }


--------------------------------------------------------------------------------
/src/simulation/actors/trains.js:
--------------------------------------------------------------------------------
 1 | import actors from './actors';
 2 | 
 3 | export default class trains extends actors {
 4 |   constructor (options) {
 5 |     super(options);
 6 | 
 7 |     this.type = 'train';
 8 |     this.heading = undefined;
 9 |     this.cars = 3;
10 |   }
11 | 
12 |   spawn (cell) {
13 |     this.cell = cell;
14 |     this.tile = this.getTile(374);
15 | 
16 |     this.x = this.cell.x;
17 |     this.y = this.cell.y;
18 |     this.z = this.cell.z;
19 | 
20 |     this.create();
21 |   }
22 | }


--------------------------------------------------------------------------------
/src/simulation/global/global.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholas-ochoa/OpenSC2K/efb769bf01fb7ac56a97dd0f259a662fec36f260/src/simulation/global/global.js


--------------------------------------------------------------------------------
/src/simulation/global/rail.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholas-ochoa/OpenSC2K/efb769bf01fb7ac56a97dd0f259a662fec36f260/src/simulation/global/rail.js


--------------------------------------------------------------------------------
/src/simulation/index.js:
--------------------------------------------------------------------------------
 1 | export default class simulation {
 2 |   constructor (options) {
 3 |     this.scene = options.scene;
 4 |     this.common = this.scene.sys.game.common;
 5 |     this.sims = [];
 6 |     this.simTick = 0;
 7 | 
 8 |     this.paused = false;
 9 | 
10 |     this.timer = this.scene.time.addEvent({
11 |       delay: 1000,
12 |       callback: () => { this.tick(); },
13 |       cellbackScope: this,
14 |       loop: true
15 |     });
16 |   }
17 | 
18 |   tick () {
19 |     if (this.paused)
20 |       return;
21 | 
22 |     this.sims.forEach((sim) => {
23 |       
24 |     });
25 | 
26 |     this.simTick++;
27 |   }
28 | 
29 |   register () {
30 | 
31 |   }
32 | 
33 |   deregister () {
34 |     
35 |   }
36 | 
37 |   sleep () {
38 |     this.paused = true;
39 |   }
40 | 
41 |   wake () {
42 |     this.paused = false;
43 |   }
44 | }


--------------------------------------------------------------------------------
/src/simulation/micro/highwayTraffic.js:
--------------------------------------------------------------------------------
 1 | import traffic from './traffic';
 2 | 
 3 | export default class highwayTraffic extends traffic {
 4 |   constructor (options) {
 5 |     super(options);
 6 | 
 7 |     this.lightThreshold = 30;
 8 |     this.heavyThreshold = 58;
 9 |     this.trafficDirection = null;
10 |   }
11 | 
12 |   
13 |   // determine traffic direction
14 |   calculateTrafficDirection () {
15 |     if (this.cell.highway.onramp)
16 |       console.log('onramp');
17 | 
18 |     let cells = this.cell.surrounding();
19 |     
20 |     // north / south highways
21 |     if (this.cell.highway.tile.direction == 'ns' && !this.cell.highway.onramp)
22 |       if (!cells.e.highway && cells.w.highway && !cells.w.highway.onramp)
23 |         this.trafficDirection = 'nb';
24 |       else if (!cells.w.highway && cells.e.highway && !cells.e.highway.onramp)
25 |         this.trafficDirection = 'sb';
26 |       else if (cells.e.highway && cells.w.highway && !cells.w.highway.onramp)
27 |         this.trafficDirection = 'nb';
28 |       else if (cells.w.highway && cells.e.highway && !cells.e.highway.onramp)
29 |         this.trafficDirection = 'sb';
30 | 
31 |     // east / west highways
32 |     if (this.cell.highway.tile.direction == 'ew' && !this.cell.highway.onramp)
33 |       if (!cells.s.highway && cells.n.highway && !cells.n.highway.onramp)
34 |         this.trafficDirection = 'eb';
35 |       else if (!cells.n.highway && cells.s.highway && !cells.s.highway.onramp)
36 |         this.trafficDirection = 'wb';
37 |       else if (cells.s.highway && cells.n.highway && !cells.n.highway.onramp)
38 |         this.trafficDirection = 'eb';
39 |       else if (cells.n.highway && cells.s.highway && !cells.s.highway.onramp)
40 |         this.trafficDirection = 'wb';
41 | 
42 |     // north / south onramps
43 |     if (this.cell.highway.onramp && cells.n.highway.tile.direction == 'ew')
44 |       this.trafficDirection = 'wb';
45 | 
46 |     if (this.cell.highway.onramp && cells.w.highway.tile.direction == 'ns')
47 |       this.trafficDirection = 'sb';
48 | 
49 | 
50 |     if (['nb','wb'].includes(this.trafficDirection))
51 |       this.cell.highway.reverseAnimation = false;
52 |     else
53 |       this.cell.highway.reverseAnimation = true;
54 | 
55 |     return this.cell.highway.reverseAnimation;
56 |   }
57 | }


--------------------------------------------------------------------------------
/src/simulation/micro/simulation.js:
--------------------------------------------------------------------------------
 1 | export default class simulation {
 2 |   constructor (options) {
 3 |     this.scene = options.scene;
 4 |     this.common = this.scene.sys.game.common;
 5 |     this.cell = options.cell;
 6 |     this.data = this.cell.data;
 7 | 
 8 |     this.sprites = [];
 9 | 
10 |     this.tick = 0; // current tick
11 |     this.tickFrequency = 10; // how often to update
12 | 
13 |     this.x = options.cell.x;
14 |     this.y = options.cell.y;
15 |     this.z = options.cell.z;
16 |   }
17 | 
18 |   create () {
19 | 
20 |   }
21 | 
22 |   update () {
23 |     
24 |   }
25 | 
26 |   sleep () {
27 | 
28 |   }
29 | 
30 |   wake () {
31 | 
32 |   }
33 | 
34 |   hide () {
35 | 
36 |   }
37 | 
38 |   show () {
39 |     
40 |   }
41 | 
42 |   destroy () {
43 |     this.sprites = [];
44 |   }
45 | 
46 |   addSprite (sprite) {
47 |     this.sprites.push(sprite);
48 |   }
49 | }


--------------------------------------------------------------------------------
/src/simulation/micro/traffic.js:
--------------------------------------------------------------------------------
 1 | import simulation from './simulation';
 2 | 
 3 | export default class traffic extends simulation {
 4 |   constructor (options) {
 5 |     super(options);
 6 | 
 7 |     this.current = 0;
 8 |     this.average = 0;
 9 |     this.maximum = 0;
10 | 
11 |     this.lightThreshold = 86;
12 |     this.heavyThreshold = 172;
13 |   }
14 | 
15 |   create () {
16 |     if (this.cell.data._XTRF > 0)
17 |       this.current = this.cell.data._XTRF;
18 |     else
19 |       this.current = 0;
20 |   }
21 | 
22 |   getTrafficDensity () {
23 |     if (this.current < this.lightThreshold)
24 |       return null;
25 | 
26 |     if (this.current >= this.lightThreshold && this.current < this.heavyThreshold)
27 |       return 'light';
28 | 
29 |     if (this.current >= this.heavyThreshold)
30 |       return 'heavy';
31 |   }
32 | }


--------------------------------------------------------------------------------
/src/styles/global.css:
--------------------------------------------------------------------------------
 1 | html,
 2 | body {
 3 |   margin: 0;
 4 |   padding: 0;
 5 |   background-color: #000;
 6 |   overflow: hidden;
 7 |   image-rendering: optimizeSpeed;              /* Older versions of FF */
 8 |   image-rendering: -moz-crisp-edges;           /* FF 6.0+ */
 9 |   image-rendering: -webkit-optimize-contrast;  /* Webkit (non standard naming) */
10 |   image-rendering: -o-crisp-edges;             /* OS X & Windows Opera (12.02+) */
11 |   image-rendering: crisp-edges;                /* Possible future browsers. */
12 |   -ms-interpolation-mode: nearest-neighbor;    /* IE (non standard naming) */
13 |   image-rendering: pixelated;                  /* Chrome 41 */
14 | }
15 | 
16 | #fileOpen {
17 |   visibility: hidden;
18 | }
19 | 
20 | .ui-container {
21 |   top: 0;
22 |   left: 0;
23 |   position: absolute;
24 | }
25 | 
26 | .toolbar {
27 |   top: 50px;
28 |   left: 50px;
29 | }


--------------------------------------------------------------------------------
/src/ui/gui.js:
--------------------------------------------------------------------------------
 1 | import '../../lib/fomantic-ui-2.7.1/semantic.css';
 2 | import '../../lib/fomantic-ui-2.7.1/semantic.js';
 3 | import $ from 'jquery';
 4 | 
 5 | let body = $('body');
 6 | 
 7 | let modalHtml = `
 8 | <div class='ui-container'>
 9 | 
10 |   <div class='ui grid toolbar'>
11 |     <div class='column'>
12 | 
13 |       <div class='row'>
14 |         <button class="ui icon button">
15 |           <i class="cloud icon"></i>
16 |         </button>
17 |         <button class="ui icon button">
18 |           <i class="cloud icon"></i>
19 |         </button>
20 |         <button class="ui icon button">
21 |           <i class="cloud icon"></i>
22 |         </button>
23 |       </div>
24 | 
25 |       <div class='row'>
26 |         <button class="ui icon button">
27 |           <i class="cloud icon"></i>
28 |         </button>
29 |         <button class="ui icon button">
30 |           <i class="cloud icon"></i>
31 |         </button>
32 |         <button class="ui icon button">
33 |           <i class="cloud icon"></i>
34 |         </button>
35 |       </div>
36 | 
37 |       <div class='row'>
38 |         <button class="ui icon button">
39 |           <i class="cloud icon"></i>
40 |         </button>
41 |         <button class="ui icon button">
42 |           <i class="cloud icon"></i>
43 |         </button>
44 |         <button class="ui icon button">
45 |           <i class="cloud icon"></i>
46 |         </button>
47 |       </div>
48 |       
49 |     </div>
50 |   </div>
51 | </div>
52 | `;
53 | 
54 | //body.append(modalHtml);
55 | 
56 | //$('.ui.modal').modal();


--------------------------------------------------------------------------------
/src/ui/ui.js:
--------------------------------------------------------------------------------
  1 | import Phaser from 'phaser';
  2 | 
  3 | export default class ui extends Phaser.Scene {
  4 |   constructor () {
  5 |     super({
  6 |       key: 'ui',
  7 |       active: true
  8 |     });
  9 | 
 10 |     this.debug = false;
 11 |     this.debugOffset = -250;
 12 |     this.boundsBuffer = 300;
 13 |     
 14 |     this.viewport = {
 15 |       displayBounds: {},
 16 |       displayBoundsBuffer: {},
 17 |       worldBounds: {},
 18 |       worldBoundsBuffer: {}
 19 |     };
 20 | 
 21 |     this.preloadComplete = false;
 22 |     this.initialized = false;
 23 |   }
 24 | 
 25 |   preload () {
 26 |     this.common = this.sys.game.common;
 27 |     this.preloadComplete = true;
 28 |   }
 29 | 
 30 |   create () {
 31 |     if (!this.preloadComplete)
 32 |       return;
 33 | 
 34 |     this.common.ui = this;
 35 |     this.initialized = true;
 36 |   }
 37 | 
 38 | 
 39 |   updateBounds () {
 40 |     if (!this.common || !this.common.world || !this.common.world.worldCamera)
 41 |       return;
 42 | 
 43 |     let camera = this.common.world.worldCamera.camera;
 44 | 
 45 |     this.viewport.width = document.documentElement.clientWidth;
 46 |     this.viewport.height = document.documentElement.clientHeight;
 47 | 
 48 |     this.viewport.displayBounds.topLeft           = new Phaser.Geom.Point(0 + this.debugOffset,                     0 + this.debugOffset);
 49 |     this.viewport.displayBounds.bottomLeft        = new Phaser.Geom.Point(0 + this.debugOffset,                     this.viewport.height - this.debugOffset);
 50 |     this.viewport.displayBounds.topRight          = new Phaser.Geom.Point(this.viewport.width - this.debugOffset,   0 + this.debugOffset);
 51 |     this.viewport.displayBounds.bottomRight       = new Phaser.Geom.Point(this.viewport.width - this.debugOffset,   this.viewport.height - this.debugOffset);
 52 |     this.viewport.displayBounds.width             = this.viewport.displayBounds.bottomRight.x - this.viewport.displayBounds.topLeft.x;
 53 |     this.viewport.displayBounds.height            = this.viewport.displayBounds.bottomLeft.y - this.viewport.displayBounds.topLeft.y;
 54 |     this.viewport.displayBounds.rect              = new Phaser.Geom.Rectangle(this.viewport.displayBounds.topLeft.x, this.viewport.displayBounds.topLeft.y, this.viewport.displayBounds.width, this.viewport.displayBounds.height);
 55 | 
 56 |     this.viewport.displayBoundsBuffer.topLeft     = new Phaser.Geom.Point(this.viewport.displayBounds.topLeft.x + this.boundsBuffer,      this.viewport.displayBounds.topLeft.y + this.boundsBuffer);
 57 |     this.viewport.displayBoundsBuffer.bottomLeft  = new Phaser.Geom.Point(this.viewport.displayBounds.bottomLeft.x + this.boundsBuffer,   this.viewport.displayBounds.bottomLeft.y - this.boundsBuffer);
 58 |     this.viewport.displayBoundsBuffer.topRight    = new Phaser.Geom.Point(this.viewport.displayBounds.topRight.x - this.boundsBuffer,     this.viewport.displayBounds.topRight.y + this.boundsBuffer);
 59 |     this.viewport.displayBoundsBuffer.bottomRight = new Phaser.Geom.Point(this.viewport.displayBounds.bottomRight.x - this.boundsBuffer,  this.viewport.displayBounds.bottomRight.y - this.boundsBuffer);
 60 |     this.viewport.displayBoundsBuffer.width       = this.viewport.displayBoundsBuffer.bottomRight.x - this.viewport.displayBoundsBuffer.topLeft.x;
 61 |     this.viewport.displayBoundsBuffer.height      = this.viewport.displayBoundsBuffer.bottomLeft.y - this.viewport.displayBoundsBuffer.topLeft.y;
 62 |     this.viewport.displayBoundsBuffer.rect        = new Phaser.Geom.Rectangle(this.viewport.displayBoundsBuffer.topLeft.x, this.viewport.displayBoundsBuffer.topLeft.y, this.viewport.displayBoundsBuffer.width, this.viewport.displayBoundsBuffer.height);
 63 | 
 64 |     this.viewport.worldBounds.topLeft     = camera.getWorldPoint(this.viewport.displayBounds.topLeft.x,      this.viewport.displayBounds.topLeft.y);
 65 |     this.viewport.worldBounds.bottomLeft  = camera.getWorldPoint(this.viewport.displayBounds.bottomLeft.x,   this.viewport.displayBounds.bottomLeft.y);
 66 |     this.viewport.worldBounds.topRight    = camera.getWorldPoint(this.viewport.displayBounds.topRight.x,     this.viewport.displayBounds.topRight.y);
 67 |     this.viewport.worldBounds.bottomRight = camera.getWorldPoint(this.viewport.displayBounds.bottomRight.x,  this.viewport.displayBounds.bottomRight.y);
 68 |     this.viewport.worldBounds.width       = this.viewport.worldBounds.bottomRight.x - this.viewport.worldBounds.topLeft.x;
 69 |     this.viewport.worldBounds.height      = this.viewport.worldBounds.bottomLeft.y - this.viewport.worldBounds.topLeft.y;
 70 |     this.viewport.worldBounds.rect        = new Phaser.Geom.Rectangle(this.viewport.worldBounds.topLeft.x, this.viewport.worldBounds.topLeft.y, this.viewport.worldBounds.width, this.viewport.worldBounds.height);
 71 | 
 72 |     this.viewport.worldBoundsBuffer.topLeft     = camera.getWorldPoint(this.viewport.displayBoundsBuffer.topLeft.x,      this.viewport.displayBoundsBuffer.topLeft.y);
 73 |     this.viewport.worldBoundsBuffer.bottomLeft  = camera.getWorldPoint(this.viewport.displayBoundsBuffer.bottomLeft.x,   this.viewport.displayBoundsBuffer.bottomLeft.y);
 74 |     this.viewport.worldBoundsBuffer.topRight    = camera.getWorldPoint(this.viewport.displayBoundsBuffer.topRight.x,     this.viewport.displayBoundsBuffer.topRight.y);
 75 |     this.viewport.worldBoundsBuffer.bottomRight = camera.getWorldPoint(this.viewport.displayBoundsBuffer.bottomRight.x,  this.viewport.displayBoundsBuffer.bottomRight.y);
 76 |     this.viewport.worldBoundsBuffer.width       = this.viewport.worldBoundsBuffer.bottomRight.x - this.viewport.worldBoundsBuffer.topLeft.x;
 77 |     this.viewport.worldBoundsBuffer.height      = this.viewport.worldBoundsBuffer.bottomLeft.y - this.viewport.worldBoundsBuffer.topLeft.y;
 78 |     this.viewport.worldBoundsBuffer.rect        = new Phaser.Geom.Rectangle(this.viewport.worldBoundsBuffer.topLeft.x, this.viewport.worldBoundsBuffer.topLeft.y, this.viewport.worldBoundsBuffer.width, this.viewport.worldBoundsBuffer.height);
 79 | 
 80 |     if (!this.displayArea && !this.displayAreaBuffer && this.debug) {
 81 |       this.displayArea = this.add.graphics();
 82 |       this.displayArea.fillStyle(0xAA0000, .25);
 83 |       this.displayArea.lineStyle(2, 0xAA0000, 1);
 84 |       this.displayArea.fillRectShape(this.viewport.displayBounds.rect);
 85 |       this.displayArea.strokeRectShape(this.viewport.displayBounds.rect);
 86 |       this.displayArea.setDepth(999999999);
 87 | 
 88 |       this.displayAreaBuffer = this.add.graphics();
 89 |       this.displayAreaBuffer.fillStyle(0xAAAA00, .25);
 90 |       this.displayAreaBuffer.lineStyle(2, 0xAAAA00, 1);
 91 |       this.displayAreaBuffer.fillRectShape(this.viewport.displayBoundsBuffer.rect);
 92 |       this.displayAreaBuffer.strokeRectShape(this.viewport.displayBoundsBuffer.rect);
 93 |       this.displayAreaBuffer.setDepth(999999999);
 94 |       //this.displayAreaBuffer.setVisible(false);
 95 |       
 96 |     }
 97 | 
 98 |     this.common.viewport = this.viewport;
 99 |   }
100 | 
101 |   // update (time, delta) {
102 |   //   this.updateBounds();
103 |   // }
104 | 
105 |   resize () {
106 |     this.cameras.main.setViewport(0, 0, document.documentElement.clientWidth, document.documentElement.clientHeight);
107 | 
108 |     if (this.displayArea && this.displayAreaBuffer && this.debug) {
109 |       this.displayArea.clear();
110 |       this.displayAreaBuffer.clear();
111 | 
112 |       this.displayArea = undefined;
113 |       this.displayAreaBuffer = undefined;
114 |     }
115 |   }
116 | 
117 |   shutdown () {
118 |     if (!this.initialized)
119 |       return;
120 | 
121 |     this.initialized = false;
122 |     this.preloadComplete = false;
123 | 
124 |     this.scene.stop();
125 |   }
126 | 
127 | }


--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
 1 | //
 2 | //
 3 | //
 4 | function wrap (value, min, max) {
 5 |   if (value > max)
 6 |     value = min;
 7 | 
 8 |   if (value < min)
 9 |     value = max;
10 | 
11 |   return value;
12 | }
13 | 
14 | 
15 | //
16 | //
17 | //
18 | function isNumberBetween (value, min, max) {
19 |   return value >= min && value <= max;
20 | }
21 | 
22 | 
23 | //
24 | // adapted from https://github.com/lovasoa/graham-fast
25 | // licensed under the MIT license
26 | //
27 | function polygonUnion(poly1, poly2) {
28 |   let points = [];
29 | 
30 |   for (let i = 0; i < poly1.length; i++)
31 |     points.push([poly1[i].x, poly1[i].y]);
32 | 
33 |   for (let i = 0; i < poly2.length; i++)
34 |     points.push([poly2[i].x, poly2[i].y]);
35 | 
36 |   // The enveloppe is the points themselves
37 |   if (points.length <= 3)
38 |     return points;
39 |   
40 |   // Find the pivot
41 |   let pivot = points[0];
42 | 
43 |   for (let i = 0; i < points.length; i++)
44 |     if (points[i][1] < pivot[1] || (points[i][1] === pivot[1] && points[i][0] < pivot[0]))
45 |       pivot = points[i];
46 | 
47 |   // Attribute an angle to the points
48 |   for (let i = 0; i < points.length; i++)
49 |     points[i]._graham_angle = Math.atan2(points[i][1] - pivot[1], points[i][0] - pivot[0]);
50 | 
51 |   points.sort(function(a, b){
52 |     return a._graham_angle === b._graham_angle ? a[0] - b[0] : a._graham_angle - b._graham_angle;
53 |   });
54 | 
55 | 
56 |   // Adding points to the result if they "turn left"
57 |   let result = [points[0]];
58 |   let len = 1;
59 | 
60 |   for (let i = 1; i < points.length; i++) {
61 |     let a = result[len-2];
62 |     let b = result[len-1];
63 |     let c = points[i];
64 | 
65 |     while ((len === 1 && b[0] === c[0] && b[1] === c[1]) || (len > 1 && (b[0]-a[0]) * (c[1]-a[1]) <= (b[1]-a[1]) * (c[0]-a[0]))) {
66 |       len--;
67 |       b = a;
68 |       a = result[len-2];
69 |     }
70 | 
71 |     result[len++] = c;
72 |   }
73 | 
74 |   result.length = len;
75 |   
76 |   // create new polygon object
77 |   let polygon = [];
78 | 
79 |   for (let i = 0; i < result.length; i++)
80 |     polygon.push({ x: result[i][0], y: result[i][1] });
81 | 
82 |   return polygon;
83 | }
84 | 
85 | export { wrap, isNumberBetween, polygonUnion };


--------------------------------------------------------------------------------
/src/utils/calculatePathBetweenCells.js:
--------------------------------------------------------------------------------
 1 | export default function (start, end) {
 2 |   let dx = end.x - start.x;
 3 |   let dy = end.y - start.y;
 4 | 
 5 |   let nx = Math.abs(dx);
 6 |   let ny = Math.abs(dy);
 7 | 
 8 |   let sx = dx > 0? 1 : -1;
 9 |   let sy = dy > 0? 1 : -1;
10 | 
11 |   let point = { x: start.x, y: start.y };
12 |   let cells = [];
13 | 
14 |   cells.push({ x: start.x, y: start.y });
15 | 
16 |   for (let ix = 0, iy = 0; ix < nx || iy < ny;) {
17 |     if ((0.5 + ix) / nx < (0.5 + iy) / ny) {
18 |       point.x += sx;
19 |       ix++;
20 |     } else {
21 |       point.y += sy;
22 |       iy++;
23 |     }
24 |     
25 |     cells.push({ x: point.x, y: point.y });
26 |   }
27 | 
28 |   return cells;
29 | }


--------------------------------------------------------------------------------
/src/world.js:
--------------------------------------------------------------------------------
 1 | import Phaser from 'phaser';
 2 | import city from './city/city';
 3 | import viewport from './world/viewport';
 4 | import events from './world/events';
 5 | import debug from './debug/debug';
 6 | import palette from './import/palette';
 7 | import artwork from './import/artwork';
 8 | import * as CONST from './constants';
 9 | import ui from './ui/gui';
10 | 
11 | export default class world extends Phaser.Scene {
12 |   constructor () {
13 |     super({ key: 'world' });
14 |   }
15 |   
16 | 
17 |   preload () {
18 |     this.sys.game.world = this;
19 | 
20 |     // load binary game assets from original SC2K
21 |     this.load.binary(CONST.PAL_MSTR_BMP, CONST.ASSETS_PATH + CONST.FILE_PAL_MSTR_BMP);
22 |     this.load.binary(CONST.LARGE_DAT,    CONST.ASSETS_PATH + CONST.FILE_LARGE_DAT);
23 | 
24 |     // start import once files are loaded
25 |     this.load.once(CONST.E_LOAD_COMPLETE, () => {
26 |       this.palette = new palette({ scene: this });
27 |       this.artwork = new artwork({ scene: this });
28 |       this.tiles = this.artwork.tiles;
29 | 
30 |       // initialize city
31 |       this.city = new city({ scene: this });
32 |     });
33 |   }
34 |   
35 | 
36 |   create () {
37 |     // load default city
38 |     this.city.load.loadDefaultCity().then(() => {
39 |       this.start();
40 |     });
41 | 
42 |     //this.ui = new ui({ scene: this });
43 |     this.viewport = new viewport({ scene: this });
44 |     this._events = new events({ scene: this });
45 |     this.debug = new debug({ scene: this });
46 |   }
47 | 
48 | 
49 |   start () {
50 |     this.city.create();
51 |   }
52 |   
53 | 
54 |   update (time, delta) {
55 |     this.viewport.update(delta);
56 |     this.city.update();
57 |   }
58 |   
59 | 
60 |   shutdown () {
61 |     this.scene.destroy();
62 |   }
63 | }


--------------------------------------------------------------------------------
/src/world/events.js:
--------------------------------------------------------------------------------
 1 | import * as tools from './tools/';
 2 | import * as CONST from '../constants';
 3 | 
 4 | export default class events {
 5 |   constructor(options) {
 6 |     this.scene = options.scene;
 7 |     //this.selectedTool = CONST.TOOL_CENTER;
 8 |     //this.selectedTool = CONST.TOOL_QUERY;
 9 |     this.selectedTool = CONST.TOOL_ROADS;
10 |     this.register();
11 | 
12 |     this.tools = {};
13 | 
14 |     Object.keys(tools).forEach((t) => {
15 |       this.tools[t] = new tools[t]({ scene: this.scene });
16 |     });
17 |   }
18 |   
19 | 
20 |   register () {
21 |     //window.addEventListener(CONST.E_RESIZE, () => {
22 |     //  this.scene.game.scale.resize(window.innerWidth, window.innerHeight);
23 |     //});
24 | 
25 |     this.scene.scale.on(CONST.E_RESIZE, this.resize, this);
26 |     this.scene.input.on(CONST.E_POINTER_OVER, this.onPointerOver, this);
27 |     this.scene.input.on(CONST.E_POINTER_OUT,  this.onPointerOut,  this);
28 |     this.scene.input.on(CONST.E_POINTER_MOVE, this.onPointerMove, this);
29 |     this.scene.input.on(CONST.E_POINTER_DOWN, this.onPointerDown, this);
30 |     this.scene.input.on(CONST.E_POINTER_UP,   this.onPointerUp,   this);
31 |   }
32 | 
33 | 
34 |   onPointerUp (pointer) {
35 |     if (this.selectedTool)
36 |       this.tools[this.selectedTool].onPointerUp(pointer);
37 |   }
38 | 
39 | 
40 |   onPointerDown (pointer, camera) {
41 |     if (this.selectedTool)
42 |       this.tools[this.selectedTool].onPointerDown(pointer, camera);
43 |   }
44 | 
45 | 
46 |   onPointerMove (pointer, localX, localY) {
47 |     this.scene.viewport.onPointerMove(pointer);
48 | 
49 |     if (this.selectedTool)
50 |       this.tools[this.selectedTool].onPointerMove(pointer, localX, localY);
51 |   }
52 | 
53 | 
54 |   onPointerOver (pointer, localX, localY) {
55 |     if (this.selectedTool)
56 |       this.tools[this.selectedTool].onPointerOver(pointer, localX, localY);
57 |   }
58 | 
59 | 
60 |   onPointerOut (pointer) {
61 |     if (this.selectedTool)
62 |       this.tools[this.selectedTool].onPointerOut(pointer);
63 |   }
64 | 
65 | 
66 |   resize (gameSize) {
67 |     this.scene.cameras.resize(gameSize.width, gameSize.height);
68 |   }
69 | }


--------------------------------------------------------------------------------
/src/world/tools/center.js:
--------------------------------------------------------------------------------
 1 | export default class center {
 2 |   constructor (options) {
 3 |     this.scene = options.scene;
 4 |     this.globals = options.scene.globals;
 5 |   }
 6 | 
 7 |   onPointerUp (pointer) {
 8 |     //console.log('center onPointerUp', pointer);
 9 |   }
10 | 
11 |   onPointerDown (pointer, gameObject) {
12 |     pointer.camera.pan(pointer.worldX, pointer.worldY, 150, 'Quart.easeInOut', false, () => {
13 |       this.scene.viewport.cullObjects();
14 |     });
15 |   }
16 | 
17 |   onPointerMove (pointer, localX, localY) {
18 |     //console.log('center onPointerMove', pointer, localX, localY);
19 |   }
20 | 
21 |   onPointerOver (pointer, localX, localY) {
22 |     //console.log('center onPointerOver', pointer, localX, localY);
23 |   }
24 | 
25 |   onPointerOut (pointer) {
26 |     //console.log('center onPointerOut', pointer);
27 |   }
28 | }


--------------------------------------------------------------------------------
/src/world/tools/index.js:
--------------------------------------------------------------------------------
1 | import center from './center';
2 | import query from './query';
3 | import roads from './roads';
4 | 
5 | export { center, query, roads };


--------------------------------------------------------------------------------
/src/world/tools/query.js:
--------------------------------------------------------------------------------
 1 | export default class query {
 2 |   constructor (options) {
 3 |     this.scene = options.scene;
 4 |     this.globals = options.scene.globals;
 5 |   }
 6 | 
 7 |   onPointerUp (pointer) {
 8 |     //console.log('query onPointerUp', pointer);
 9 |   }
10 | 
11 |   onPointerDown (pointer, gameObject) {
12 |     if (gameObject[0])
13 |       console.log(gameObject[0].cell);
14 | 
15 |     //if (gameObject[0])
16 |     //  console.log(gameObject[0].cell.depth, gameObject[0].cell.tiles.terrain.sprite.depth, gameObject[0].cell.tiles.terrain.depth, gameObject[0].cell);
17 |   }
18 | 
19 |   onPointerMove (pointer, localX, localY) {
20 |     //console.log('query onPointerMove', pointer, localX, localY);
21 |   }
22 | 
23 |   onPointerOver (pointer, localX, localY) {
24 |     //console.log('query onPointerOver', pointer, localX, localY);
25 |   }
26 | 
27 |   onPointerOut (pointer) {
28 |     //console.log('query onPointerOut', pointer);
29 |   }
30 | }


--------------------------------------------------------------------------------
/src/world/tools/roads.js:
--------------------------------------------------------------------------------
 1 | import calculatePathBetweenCells from '../../utils/calculatePathBetweenCells';
 2 | import * as CONST from '../../constants';
 3 | 
 4 | export default class roads {
 5 |   scene;
 6 |   direction;
 7 |   active = false;
 8 |   start  = { x: 0, y: 0 };
 9 |   end    = { x: 0, y: 0 };
10 |   cells  = [];
11 | 
12 |   tile = {
13 |     'ns': 29,
14 |     'ew': 30,
15 |   };
16 | 
17 | 
18 |   constructor (options) {
19 |     this.scene = options.scene;
20 |   }
21 | 
22 |   // y = 0   north
23 |   // y = 127 south
24 |   // x = 0   west
25 |   // x = 127 east
26 | 
27 |   calculateDirection () {
28 |     let sx = this.start.x;
29 |     let sy = this.start.y;
30 | 
31 |     let ex = this.end.x;
32 |     let ey = this.end.y;
33 |     
34 |     if (sx == ex && sy != ey) this.direction = 'ns';
35 | 
36 |     if (sx != ex && sy == ey) this.direction = 'ew';
37 |   }
38 | 
39 | 
40 |   onPointerDown (pointer, gameObject) {
41 |     this.active = true;
42 |     this.cells = [];
43 | 
44 |     if (gameObject[0])
45 |       this.start = { x: gameObject[0].cell.x, y: gameObject[0].cell.y };
46 |   }
47 | 
48 | 
49 |   onPointerUp () {
50 |     this.active = false;
51 | 
52 |     this.cells.forEach((cell) => {
53 |       cell.clearHighlight();
54 |     });
55 | 
56 |     this.calculateDirection();
57 | 
58 |     if (this.cells.length > 0)
59 |       this.cells.forEach((cell) => {
60 |         cell.tiles.set(CONST.T_ROAD, this.tile[this.direction]);
61 |         cell.tiles[CONST.T_ROAD].create();
62 |       });
63 |   }
64 | 
65 | 
66 |   onPointerMove () {
67 |     return;
68 |   }
69 | 
70 | 
71 |   onPointerOver (pointer, gameObject) {
72 |     if (!this.active) return;
73 | 
74 |     if (gameObject[0])
75 |       this.end = { x: gameObject[0].cell.x, y: gameObject[0].cell.y };
76 | 
77 |     let list = calculatePathBetweenCells(this.start, this.end);
78 |     
79 |     for (let i = 0; i < list.length; i++)
80 |       this.cells.push(this.scene.city.map.cells?.[list[i].x]?.[list[i].y]);
81 | 
82 |     this.cells.forEach((cell) => {
83 |       cell.highlight();
84 |     });
85 |   }
86 | 
87 | 
88 |   onPointerOut () {
89 |     if (!this.active) return;
90 | 
91 |     this.cells.forEach((cell) => {
92 |       cell.clearHighlight();
93 |     });
94 | 
95 |     this.cells = [];
96 |     return;
97 |   }
98 | }


--------------------------------------------------------------------------------
/src/world/viewport.js:
--------------------------------------------------------------------------------
 1 | import Phaser from 'phaser';
 2 | import * as CONST from '../constants';
 3 | 
 4 | export default class viewport {
 5 |   constructor(options) {
 6 |     this.scene        = options.scene;
 7 |     this.camera       = this.scene.cameras.main;
 8 |     this.camera.name  = CONST.CAMERA_NAME;
 9 |     this.worldView    = this.camera.worldView;
10 | 
11 |     this.objectsRendered = 0;
12 | 
13 |     this.viewportPaddingMultiplier = 1.5;
14 | 
15 |     this.worldPoint = {
16 |       x: 0,
17 |       y: 0
18 |     };
19 | 
20 |     let keys = this.scene.input.keyboard.addKeys({
21 |       'up': Phaser.Input.Keyboard.KeyCodes.W,
22 |       'down': Phaser.Input.Keyboard.KeyCodes.S,
23 |       'left': Phaser.Input.Keyboard.KeyCodes.A,
24 |       'right': Phaser.Input.Keyboard.KeyCodes.D,
25 |       'zoomIn': Phaser.Input.Keyboard.KeyCodes.Q,
26 |       'zoomOut': Phaser.Input.Keyboard.KeyCodes.E,
27 |     });
28 | 
29 |     let controlConfig = {
30 |       camera: this.camera,
31 |       up: keys.up,
32 |       down: keys.down,
33 |       left: keys.left,
34 |       right: keys.right,
35 |       zoomIn: keys.zoomIn,
36 |       zoomOut: keys.zoomOut,
37 |       acceleration: 0.04,
38 |       drag: 0.0005,
39 |       maxSpeed: 1
40 |     };
41 | 
42 |     this.controls = new Phaser.Cameras.Controls.SmoothedKeyControl(controlConfig);
43 | 
44 |     this.camera.scrollX = -1531;
45 |     this.camera.scrollY = 140;
46 |     this.camera.zoom = 1;
47 |   }
48 | 
49 | 
50 |   onPointerMove (pointer) {
51 |     let { x, y } = this.camera.getWorldPoint(pointer.x, pointer.y);
52 | 
53 |     this.worldPoint.x = x;
54 |     this.worldPoint.y = y;
55 |   }
56 | 
57 | 
58 |   update (delta) {
59 |     this.controls.update(delta);
60 | 
61 |     if (this.controls._speedX !== 0 || this.controls._speedY !== 0 || this.controls._zoom !== 0)
62 |       this.cullObjects();
63 |   }
64 | 
65 | 
66 |   cullObjects () {
67 |     let cells = this.scene.city.map.cells;
68 |     let view  = Phaser.Geom.Rectangle.Clone(this.worldView);
69 |     let cx = view.centerX;
70 |     let cy = view.centerY;
71 | 
72 |     Phaser.Geom.Rectangle.Scale(view, this.viewportPaddingMultiplier);
73 |     Phaser.Geom.Rectangle.CenterOn(view, cx, cy);
74 | 
75 |     for (let x = 0; x < CONST.MAP_SIZE; x++)
76 |       for (let y = 0; y < CONST.MAP_SIZE; y++)
77 |         if (Phaser.Geom.Rectangle.Contains(view, cells[x][y].position.center.x, cells[x][y].position.center.y))
78 |           cells[x][y].show();
79 |         else
80 |           cells[x][y].hide();
81 | 
82 | 
83 |     this.objectsRendered = 0;
84 | 
85 |     this.scene.children.list.forEach((gameObject) => {
86 |       if (gameObject.visible) this.objectsRendered++;
87 |     });
88 |   }
89 | }


--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
 1 | const path = require('path');
 2 | const webpack = require('webpack');
 3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
 4 | const phaser = path.join(__dirname, '/node_modules/phaser/src/phaser.js');
 5 | 
 6 | module.exports = {
 7 |   mode: 'development',
 8 |   entry: {
 9 |     app: ['@babel/polyfill', path.resolve(__dirname, 'src/index.js')]
10 |   },
11 |   output: {
12 |     pathinfo: true,
13 |     path: path.resolve(__dirname, 'dist'),
14 |     filename: 'app.js'
15 |   },
16 |   devtool: 'source-map',
17 |   devServer: {
18 |     contentBase: path.join(__dirname, './'),
19 |     port: 3000,
20 |     disableHostCheck: true
21 |   },
22 |   optimization: {
23 |     runtimeChunk: false,
24 |     splitChunks: {
25 |       cacheGroups: {
26 |         commons: {
27 |           test: /[\\/]node_modules[\\/]/,
28 |           name: 'vendors',
29 |           chunks: 'all',
30 |         },
31 |       }
32 |     }
33 |   },
34 |   plugins: [
35 |     new webpack.DefinePlugin({
36 |       WEBGL_RENDERER: true,
37 |       CANVAS_RENDERER: true,
38 |     }),
39 |     new HtmlWebpackPlugin({
40 |       template: 'src/index.html'
41 |     }),
42 |     new webpack.ProvidePlugin({
43 |       $: 'jquery',
44 |       jQuery: 'jquery'
45 |     })
46 |   ],
47 |   resolve: {
48 |     alias: {
49 |       'phaser': phaser
50 |     }
51 |   },
52 |   module: {
53 |     rules: [
54 |       {
55 |         test: /\.js$/,
56 |         exclude: /(node_modules)/,
57 |         use: [{
58 |           loader: 'babel-loader',
59 |           options: {
60 |             cacheDirectory: true
61 |           }
62 |         }],
63 |       },
64 |       {
65 |         test: /\.(css)$/,
66 |         use: [
67 |           'style-loader',
68 |           {
69 |             loader: 'css-loader',
70 |             options: {
71 |               sourceMap: true
72 |             }
73 |           }
74 |         ]
75 |       },
76 |       {
77 |         test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/,
78 |         loader: 'url-loader',
79 |         options: {
80 |           limit: 10000
81 |         }
82 |       },
83 |       {
84 |         test: /phaser-split\.js$/,
85 |         loader: 'expose-loader',
86 |         options: 'Phaser'
87 |       },
88 |       {
89 |         test: [/\.vert$/, /\.frag$/],
90 |         loader: 'raw-loader'
91 |       }
92 |     ]
93 |   }
94 | };


--------------------------------------------------------------------------------